Merged with dev

This commit is contained in:
bubnikv 2018-09-17 15:12:13 +02:00
parent d934b63424
commit fe3b92870f
90 changed files with 3310 additions and 1748 deletions

View File

@ -334,28 +334,4 @@ sub set_menu_item_icon {
}
}
sub save_window_pos {
my ($self, $window, $name) = @_;
$self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY);
$self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH);
$self->{app_config}->set("${name}_maximized", $window->IsMaximized);
$self->{app_config}->save;
}
sub restore_window_pos {
my ($self, $window, $name) = @_;
if ($self->{app_config}->has("${name}_pos")) {
my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ];
$window->SetSize($size);
my $display = Wx::Display->new->GetClientArea();
my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ];
if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) {
$window->Move($pos);
}
$window->Maximize(1) if $self->{app_config}->get("${name}_maximized");
}
}
1;

View File

@ -95,7 +95,7 @@ sub new {
$self->Fit;
$self->SetMinSize([760, 490]);
$self->SetSize($self->GetMinSize);
wxTheApp->restore_window_pos($self, "main_frame");
Slic3r::GUI::restore_window_size($self, "main_frame");
$self->Show;
$self->Layout;
}
@ -108,7 +108,7 @@ sub new {
return;
}
# save window size
wxTheApp->save_window_pos($self, "main_frame");
Slic3r::GUI::save_window_size($self, "main_frame");
# Save the slic3r.ini. Usually the ini file is saved from "on idle" callback,
# but in rare cases it may not have been called yet.
wxTheApp->{app_config}->save;
@ -207,10 +207,12 @@ sub _init_tabpanel {
EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub {
my ($self, $event) = @_;
my $obj_idx = $event->GetId;
my $child = $event->GetInt == 1 ? 1 : undef;
# 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);
$self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx, $child);
$self->{plater}->item_changed_selection($obj_idx);
my $vol_idx = $event->GetInt;
$self->{plater}->select_object_from_cpp($obj_idx < 0 ? undef: $obj_idx, $vol_idx<0 ? -1 : $vol_idx);
});
# The following event is emited by the C++ GUI implementation on object settings change.

View File

@ -104,10 +104,11 @@ sub new {
# callback to enable/disable action buttons
my $enable_action_buttons = sub {
my ($enable) = @_;
$self->{btn_export_gcode}->Enable($enable);
$self->{btn_reslice}->Enable($enable);
$self->{btn_print}->Enable($enable);
$self->{btn_send_gcode}->Enable($enable);
Slic3r::GUI::enable_action_buttons($enable);
# $self->{btn_export_gcode}->Enable($enable);
# $self->{btn_reslice}->Enable($enable);
# $self->{btn_print}->Enable($enable);
# $self->{btn_send_gcode}->Enable($enable);
};
# callback to react to gizmo scale
@ -230,7 +231,14 @@ sub new {
my ($obj_idx, $object) = $self->selected_object;
if (defined $obj_idx) {
my $vol_idx = Slic3r::GUI::_3DScene::get_first_volume_id($self->{canvas3D}, $obj_idx);
Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $vol_idx) if ($vol_idx != -1);
#Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $vol_idx) if ($vol_idx != -1);
my $inst_cnt = $self->{model}->objects->[$obj_idx]->instances_count;
for (0..$inst_cnt-1){
Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $_ + $vol_idx) if ($vol_idx != -1);
}
my $volume_idx = Slic3r::GUI::_3DScene::get_in_object_volume_id($self->{canvas3D}, $vol_idx);
Slic3r::GUI::select_current_volume($obj_idx, $volume_idx) if ($volume_idx != -1);
}
}
};
@ -368,26 +376,35 @@ sub new {
# }
### Panel for right column
# $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);
$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);
### Scrolled Window for panel without "Export G-code" and "Slice now" buttons
my $scrolled_window_sizer = $self->{scrolled_window_sizer} = Wx::BoxSizer->new(wxVERTICAL);
$scrolled_window_sizer->SetMinSize([320, -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(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], 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);
$self->{btn_export_stl} = Wx::Button->new($self->{right_panel}, -1, L("Export STL…"), 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]);#, wxNO_BORDER);#, 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);
$self->{btn_print} = Wx::Button->new($scrolled_window_panel, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_send_gcode} = Wx::Button->new($scrolled_window_panel, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT);
#$self->{btn_export_stl} = Wx::Button->new($self->{right_panel}, -1, L("Export STL…"), wxDefaultPosition, [-1, 30], wxBU_LEFT);
#$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font);
#$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font);
$self->{btn_print}->Hide;
$self->{btn_send_gcode}->Hide;
# export_gcode cog_go.png
#! reslice reslice.png
my %icons = qw(
print arrow_up.png
send_gcode arrow_up.png
reslice reslice.png
export_stl brick_go.png
);
for (grep $self->{"btn_$_"}, keys %icons) {
@ -494,9 +511,11 @@ sub new {
# $self->{preset_choosers}{$group}[$idx]
$self->{preset_choosers} = {};
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);
# my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
my $text = Wx::StaticText->new($scrolled_window_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);
# my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
my $choice = Wx::BitmapComboBox->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
if ($group eq 'filament') {
EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } );
}
@ -515,26 +534,13 @@ sub new {
}
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;
# }
#! Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets);
Slic3r::GUI::add_frequently_changed_parameters($self->{scrolled_window_panel}, $frequently_changed_parameters_sizer, $presets);
my $object_info_sizer;
{
# my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info"));
my $box = Wx::StaticBox->new($self->{right_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]);
@ -554,25 +560,26 @@ 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($self->{right_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);
# $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"} = 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} = Wx::StaticBitmap->new($self->{right_panel}, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG));
$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]) {
# this fuction show/hide info_manifold_warning_icon on the c++ side now
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
#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);
@ -590,18 +597,19 @@ sub new {
}
}
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($self->{right_panel}, -1, L("Sliced Info")), wxVERTICAL);
my $print_info_box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info"));
$print_info_box->SetFont($Slic3r::GUI::small_bold_font);
my $print_info_sizer = $self->{print_info_sizer} = Wx::StaticBoxSizer->new($print_info_box, 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);
$self->{buttons_sizer} = $buttons_sizer;
$buttons_sizer->AddStretchSpacer(1);
$buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0);
$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_stl}, 0, wxALIGN_RIGHT, 0);
#! $buttons_sizer->Add($self->{btn_reslice}, 0, wxALIGN_RIGHT, 0);
$buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT | wxBOTTOM | wxTOP, 5);
$buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT | wxBOTTOM | wxTOP, 5);
# $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5);
# $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0);
@ -611,24 +619,39 @@ sub new {
### 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);
$info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxTOP, 20);
$info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxTOP, 20);
$scrolled_window_sizer->Add($presets, 0, wxEXPAND | wxLEFT, 2) if defined $presets;
$scrolled_window_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxLEFT, 0) if defined $frequently_changed_parameters_sizer;
$scrolled_window_sizer->Add($buttons_sizer, 0, wxEXPAND, 0);
$scrolled_window_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);
### Sizer for "Export G-code" & "Slice now" buttons
my $btns_sizer = Wx::BoxSizer->new(wxVERTICAL);
$btns_sizer->SetMinSize([318, -1]);
$btns_sizer->Add($self->{btn_reslice}, 0, wxEXPAND, 0);
$btns_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxTOP, 5);
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
$self->{right_panel}->SetSizer($right_sizer);
$right_sizer->SetMinSize([320, -1]);
$right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets;
$right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer;
$right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 10) if defined $expert_mode_part_sizer;
$right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10);
$right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20);
#! $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets;
#! $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer;
#! $right_sizer->Add($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);
$right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20);
#! $self->print_info_box_show(0);
$right_sizer->Add($scrolled_window_panel, 1, wxEXPAND | wxTOP, 5);
# $right_sizer->Add($self->{btn_reslice}, 0, wxEXPAND | wxLEFT | wxTOP, 20);
# $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP, 20);
$right_sizer->Add($btns_sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1);
$hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 3);
$hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 0);#3);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
# $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar};
@ -639,16 +662,21 @@ sub new {
$self->SetSizer($sizer);
# Send sizers/buttons to C++
Slic3r::GUI::set_objects_from_perl( $self->{right_panel},
Slic3r::GUI::set_objects_from_perl( $self->{scrolled_window_panel},
$frequently_changed_parameters_sizer,
$expert_mode_part_sizer,
$info_sizer,
$self->{btn_export_gcode},
$self->{btn_export_stl},
# $self->{btn_export_stl},
$self->{btn_reslice},
$self->{btn_print},
$self->{btn_send_gcode},
$self->{object_info_manifold_warning_icon} );
Slic3r::GUI::set_model_events_from_perl( $self->{model},
$self->{event_object_selection_changed},
$self->{event_object_settings_changed},
$self->{event_remove_object},
$self->{event_update_scene});
}
# Last correct selected item for each preset
@ -1594,14 +1622,16 @@ sub print_info_box_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 $panel = $self->{scrolled_window_panel};#$self->{right_panel};
my $sizer = $self->{info_sizer};
return if (!$sizer || !$show && ($sizer->IsShown(1) == $show));
# return if (!$sizer || !$show && ($sizer->IsShown(1) == $show));
return if (!$sizer);
Slic3r::GUI::set_show_print_info($show);
return if (wxTheApp->{app_config}->get("view_mode") eq "simple");
# return if (wxTheApp->{app_config}->get("view_mode") eq "simple");
if ($show) {
# if ($show)
{
my $print_info_sizer = $self->{print_info_sizer};
$print_info_sizer->Clear(1);
my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5);
@ -1609,20 +1639,37 @@ sub print_info_box_show {
$grid_sizer->AddGrowableCol(1, 1);
$grid_sizer->AddGrowableCol(3, 1);
$print_info_sizer->Add($grid_sizer, 0, wxEXPAND);
my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0;
my @info = (
L("Used Filament (m)")
=> sprintf("%.2f" , $self->{print}->total_used_filament / 1000),
=> $is_wipe_tower ?
sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_used_filament / 1000,
($self->{print}->total_used_filament - $self->{print}->total_wipe_tower_filament) / 1000,
L("objects"),
$self->{print}->total_wipe_tower_filament / 1000,
L("wipe tower")) :
sprintf("%.2f" , $self->{print}->total_used_filament / 1000),
L("Used Filament (mm³)")
=> sprintf("%.2f" , $self->{print}->total_extruded_volume),
L("Used Filament (g)"),
=> sprintf("%.2f" , $self->{print}->total_weight),
L("Cost"),
=> sprintf("%.2f" , $self->{print}->total_cost),
=> $is_wipe_tower ?
sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_cost,
($self->{print}->total_cost - $self->{print}->total_wipe_tower_cost),
L("objects"),
$self->{print}->total_wipe_tower_cost,
L("wipe tower")) :
sprintf("%.2f" , $self->{print}->total_cost),
L("Estimated printing time (normal mode)")
=> $self->{print}->estimated_normal_print_time,
L("Estimated printing time (silent mode)")
=> $self->{print}->estimated_silent_print_time
);
# if there is a wipe tower, insert number of toolchanges info into the array:
splice (@info, 8, 0, L("Number of tool changes") => sprintf("%.d", $self->{print}->wipe_tower_number_of_toolchanges)) if ($is_wipe_tower);
while ( my $label = shift @info) {
my $value = shift @info;
next if $value eq "N/A";
@ -1639,7 +1686,7 @@ sub print_info_box_show {
# $scrolled_window_sizer->Show(2, $show);
# $scrolled_window_panel->Layout;
$sizer->Show(1, $show);
$sizer->Show(1, $show && wxTheApp->{app_config}->get("view_mode") ne "simple");
$self->Layout;
$panel->Refresh;
@ -1823,6 +1870,7 @@ sub _get_export_file {
# (i.e. when an object is added/removed/moved/rotated/scaled)
sub update {
my ($self, $force_autocenter) = @_;
$self->Freeze;
if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) {
$self->{model}->center_instances_around_point($self->bed_centerf);
}
@ -1836,6 +1884,7 @@ sub update {
$self->{preview3D}->reset_gcode_preview_data if $self->{preview3D};
$self->{preview3D}->reload_print if $self->{preview3D};
$self->schedule_background_process;
$self->Thaw;
}
# When a printer technology is changed, the UI needs to be updated to show/hide needed preset combo boxes.
@ -1868,7 +1917,8 @@ sub on_extruders_change {
my @presets = $choices->[0]->GetStrings;
# initialize new choice
my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
# my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
my $choice = Wx::BitmapComboBox->new($self->{scrolled_window_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
my $extruder_idx = scalar @$choices;
EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } );
push @$choices, $choice;
@ -1895,6 +1945,7 @@ sub on_extruders_change {
$choices->[-1]->Destroy;
pop @$choices;
}
$self->{right_panel}->Layout;
$self->Layout;
}
@ -2221,15 +2272,59 @@ sub select_object {
if (defined $obj_idx) {
$self->{objects}->[$obj_idx]->selected(1);
# Select current object in the list on c++ side, if item isn't child
if (!defined $child){
Slic3r::GUI::select_current_object($obj_idx);}
# if (!defined $child){
# Slic3r::GUI::select_current_object($obj_idx);} # all selections in the object list is on c++ side
} else {
# Unselect all objects in the list on c++ side
Slic3r::GUI::unselect_objects();
# Slic3r::GUI::unselect_objects(); # all selections in the object list is on c++ side
}
$self->selection_changed(1);
}
sub select_object_from_cpp {
my ($self, $obj_idx, $vol_idx) = @_;
# remove current selection
foreach my $o (0..$#{$self->{objects}}) {
$self->{objects}->[$o]->selected(0);
}
my $curr = Slic3r::GUI::_3DScene::get_select_by($self->{canvas3D});
if (defined $obj_idx) {
if ($vol_idx == -1){
if ($curr eq 'object') {
$self->{objects}->[$obj_idx]->selected(1);
}
elsif ($curr eq 'volume') {
Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'object');
}
my $selections = $self->collect_selections;
Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1);
}
else {
if ($curr eq 'object') {
Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'volume');
}
my $selections = [];
Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D});
Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1);
my $volume_idx = Slic3r::GUI::_3DScene::get_first_volume_id($self->{canvas3D}, $obj_idx);
my $inst_cnt = $self->{model}->objects->[$obj_idx]->instances_count;
for (0..$inst_cnt-1){
Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $vol_idx*$inst_cnt + $_ + $volume_idx) if ($volume_idx != -1);
}
}
}
$self->selection_changed(1);
}
sub selected_object {
my ($self) = @_;
my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} };

View File

@ -5,12 +5,12 @@ use utf8;
use Slic3r::Print::State ':steps';
use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE wxCB_READONLY);
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX EVT_CHOICE EVT_CHECKLISTBOX);
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX EVT_CHOICE EVT_CHECKLISTBOX EVT_SIZE);
use base qw(Wx::Panel Class::Accessor);
use Wx::Locale gettext => 'L';
__PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer));
__PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer double_slider_sizer));
sub new {
my $class = shift;
@ -27,47 +27,47 @@ sub new {
Slic3r::GUI::_3DScene::enable_shader($canvas, 1);
Slic3r::GUI::_3DScene::set_config($canvas, $config);
$self->canvas($canvas);
my $slider_low = Wx::Slider->new(
$self, -1,
0, # default
0, # min
# my $slider_low = Wx::Slider->new(
# $self, -1,
# 0, # default
# 0, # min
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
# will skip drawing the slider if max <= min:
1, # max
wxDefaultPosition,
wxDefaultSize,
wxVERTICAL | wxSL_INVERSE,
);
$self->slider_low($slider_low);
my $slider_high = Wx::Slider->new(
$self, -1,
0, # default
0, # min
# 1, # max
# wxDefaultPosition,
# wxDefaultSize,
# wxVERTICAL | wxSL_INVERSE,
# );
# $self->slider_low($slider_low);
# my $slider_high = Wx::Slider->new(
# $self, -1,
# 0, # default
# 0, # min
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
# will skip drawing the slider if max <= min:
1, # max
wxDefaultPosition,
wxDefaultSize,
wxVERTICAL | wxSL_INVERSE,
);
$self->slider_high($slider_high);
# 1, # max
# wxDefaultPosition,
# wxDefaultSize,
# wxVERTICAL | wxSL_INVERSE,
# );
# $self->slider_high($slider_high);
my $z_label_low = $self->{z_label_low} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
$z_label_low->SetFont($Slic3r::GUI::small_font);
my $z_label_high = $self->{z_label_high} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
$z_label_high->SetFont($Slic3r::GUI::small_font);
# my $z_label_low = $self->{z_label_low} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
# [40,-1], wxALIGN_CENTRE_HORIZONTAL);
# $z_label_low->SetFont($Slic3r::GUI::small_font);
# my $z_label_high = $self->{z_label_high} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
# [40,-1], wxALIGN_CENTRE_HORIZONTAL);
# $z_label_high->SetFont($Slic3r::GUI::small_font);
my $z_label_low_idx = $self->{z_label_low_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
$z_label_low_idx->SetFont($Slic3r::GUI::small_font);
my $z_label_high_idx = $self->{z_label_high_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
$z_label_high_idx->SetFont($Slic3r::GUI::small_font);
# my $z_label_low_idx = $self->{z_label_low_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
# [40,-1], wxALIGN_CENTRE_HORIZONTAL);
# $z_label_low_idx->SetFont($Slic3r::GUI::small_font);
# my $z_label_high_idx = $self->{z_label_high_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
# [40,-1], wxALIGN_CENTRE_HORIZONTAL);
# $z_label_high_idx->SetFont($Slic3r::GUI::small_font);
$self->single_layer(0);
my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, L("1 Layer"));
# $self->single_layer(0);
# my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, L("1 Layer"));
my $label_view_type = $self->{label_view_type} = Wx::StaticText->new($self, -1, L("View"));
@ -103,25 +103,30 @@ sub new {
.L("Custom");
Slic3r::GUI::create_combochecklist($combochecklist_features, $feature_text, $feature_items, 1);
my $double_slider_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
Slic3r::GUI::create_double_slider($self, $double_slider_sizer, $self->canvas);
$self->double_slider_sizer($double_slider_sizer);
my $checkbox_travel = $self->{checkbox_travel} = Wx::CheckBox->new($self, -1, L("Travel"));
my $checkbox_retractions = $self->{checkbox_retractions} = Wx::CheckBox->new($self, -1, L("Retractions"));
my $checkbox_unretractions = $self->{checkbox_unretractions} = Wx::CheckBox->new($self, -1, L("Unretractions"));
my $checkbox_shells = $self->{checkbox_shells} = Wx::CheckBox->new($self, -1, L("Shells"));
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
my $vsizer_outer = Wx::BoxSizer->new(wxVERTICAL);
$vsizer->Add($slider_low, 3, wxALIGN_CENTER_HORIZONTAL, 0);
$vsizer->Add($z_label_low_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0);
$vsizer->Add($z_label_low, 0, wxALIGN_CENTER_HORIZONTAL, 0);
$hsizer->Add($vsizer, 0, wxEXPAND, 0);
$vsizer = Wx::BoxSizer->new(wxVERTICAL);
$vsizer->Add($slider_high, 3, wxALIGN_CENTER_HORIZONTAL, 0);
$vsizer->Add($z_label_high_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0);
$vsizer->Add($z_label_high, 0, 0, 0);
$hsizer->Add($vsizer, 0, wxEXPAND, 0);
$vsizer_outer->Add($hsizer, 3, wxALIGN_CENTER_HORIZONTAL, 0);
$vsizer_outer->Add($checkbox_singlelayer, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5);
# my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
# my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
# my $vsizer_outer = Wx::BoxSizer->new(wxVERTICAL);
# $vsizer->Add($slider_low, 3, wxALIGN_CENTER_HORIZONTAL, 0);
# $vsizer->Add($z_label_low_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0);
# $vsizer->Add($z_label_low, 0, wxALIGN_CENTER_HORIZONTAL, 0);
# $hsizer->Add($vsizer, 0, wxEXPAND, 0);
# $vsizer = Wx::BoxSizer->new(wxVERTICAL);
# $vsizer->Add($slider_high, 3, wxALIGN_CENTER_HORIZONTAL, 0);
# $vsizer->Add($z_label_high_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0);
# $vsizer->Add($z_label_high, 0, 0, 0);
# $hsizer->Add($vsizer, 0, wxEXPAND, 0);
# $vsizer_outer->Add($hsizer, 3, wxALIGN_CENTER_HORIZONTAL, 0);
# $vsizer_outer->Add($double_slider_sizer, 3, wxEXPAND, 0);
# $vsizer_outer->Add($checkbox_singlelayer, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5);
my $bottom_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$bottom_sizer->Add($label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5);
@ -140,81 +145,83 @@ sub new {
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
$sizer->Add($vsizer_outer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
# $sizer->Add($vsizer_outer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
$sizer->Add($double_slider_sizer, 0, wxEXPAND, 0);#wxTOP | wxBOTTOM | wxEXPAND, 5);
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
$main_sizer->Add($sizer, 1, wxALL | wxEXPAND, 0);
$main_sizer->Add($bottom_sizer, 0, wxALL | wxEXPAND, 0);
EVT_SLIDER($self, $slider_low, sub {
$slider_high->SetValue($slider_low->GetValue) if $self->single_layer;
$self->set_z_idx_low ($slider_low ->GetValue)
});
EVT_SLIDER($self, $slider_high, sub {
$slider_low->SetValue($slider_high->GetValue) if $self->single_layer;
$self->set_z_idx_high($slider_high->GetValue)
});
EVT_KEY_DOWN($canvas, sub {
my ($s, $event) = @_;
my $key = $event->GetKeyCode;
if ($event->HasModifiers) {
$event->Skip;
} else {
if ($key == ord('U')) {
$slider_high->SetValue($slider_high->GetValue + 1);
$slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown());
$self->set_z_idx_high($slider_high->GetValue);
} elsif ($key == ord('D')) {
$slider_high->SetValue($slider_high->GetValue - 1);
$slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown());
$self->set_z_idx_high($slider_high->GetValue);
} elsif ($key == ord('S')) {
$checkbox_singlelayer->SetValue(! $checkbox_singlelayer->GetValue());
$self->single_layer($checkbox_singlelayer->GetValue());
if ($self->single_layer) {
$slider_low->SetValue($slider_high->GetValue);
$self->set_z_idx_high($slider_high->GetValue);
}
} else {
$event->Skip;
}
}
});
EVT_KEY_DOWN($slider_low, sub {
my ($s, $event) = @_;
my $key = $event->GetKeyCode;
if ($event->HasModifiers) {
$event->Skip;
} else {
if ($key == WXK_LEFT) {
} elsif ($key == WXK_RIGHT) {
$slider_high->SetFocus;
} else {
$event->Skip;
}
}
});
EVT_KEY_DOWN($slider_high, sub {
my ($s, $event) = @_;
my $key = $event->GetKeyCode;
if ($event->HasModifiers) {
$event->Skip;
} else {
if ($key == WXK_LEFT) {
$slider_low->SetFocus;
} elsif ($key == WXK_RIGHT) {
} else {
$event->Skip;
}
}
});
EVT_CHECKBOX($self, $checkbox_singlelayer, sub {
$self->single_layer($checkbox_singlelayer->GetValue());
if ($self->single_layer) {
$slider_low->SetValue($slider_high->GetValue);
$self->set_z_idx_high($slider_high->GetValue);
}
});
# EVT_SLIDER($self, $slider_low, sub {
# $slider_high->SetValue($slider_low->GetValue) if $self->single_layer;
# $self->set_z_idx_low ($slider_low ->GetValue)
# });
# EVT_SLIDER($self, $slider_high, sub {
# $slider_low->SetValue($slider_high->GetValue) if $self->single_layer;
# $self->set_z_idx_high($slider_high->GetValue)
# });
# EVT_KEY_DOWN($canvas, sub {
# my ($s, $event) = @_;
# Slic3r::GUI::update_double_slider_from_canvas($event);
# my $key = $event->GetKeyCode;
# if ($event->HasModifiers) {
# $event->Skip;
# } else {
# if ($key == ord('U')) {
# $slider_high->SetValue($slider_high->GetValue + 1);
# $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown());
# $self->set_z_idx_high($slider_high->GetValue);
# } elsif ($key == ord('D')) {
# $slider_high->SetValue($slider_high->GetValue - 1);
# $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown());
# $self->set_z_idx_high($slider_high->GetValue);
# } elsif ($key == ord('S')) {
# $checkbox_singlelayer->SetValue(! $checkbox_singlelayer->GetValue());
# $self->single_layer($checkbox_singlelayer->GetValue());
# if ($self->single_layer) {
# $slider_low->SetValue($slider_high->GetValue);
# $self->set_z_idx_high($slider_high->GetValue);
# }
# } else {
# $event->Skip;
# }
# }
# });
# EVT_KEY_DOWN($slider_low, sub {
# my ($s, $event) = @_;
# my $key = $event->GetKeyCode;
# if ($event->HasModifiers) {
# $event->Skip;
# } else {
# if ($key == WXK_LEFT) {
# } elsif ($key == WXK_RIGHT) {
# $slider_high->SetFocus;
# } else {
# $event->Skip;
# }
# }
# });
# EVT_KEY_DOWN($slider_high, sub {
# my ($s, $event) = @_;
# my $key = $event->GetKeyCode;
# if ($event->HasModifiers) {
# $event->Skip;
# } else {
# if ($key == WXK_LEFT) {
# $slider_low->SetFocus;
# } elsif ($key == WXK_RIGHT) {
# } else {
# $event->Skip;
# }
# }
# });
# EVT_CHECKBOX($self, $checkbox_singlelayer, sub {
# $self->single_layer($checkbox_singlelayer->GetValue());
# if ($self->single_layer) {
# $slider_low->SetValue($slider_high->GetValue);
# $self->set_z_idx_high($slider_high->GetValue);
# }
# });
EVT_CHOICE($self, $choice_view_type, sub {
my $selection = $choice_view_type->GetCurrentSelection();
$self->{preferred_color_mode} = ($selection == $self->{tool_idx}) ? 'tool' : 'feature';
@ -244,6 +251,12 @@ sub new {
$self->refresh_print;
});
EVT_SIZE($self, sub {
my ($s, $event) = @_;
$event->Skip;
$self->Refresh;
});
$self->SetSizer($main_sizer);
$self->SetMinSize($self->GetSize);
$sizer->SetSizeHints($self);
@ -392,62 +405,69 @@ sub load_print {
sub reset_sliders {
my ($self) = @_;
$self->enabled(0);
$self->set_z_range(0,0);
$self->slider_low->Hide;
$self->slider_high->Hide;
$self->{z_label_low}->SetLabel("");
$self->{z_label_high}->SetLabel("");
$self->{z_label_low_idx}->SetLabel("");
$self->{z_label_high_idx}->SetLabel("");
# $self->set_z_range(0,0);
# $self->slider_low->Hide;
# $self->slider_high->Hide;
# $self->{z_label_low}->SetLabel("");
# $self->{z_label_high}->SetLabel("");
# $self->{z_label_low_idx}->SetLabel("");
# $self->{z_label_high_idx}->SetLabel("");
Slic3r::GUI::reset_double_slider();
$self->double_slider_sizer->Hide(0);
}
sub update_sliders
{
my ($self, $n_layers) = @_;
my $z_idx_low = $self->slider_low->GetValue;
my $z_idx_high = $self->slider_high->GetValue;
# my $z_idx_low = $self->slider_low->GetValue;
# my $z_idx_high = $self->slider_high->GetValue;
$self->enabled(1);
$self->slider_low->SetRange(0, $n_layers - 1);
$self->slider_high->SetRange(0, $n_layers - 1);
# $self->slider_low->SetRange(0, $n_layers - 1);
# $self->slider_high->SetRange(0, $n_layers - 1);
if ($self->{force_sliders_full_range}) {
$z_idx_low = 0;
$z_idx_high = $n_layers - 1;
} elsif ($z_idx_high < $n_layers && ($self->single_layer || $z_idx_high != 0)) {
# search new indices for nearest z (size of $self->{layers_z} may change in dependence of what is shown)
if (defined($self->{z_low})) {
for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) {
if ($self->{layers_z}[$i] <= $self->{z_low}) {
$z_idx_low = $i;
last;
}
}
}
if (defined($self->{z_high})) {
for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) {
if ($self->{layers_z}[$i] <= $self->{z_high}) {
$z_idx_high = $i;
last;
}
}
}
} elsif ($z_idx_high >= $n_layers) {
# Out of range. Disable 'single layer' view.
$self->single_layer(0);
$self->{checkbox_singlelayer}->SetValue(0);
$z_idx_low = 0;
$z_idx_high = $n_layers - 1;
} else {
$z_idx_low = 0;
$z_idx_high = $n_layers - 1;
}
# if ($self->{force_sliders_full_range}) {
# $z_idx_low = 0;
# $z_idx_high = $n_layers - 1;
# } elsif ($z_idx_high < $n_layers && ($self->single_layer || $z_idx_high != 0)) {
# # search new indices for nearest z (size of $self->{layers_z} may change in dependence of what is shown)
# if (defined($self->{z_low})) {
# for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) {
# if ($self->{layers_z}[$i] <= $self->{z_low}) {
# $z_idx_low = $i;
# last;
# }
# }
# }
# if (defined($self->{z_high})) {
# for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) {
# if ($self->{layers_z}[$i] <= $self->{z_high}) {
# $z_idx_high = $i;
# last;
# }
# }
# }
# } elsif ($z_idx_high >= $n_layers) {
# # Out of range. Disable 'single layer' view.
# $self->single_layer(0);
# $self->{checkbox_singlelayer}->SetValue(0);
# $z_idx_low = 0;
# $z_idx_high = $n_layers - 1;
# } else {
# $z_idx_low = 0;
# $z_idx_high = $n_layers - 1;
# }
# $self->slider_low->SetValue($z_idx_low);
# $self->slider_high->SetValue($z_idx_high);
# $self->slider_low->Show;
# $self->slider_high->Show;
# $self->set_z_range($self->{layers_z}[$z_idx_low], $self->{layers_z}[$z_idx_high]);
Slic3r::GUI::update_double_slider($self->{force_sliders_full_range});
$self->double_slider_sizer->Show(0);
$self->slider_low->SetValue($z_idx_low);
$self->slider_high->SetValue($z_idx_high);
$self->slider_low->Show;
$self->slider_high->Show;
$self->set_z_range($self->{layers_z}[$z_idx_low], $self->{layers_z}[$z_idx_high]);
$self->Layout;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

View File

@ -179,6 +179,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/SurfaceCollection.hpp
${LIBDIR}/libslic3r/SVG.cpp
${LIBDIR}/libslic3r/SVG.hpp
${LIBDIR}/libslic3r/Technologies.hpp
${LIBDIR}/libslic3r/TriangleMesh.cpp
${LIBDIR}/libslic3r/TriangleMesh.hpp
${LIBDIR}/libslic3r/SLABasePool.hpp
@ -568,7 +569,7 @@ if (WIN32)
endif ()
# SLIC3R_MSVC_PDB
if (MSVC AND SLIC3R_MSVC_PDB AND ${CMAKE_BUILD_TYPE} STREQUAL "Release")
if (MSVC AND SLIC3R_MSVC_PDB AND "${CMAKE_BUILD_TYPE}" STREQUAL "Release")
set_target_properties(XS PROPERTIES
COMPILE_FLAGS "/Zi"
LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF"

View File

@ -640,6 +640,7 @@ public:
// The progress function will be called with the number of placed items
using ProgressFunction = std::function<void(unsigned)>;
using StopCondition = std::function<bool(void)>;
/**
* A wrapper interface (trait) class for any selections strategy provider.
@ -674,6 +675,8 @@ public:
*/
void progressIndicator(ProgressFunction fn) { impl_.progressIndicator(fn); }
void stopCondition(StopCondition cond) { impl_.stopCondition(cond); }
/**
* \brief A method to start the calculation on the input sequence.
*
@ -864,6 +867,11 @@ public:
selector_.progressIndicator(func); return *this;
}
/// Set a predicate to tell when to abort nesting.
inline Nester& stopCondition(StopCondition fn) {
selector_.stopCondition(fn); return *this;
}
inline PackGroup lastResult() {
PackGroup ret;
for(size_t i = 0; i < selector_.binCount(); i++) {

View File

@ -551,7 +551,7 @@ public:
// Safety test: try to pack each item into an empty bin. If it fails
// then it should be removed from the not_packed list
{ auto it = store_.begin();
while (it != store_.end()) {
while (it != store_.end() && !this->stopcond_()) {
Placer p(bin); p.configure(pconfig);
if(!p.pack(*it, rem(it, store_))) {
it = store_.erase(it);
@ -592,9 +592,11 @@ public:
bool do_pairs = config_.try_pairs;
bool do_triplets = config_.try_triplets;
StopCondition stopcond = this->stopcond_;
// The DJD heuristic algorithm itself:
auto packjob = [INITIAL_FILL_AREA, bin_area, w, do_triplets, do_pairs,
stopcond,
&tryOneByOne,
&tryGroupsOfTwo,
&tryGroupsOfThree,
@ -606,12 +608,12 @@ public:
double waste = .0;
bool lasttry = false;
while(!not_packed.empty()) {
while(!not_packed.empty() && !stopcond()) {
{// Fill the bin up to INITIAL_FILL_PROPORTION of its capacity
auto it = not_packed.begin();
while(it != not_packed.end() &&
while(it != not_packed.end() && !stopcond() &&
filled_area < INITIAL_FILL_AREA)
{
if(placer.pack(*it, rem(it, not_packed))) {
@ -623,14 +625,14 @@ public:
}
}
// try pieses one by one
// try pieces one by one
while(tryOneByOne(placer, not_packed, waste, free_area,
filled_area)) {
waste = 0; lasttry = false;
makeProgress(placer, idx, 1);
}
// try groups of 2 pieses
// try groups of 2 pieces
while(do_pairs &&
tryGroupsOfTwo(placer, not_packed, waste, free_area,
filled_area)) {
@ -638,7 +640,7 @@ public:
makeProgress(placer, idx, 2);
}
// try groups of 3 pieses
// try groups of 3 pieces
while(do_triplets &&
tryGroupsOfThree(placer, not_packed, waste, free_area,
filled_area)) {

View File

@ -60,7 +60,7 @@ public:
placer.configure(pconfig);
auto it = store_.begin();
while(it != store_.end()) {
while(it != store_.end() && !this->stopcond_()) {
if(!placer.pack(*it, {std::next(it), store_.end()})) {
if(packed_bins_.back().empty()) ++it;
placer.clearItems();

View File

@ -56,10 +56,12 @@ public:
this->progress_(static_cast<unsigned>(--total));
};
auto& cancelled = this->stopcond_;
// Safety test: try to pack each item into an empty bin. If it fails
// then it should be removed from the list
{ auto it = store_.begin();
while (it != store_.end()) {
while (it != store_.end() && !cancelled()) {
Placer p(bin); p.configure(pconfig);
if(!p.pack(*it)) {
it = store_.erase(it);
@ -67,13 +69,14 @@ public:
}
}
auto it = store_.begin();
while(it != store_.end()) {
while(it != store_.end() && !cancelled()) {
bool was_packed = false;
size_t j = 0;
while(!was_packed) {
for(; j < placers.size() && !was_packed; j++) {
while(!was_packed && !cancelled()) {
for(; j < placers.size() && !was_packed && !cancelled(); j++) {
if((was_packed = placers[j].pack(*it, rem(it, store_) )))
makeProgress(placers[j], j);
}

View File

@ -2,6 +2,7 @@
#define SELECTION_BOILERPLATE_HPP
#include "../libnest2d.hpp"
#include <atomic>
namespace libnest2d { namespace selections {
@ -25,14 +26,15 @@ public:
return packed_bins_[binIndex];
}
inline void progressIndicator(ProgressFunction fn) {
progress_ = fn;
}
inline void progressIndicator(ProgressFunction fn) { progress_ = fn; }
inline void stopCondition(StopCondition cond) { stopcond_ = cond; }
protected:
PackGroup packed_bins_;
ProgressFunction progress_ = [](unsigned){};
StopCondition stopcond_ = [](){ return false; };
};
}

View File

@ -1225,8 +1225,10 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo
return true;
}
Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const
Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) const
{
assert(std::abs(2 * offset) < m_resolution);
typedef std::unordered_multimap<Point, int, PointHash> EndPointMapType;
// 0) Prepare a binary grid.
size_t cell_rows = m_rows + 2;
@ -1237,7 +1239,7 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const
cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1);
// Fill in empty cells, which have a left / right neighbor filled.
// Fill in empty cells, which have the top / bottom neighbor filled.
{
if (fill_holes) {
std::vector<char> cell_inside2(cell_inside);
for (int r = 1; r + 1 < int(cell_rows); ++ r) {
for (int c = 1; c + 1 < int(cell_cols); ++ c) {

View File

@ -58,7 +58,7 @@ public:
const size_t cols() const { return m_cols; }
// For supports: Contours enclosing the rasterized edges.
Polygons contours_simplified(coord_t offset) const;
Polygons contours_simplified(coord_t offset, bool fill_holes) const;
protected:
struct Cell {

View File

@ -61,12 +61,11 @@ ExPolygonCollection::rotate(double angle, const Point &center)
}
template <class T>
bool
ExPolygonCollection::contains(const T &item) const
bool ExPolygonCollection::contains(const T &item) const
{
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
if (it->contains(item)) return true;
}
for (const ExPolygon &poly : this->expolygons)
if (poly.contains(item))
return true;
return false;
}
template bool ExPolygonCollection::contains<Point>(const Point &item) const;

View File

@ -91,6 +91,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
virtual double min_mm3_per_mm() const = 0;
virtual Polyline as_polyline() const = 0;
virtual void collect_polylines(Polylines &dst) const = 0;
virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; }
virtual double length() const = 0;
virtual double total_volume() const = 0;
};
@ -123,8 +125,11 @@ public:
ExtrusionPath* clone() const { return new ExtrusionPath (*this); }
void reverse() { this->polyline.reverse(); }
Point first_point() const { return this->polyline.points.front(); }
Point last_point() const { return this->polyline.points.back(); }
Point first_point() const override { return this->polyline.points.front(); }
Point last_point() const override { return this->polyline.points.back(); }
size_t size() const { return this->polyline.size(); }
bool empty() const { return this->polyline.empty(); }
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
// Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection.
// Currently not used.
void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
@ -133,8 +138,8 @@ public:
void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
void clip_end(double distance);
void simplify(double tolerance);
virtual double length() const;
virtual ExtrusionRole role() const { return m_role; }
double length() const override;
ExtrusionRole role() const override { return m_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
@ -149,7 +154,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const { return this->mm3_per_mm; }
Polyline as_polyline() const { return this->polyline; }
virtual double total_volume() const { return mm3_per_mm * unscale<double>(length()); }
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
private:
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
@ -178,10 +184,10 @@ public:
bool can_reverse() const { return true; }
ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); }
void reverse();
Point first_point() const { return this->paths.front().polyline.points.front(); }
Point last_point() const { return this->paths.back().polyline.points.back(); }
virtual double length() const;
virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); }
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { return this->paths.back().polyline.points.back(); }
double length() const override;
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
@ -196,7 +202,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const;
Polyline as_polyline() const;
virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
};
// Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
@ -218,17 +225,17 @@ public:
bool make_clockwise();
bool make_counter_clockwise();
void reverse();
Point first_point() const { return this->paths.front().polyline.points.front(); }
Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
Polygon polygon() const;
virtual double length() const;
double length() const override;
bool split_at_vertex(const Point &point);
void split_at(const Point &point, bool prefer_non_overhang);
void clip_end(double distance, ExtrusionPaths* paths) const;
// Test, whether the point is extruded by a bridging flow.
// This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead.
bool has_overhang_point(const Point &point) const;
virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); }
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
ExtrusionLoopRole loop_role() const { return m_loop_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
@ -244,7 +251,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const;
Polyline as_polyline() const { return this->polygon().split_at_first_point(); }
virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
private:
ExtrusionLoopRole m_loop_role;

View File

@ -24,7 +24,7 @@ public:
explicit operator ExtrusionPaths() const;
bool is_collection() const { return true; };
virtual ExtrusionRole role() const {
ExtrusionRole role() const override {
ExtrusionRole out = erNone;
for (const ExtrusionEntity *ee : entities) {
ExtrusionRole er = ee->role();
@ -71,11 +71,11 @@ public:
Point last_point() const { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const;
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
@ -84,14 +84,20 @@ public:
void flatten(ExtrusionEntityCollection* retval) const;
ExtrusionEntityCollection flatten() const;
double min_mm3_per_mm() const;
virtual double total_volume() const {double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
// Following methods shall never be called on an ExtrusionEntityCollection.
Polyline as_polyline() const {
CONFESS("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline();
};
virtual double length() const {
void collect_polylines(Polylines &dst) const override {
for (ExtrusionEntity* extrusion_entity : this->entities)
extrusion_entity->collect_polylines(dst);
}
double length() const override {
CONFESS("Calling length() on a ExtrusionEntityCollection");
return 0.;
}

View File

@ -86,8 +86,8 @@ void FillHoneycomb::_fill_surface_single(
Polylines paths;
{
Polylines p;
for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it)
p.push_back((Polyline)(*it));
for (Polygon &poly : polygons)
p.emplace_back(poly.points);
paths = intersection_pl(p, to_polygons(expolygon));
}

View File

@ -115,7 +115,8 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
// if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
false);
// bridge_flow_ratio
0.f);
}
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
@ -127,7 +128,8 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig
(width.value > 0) ? width : object->config().extrusion_width,
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)),
false);
// bridge_flow_ratio
0.f);
}
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
@ -139,7 +141,8 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig
// if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
false);
// bridge_flow_ratio
0.f);
}
}

View File

@ -71,6 +71,7 @@ const char* VOLUME_TYPE = "volume";
const char* NAME_KEY = "name";
const char* MODIFIER_KEY = "modifier";
const char* VOLUME_TYPE_KEY = "volume_type";
const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
const char* VALID_OBJECT_TYPES[] =
@ -1252,9 +1253,13 @@ namespace Slic3r {
// we extract from the given matrix only the values currently used
// translation
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec3d offset(transform(0, 3), transform(1, 3), transform(2, 3));
#else
double offset_x = transform(0, 3);
double offset_y = transform(1, 3);
double offset_z = transform(2, 3);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// scale
double sx = ::sqrt(sqr(transform(0, 0)) + sqr(transform(1, 0)) + sqr(transform(2, 0)));
@ -1287,8 +1292,12 @@ namespace Slic3r {
double angle_z = (rotation.axis() == Vec3d::UnitZ()) ? rotation.angle() : -rotation.angle();
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance.set_offset(offset);
#else
instance.offset(0) = offset_x;
instance.offset(1) = offset_y;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
instance.scaling_factor = sx;
instance.rotation = angle_z;
}
@ -1434,7 +1443,9 @@ namespace Slic3r {
if (metadata.key == NAME_KEY)
volume->name = metadata.value;
else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
volume->modifier = true;
volume->set_type(ModelVolume::PARAMETER_MODIFIER);
else if (metadata.key == VOLUME_TYPE_KEY)
volume->set_type(ModelVolume::type_from_string(metadata.value));
else
volume->config.set_deserialize(metadata.key, metadata.value);
}
@ -1949,9 +1960,12 @@ namespace Slic3r {
if (!volume->name.empty())
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
// stores volume's modifier field
if (volume->modifier)
// stores volume's modifier field (legacy, to support old slicers)
if (volume->is_modifier())
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
// stores volume's type (overrides the modifier field above)
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";
// stores volume's config data
for (const std::string& key : volume->config.keys())

View File

@ -29,7 +29,12 @@
// VERSION NUMBERS
// 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them.
// 1 : Introduction of amf versioning. No other change in data saved into amf files.
#if ENABLE_MODELINSTANCE_3D_OFFSET
// 2 : Added z component of offset.
const unsigned int VERSION_AMF = 2;
#else
const unsigned int VERSION_AMF = 1;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version";
const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
@ -119,19 +124,31 @@ struct AMFParserContext
NODE_TYPE_INSTANCE, // amf/constellation/instance
NODE_TYPE_DELTAX, // amf/constellation/instance/deltax
NODE_TYPE_DELTAY, // amf/constellation/instance/deltay
#if ENABLE_MODELINSTANCE_3D_OFFSET
NODE_TYPE_DELTAZ, // amf/constellation/instance/deltaz
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
NODE_TYPE_RZ, // amf/constellation/instance/rz
NODE_TYPE_SCALE, // amf/constellation/instance/scale
NODE_TYPE_METADATA, // anywhere under amf/*/metadata
};
struct Instance {
#if ENABLE_MODELINSTANCE_3D_OFFSET
Instance() : deltax_set(false), deltay_set(false), deltaz_set(false), rz_set(false), scale_set(false) {}
#else
Instance() : deltax_set(false), deltay_set(false), rz_set(false), scale_set(false) {}
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// Shift in the X axis.
float deltax;
bool deltax_set;
// Shift in the Y axis.
float deltay;
bool deltay_set;
#if ENABLE_MODELINSTANCE_3D_OFFSET
// Shift in the Z axis.
float deltaz;
bool deltaz_set;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// Rotation around the Z axis.
float rz;
bool rz_set;
@ -254,6 +271,10 @@ void AMFParserContext::startElement(const char *name, const char **atts)
node_type_new = NODE_TYPE_DELTAX;
else if (strcmp(name, "deltay") == 0)
node_type_new = NODE_TYPE_DELTAY;
#if ENABLE_MODELINSTANCE_3D_OFFSET
else if (strcmp(name, "deltaz") == 0)
node_type_new = NODE_TYPE_DELTAZ;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
else if (strcmp(name, "rz") == 0)
node_type_new = NODE_TYPE_RZ;
else if (strcmp(name, "scale") == 0)
@ -314,7 +335,15 @@ void AMFParserContext::characters(const XML_Char *s, int len)
{
switch (m_path.size()) {
case 4:
#if ENABLE_MODELINSTANCE_3D_OFFSET
if (m_path.back() == NODE_TYPE_DELTAX ||
m_path.back() == NODE_TYPE_DELTAY ||
m_path.back() == NODE_TYPE_DELTAZ ||
m_path.back() == NODE_TYPE_RZ ||
m_path.back() == NODE_TYPE_SCALE)
#else
if (m_path.back() == NODE_TYPE_DELTAX || m_path.back() == NODE_TYPE_DELTAY || m_path.back() == NODE_TYPE_RZ || m_path.back() == NODE_TYPE_SCALE)
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
m_value[0].append(s, len);
break;
case 6:
@ -354,6 +383,14 @@ void AMFParserContext::endElement(const char * /* name */)
m_instance->deltay_set = true;
m_value[0].clear();
break;
#if ENABLE_MODELINSTANCE_3D_OFFSET
case NODE_TYPE_DELTAZ:
assert(m_instance);
m_instance->deltaz = float(atof(m_value[0].c_str()));
m_instance->deltaz_set = true;
m_value[0].clear();
break;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
case NODE_TYPE_RZ:
assert(m_instance);
m_instance->rz = float(atof(m_value[0].c_str()));
@ -458,9 +495,14 @@ void AMFParserContext::endElement(const char * /* name */)
p = end + 1;
}
m_object->layer_height_profile_valid = true;
} else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume && strcmp(opt_key, "modifier") == 0) {
} else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) {
if (strcmp(opt_key, "modifier") == 0) {
// Is this volume a modifier volume?
m_volume->modifier = atoi(m_value[1].c_str()) == 1;
// "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
} else if (strcmp(opt_key, "volume_type") == 0) {
m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
}
}
} else if (m_path.size() == 3) {
if (m_path[1] == NODE_TYPE_MATERIAL) {
@ -498,8 +540,12 @@ void AMFParserContext::endDocument()
for (const Instance &instance : object.second.instances)
if (instance.deltax_set && instance.deltay_set) {
ModelInstance *mi = m_model.objects[object.second.idx]->add_instance();
#if ENABLE_MODELINSTANCE_3D_OFFSET
mi->set_offset(Vec3d((double)instance.deltax, (double)instance.deltay, (double)instance.deltaz));
#else
mi->offset(0) = instance.deltax;
mi->offset(1) = instance.deltay;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
mi->rotation = instance.rz_set ? instance.rz : 0.f;
mi->scaling_factor = instance.scale_set ? instance.scale : 1.f;
}
@ -781,8 +827,9 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n";
if (!volume->name.empty())
stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
if (volume->modifier)
if (volume->is_modifier())
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
stream << " <triangle>\n";
for (int j = 0; j < 3; ++j)
@ -800,12 +847,21 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
" <instance objectid=\"" PRINTF_ZU "\">\n"
" <deltax>%lf</deltax>\n"
" <deltay>%lf</deltay>\n"
#if ENABLE_MODELINSTANCE_3D_OFFSET
" <deltaz>%lf</deltaz>\n"
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
" <rz>%lf</rz>\n"
" <scale>%lf</scale>\n"
" </instance>\n",
object_id,
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance->get_offset(X),
instance->get_offset(Y),
instance->get_offset(Z),
#else
instance->offset(0),
instance->offset(1),
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
instance->rotation,
instance->scaling_factor);
//FIXME missing instance->scaling_factor

View File

@ -166,7 +166,11 @@ bool load_prus(const char *path, Model *model)
float trafo[3][4] = { 0 };
double instance_rotation = 0.;
double instance_scaling_factor = 1.f;
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec3d instance_offset = Vec3d::Zero();
#else
Vec2d instance_offset(0., 0.);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
bool trafo_set = false;
unsigned int group_id = (unsigned int)-1;
unsigned int extruder_id = (unsigned int)-1;
@ -207,8 +211,12 @@ bool load_prus(const char *path, Model *model)
for (size_t c = 0; c < 3; ++ c)
trafo[r][c] += mat_trafo(r, c);
}
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2]));
#else
instance_offset(0) = position[0] - zero[0];
instance_offset(1) = position[1] - zero[1];
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
trafo[2][3] = position[2] / instance_scaling_factor;
trafo_set = true;
}
@ -360,8 +368,12 @@ bool load_prus(const char *path, Model *model)
ModelInstance *instance = model_object->add_instance();
instance->rotation = instance_rotation;
instance->scaling_factor = instance_scaling_factor;
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance->set_offset(instance_offset);
#else
instance->offset = instance_offset;
++ num_models;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
++num_models;
if (group_id != (size_t)-1)
group_to_model_object[group_id] = model_object;
} else {

View File

@ -277,7 +277,6 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco
}
std::string WipeTowerIntegration::prime(GCode &gcodegen)
{
assert(m_layer_idx == 0);
@ -1000,8 +999,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms();
print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
for (const Extruder &extruder : m_writer.extruders()) {
double used_filament = extruder.used_filament();
double extruded_volume = extruder.extruded_volume();
double used_filament = extruder.used_filament() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] : 0.f);
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
print.m_print_statistics.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament));
@ -1014,8 +1013,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
_write_format(file, "; filament cost = %.1lf\n", filament_cost);
}
}
print.m_print_statistics.total_used_filament = print.m_print_statistics.total_used_filament + used_filament;
print.m_print_statistics.total_extruded_volume = print.m_print_statistics.total_extruded_volume + extruded_volume;
print.m_print_statistics.total_used_filament += used_filament;
print.m_print_statistics.total_extruded_volume += extruded_volume;
print.m_print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
print.m_print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
}
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
_write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());

View File

@ -98,6 +98,7 @@ public:
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen);
std::vector<float> used_filament_length() const;
private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&);

View File

@ -154,6 +154,12 @@ public:
// the wipe tower has been completely covered by the tool change extrusions,
// or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
virtual bool layer_finished() const = 0;
// Returns used filament length per extruder:
virtual std::vector<float> get_used_filament() const = 0;
// Returns total number of toolchanges:
virtual int get_number_of_toolchanges() const = 0;
};
}; // namespace Slic3r

View File

@ -111,9 +111,10 @@ public:
const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); }
float elapsed_time() const { return m_elapsed_time; }
float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
// Extrude with an explicitely provided amount of extrusion.
Writer& extrude_explicit(float x, float y, float e, float f = 0.f)
Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false)
{
if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate))
// Neither extrusion nor a travel move.
@ -122,6 +123,8 @@ public:
float dx = x - m_current_pos.x;
float dy = y - m_current_pos.y;
double len = sqrt(dx*dx+dy*dy);
if (record_length)
m_used_filament_length += e;
// Now do the "internal rotation" with respect to the wipe tower center
@ -162,8 +165,8 @@ public:
return *this;
}
Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f)
{ return extrude_explicit(dest.x, dest.y, e, f); }
Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false)
{ return extrude_explicit(dest.x, dest.y, e, f, record_length); }
// Travel to a new XY position. f=0 means use the current value.
Writer& travel(float x, float y, float f = 0.f)
@ -177,7 +180,7 @@ public:
{
float dx = x - m_current_pos.x;
float dy = y - m_current_pos.y;
return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f);
return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true);
}
Writer& extrude(const WipeTower::xy &dest, const float f = 0.f)
@ -259,8 +262,8 @@ public:
// extrude quickly amount e to x2 with feed f.
Writer& ram(float x1, float x2, float dy, float e0, float e, float f)
{
extrude_explicit(x1, m_current_pos.y + dy, e0, f);
extrude_explicit(x2, m_current_pos.y, e);
extrude_explicit(x1, m_current_pos.y + dy, e0, f, true);
extrude_explicit(x2, m_current_pos.y, e, 0.f, true);
return *this;
}
@ -404,6 +407,7 @@ private:
float m_last_fan_speed = 0.f;
int current_temp = -1;
const float m_default_analyzer_line_width;
float m_used_filament_length = 0.f;
std::string set_format_X(float x)
{
@ -525,6 +529,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
++ m_num_tool_changes;
}
m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear
// in the output gcode - we should not remember emitting them (we will output them twice in the worst case)
// Reset the extruder current to a normal value.
writer.set_extruder_trimpot(550)
.feedrate(6000)
@ -537,6 +544,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
// so that tool_change() will know to extrude the wipe tower brim:
m_print_brim = true;
// Ask our writer about how much material was consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
ToolChangeResult result;
result.priming = true;
result.print_z = this->m_z_pos;
@ -606,10 +616,10 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
toolchange_Load(writer, cleaning_box);
writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
++ m_num_tool_changes;
} else
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
++ m_num_tool_changes;
m_depth_traversed += wipe_area;
if (last_change_in_layer) {// draw perimeter line
@ -632,6 +642,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
";------------------\n"
"\n\n");
// Ask our writer about how much material was consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
@ -683,6 +696,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
m_print_brim = false; // Mark the brim as extruded
// Ask our writer about how much material was consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
@ -804,8 +820,9 @@ void WipeTowerPrusaMM::toolchange_Unload(
.load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed)
.travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/
.resume_preview();
if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait.
if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait.
// If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset)
// However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off).
writer.set_extruder_temp(new_temperature, false);
m_old_temperature = new_temperature;
}
@ -849,6 +866,9 @@ void WipeTowerPrusaMM::toolchange_Change(
const unsigned int new_tool,
material_type new_material)
{
// Ask the writer about how much of the old filament we consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
// Speed override for the material. Go slow for flex and soluble materials.
int speed_override;
switch (new_material) {
@ -911,7 +931,6 @@ void WipeTowerPrusaMM::toolchange_Wipe(
const float& xl = cleaning_box.ld.x;
const float& xr = cleaning_box.rd.x;
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
// the ordered volume, even if it means violating the box. This can later be removed and simply
// wipe until the end of the assigned area.
@ -926,7 +945,6 @@ void WipeTowerPrusaMM::toolchange_Wipe(
m_left_to_right = !m_left_to_right;
}
// now the wiping itself:
for (int i = 0; true; ++i) {
if (i!=0) {
@ -1050,6 +1068,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
// Ask our writer about how much material was consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
@ -1166,6 +1187,8 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
m_layer_info = m_plan.begin();
m_current_tool = (unsigned int)(-2); // we don't know which extruder to start with - we'll set it according to the first toolchange
for (auto& used : m_used_filament_length) // reset used filament stats
used = 0.f;
std::vector<WipeTower::ToolChangeResult> layer_result;
for (auto layer : m_plan)

View File

@ -94,6 +94,8 @@ public:
m_filpar[idx].ramming_step_multiplicator /= 100;
while (stream >> speed)
m_filpar[idx].ramming_speed.push_back(speed);
m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
}
@ -172,6 +174,9 @@ public:
return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed);
}
virtual std::vector<float> get_used_filament() const override { return m_used_filament_length; }
virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; }
private:
WipeTowerPrusaMM();
@ -331,6 +336,9 @@ private:
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
// Stores information about used filament length per extruder:
std::vector<float> m_used_filament_length;
// Returns gcode for wipe tower brim
// sideOnly -- set to false -- experimental, draw brim on sides of wipe tower

View File

@ -15,8 +15,7 @@
namespace Slic3r {
Flow
LayerRegion::flow(FlowRole role, bool bridge, double width) const
Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
{
return m_region->flow(
role,

View File

@ -242,10 +242,19 @@ void Model::center_instances_around_point(const Vec2d &point)
for (size_t i = 0; i < o->instances.size(); ++ i)
bb.merge(o->instance_bounding_box(i, false));
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec2d shift2 = point - to_2d(bb.center());
Vec3d shift3 = Vec3d(shift2(0), shift2(1), 0.0);
#else
Vec2d shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
for (ModelObject *o : this->objects) {
for (ModelInstance *i : o->instances)
#if ENABLE_MODELINSTANCE_3D_OFFSET
i->set_offset(i->get_offset() + shift3);
#else
i->offset += shift;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
o->invalidate_bounding_box();
}
}
@ -311,8 +320,13 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
size_t idx = 0;
for (ModelObject *o : this->objects) {
for (ModelInstance *i : o->instances) {
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec2d offset_xy = positions[idx] - instance_centers[idx];
i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z)));
#else
i->offset = positions[idx] - instance_centers[idx];
++ idx;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
++idx;
}
o->invalidate_bounding_box();
}
@ -336,7 +350,11 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb)
for (const ModelInstance *i : instances) {
for (const Vec2d &pos : positions) {
ModelInstance *instance = o->add_instance(*i);
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance->set_offset(instance->get_offset() + Vec3d(pos(0), pos(1), 0.0));
#else
instance->offset += pos;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
}
o->invalidate_bounding_box();
@ -366,13 +384,21 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
ModelObject* object = this->objects.front();
object->clear_instances();
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec3d ext_size = object->bounding_box().size() + dist * Vec3d::Ones();
#else
Vec3d size = object->bounding_box().size();
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
for (size_t x_copy = 1; x_copy <= x; ++x_copy) {
for (size_t y_copy = 1; y_copy <= y; ++y_copy) {
ModelInstance* instance = object->add_instance();
instance->offset(0) = (size(0) + dist) * (x_copy-1);
instance->offset(1) = (size(1) + dist) * (y_copy-1);
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance->set_offset(Vec3d(ext_size(0) * (double)(x_copy - 1), ext_size(1) * (double)(y_copy - 1), 0.0));
#else
instance->offset(0) = (size(0) + dist) * (x_copy - 1);
instance->offset(1) = (size(1) + dist) * (y_copy - 1);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
}
}
@ -601,7 +627,8 @@ const BoundingBoxf3& ModelObject::bounding_box() const
if (! m_bounding_box_valid) {
BoundingBoxf3 raw_bbox;
for (const ModelVolume *v : this->volumes)
if (! v->modifier)
if (v->is_model_part())
// mesh.bounding_box() returns a cached value.
raw_bbox.merge(v->mesh.bounding_box());
BoundingBoxf3 bb;
for (const ModelInstance *i : this->instances)
@ -632,7 +659,7 @@ TriangleMesh ModelObject::raw_mesh() const
{
TriangleMesh mesh;
for (const ModelVolume *v : this->volumes)
if (! v->modifier)
if (v->is_model_part())
mesh.merge(v->mesh);
return mesh;
}
@ -643,7 +670,7 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const
{
BoundingBoxf3 bb;
for (const ModelVolume *v : this->volumes)
if (! v->modifier) {
if (v->is_model_part()) {
if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances");
bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true));
}
@ -655,7 +682,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_
{
BoundingBoxf3 bb;
for (ModelVolume *v : this->volumes)
if (! v->modifier)
if (v->is_model_part())
bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate));
return bb;
}
@ -666,7 +693,7 @@ void ModelObject::center_around_origin()
// center this object around the origin
BoundingBoxf3 bb;
for (ModelVolume *v : this->volumes)
if (! v->modifier)
if (v->is_model_part())
bb.merge(v->mesh.bounding_box());
// Shift is the vector from the center of the bottom face of the bounding box to the origin
@ -676,12 +703,21 @@ void ModelObject::center_around_origin()
this->translate(shift);
this->origin_translation += shift;
#if ENABLE_MODELINSTANCE_3D_OFFSET
// set z to zero, translation in z has already been done within the mesh
shift(2) = 0.0;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
if (!this->instances.empty()) {
for (ModelInstance *i : this->instances) {
// apply rotation and scaling to vector as well before translating instance,
// in order to leave final position unaltered
#if ENABLE_MODELINSTANCE_3D_OFFSET
i->set_offset(i->get_offset() + i->transform_vector(-shift, true));
#else
Vec3d i_shift = i->world_matrix(true) * shift;
i->offset -= to_2d(i_shift);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
this->invalidate_bounding_box();
}
@ -763,7 +799,7 @@ size_t ModelObject::facets_count() const
{
size_t num = 0;
for (const ModelVolume *v : this->volumes)
if (! v->modifier)
if (v->is_model_part())
num += v->mesh.stl.stats.number_of_facets;
return num;
}
@ -771,7 +807,7 @@ size_t ModelObject::facets_count() const
bool ModelObject::needed_repair() const
{
for (const ModelVolume *v : this->volumes)
if (! v->modifier && v->mesh.needed_repair())
if (v->is_model_part() && v->mesh.needed_repair())
return true;
return false;
}
@ -787,7 +823,7 @@ void ModelObject::cut(coordf_t z, Model* model) const
lower->input_file = "";
for (ModelVolume *volume : this->volumes) {
if (volume->modifier) {
if (! volume->is_model_part()) {
// don't cut modifiers
upper->add_volume(*volume);
lower->add_volume(*volume);
@ -839,7 +875,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
ModelVolume* new_volume = new_object->add_volume(*mesh);
new_volume->name = volume->name;
new_volume->config = volume->config;
new_volume->modifier = volume->modifier;
new_volume->set_type(volume->type());
new_volume->material_id(volume->material_id());
new_objects->push_back(new_object);
@ -854,7 +890,7 @@ void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_
{
for (const ModelVolume* vol : this->volumes)
{
if (!vol->modifier)
if (vol->is_model_part())
{
for (ModelInstance* inst : this->instances)
{
@ -951,6 +987,38 @@ const TriangleMesh& ModelVolume::get_convex_hull() const
return m_convex_hull;
}
ModelVolume::Type ModelVolume::type_from_string(const std::string &s)
{
// Legacy support
if (s == "1")
return PARAMETER_MODIFIER;
// New type (supporting the support enforcers & blockers)
if (s == "ModelPart")
return MODEL_PART;
if (s == "ParameterModifier")
return PARAMETER_MODIFIER;
if (s == "SupportEnforcer")
return SUPPORT_ENFORCER;
if (s == "SupportBlocker")
return SUPPORT_BLOCKER;
assert(s == "0");
// Default value if invalud type string received.
return MODEL_PART;
}
std::string ModelVolume::type_to_string(const Type t)
{
switch (t) {
case MODEL_PART: return "ModelPart";
case PARAMETER_MODIFIER: return "ParameterModifier";
case SUPPORT_ENFORCER: return "SupportEnforcer";
case SUPPORT_BLOCKER: return "SupportBlocker";
default:
assert(false);
return "ModelPart";
}
}
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.
@ -1005,8 +1073,13 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes
}
// Translate the bounding box.
if (! dont_translate) {
#if ENABLE_MODELINSTANCE_3D_OFFSET
bbox.min += this->m_offset;
bbox.max += this->m_offset;
#else
Eigen::Map<Vec2d>(bbox.min.data()) += this->offset;
Eigen::Map<Vec2d>(bbox.max.data()) += this->offset;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
}
return bbox;
@ -1033,7 +1106,11 @@ Transform3d ModelInstance::world_matrix(bool dont_translate, bool dont_rotate, b
Transform3d m = Transform3d::Identity();
if (!dont_translate)
#if ENABLE_MODELINSTANCE_3D_OFFSET
m.translate(m_offset);
#else
m.translate(Vec3d(offset(0), offset(1), 0.0));
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
if (!dont_rotate)
m.rotate(Eigen::AngleAxisd(rotation, Vec3d::UnitZ()));

View File

@ -166,11 +166,23 @@ public:
// Configuration parameters specific to an object model geometry or a modifier volume,
// overriding the global Slic3r settings and the ModelObject settings.
DynamicPrintConfig config;
// Is it an object to be printed, or a modifier volume?
bool modifier;
enum Type {
MODEL_TYPE_INVALID = -1,
MODEL_PART = 0,
PARAMETER_MODIFIER,
SUPPORT_ENFORCER,
SUPPORT_BLOCKER,
};
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; };
Type type() const { return m_type; }
void set_type(const Type t) { m_type = t; }
bool is_model_part() const { return m_type == MODEL_PART; }
bool is_modifier() const { return m_type == PARAMETER_MODIFIER; }
bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; }
bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; }
t_model_material_id material_id() const { return this->_material_id; }
void material_id(t_model_material_id material_id);
ModelMaterial* material() const;
@ -185,24 +197,30 @@ public:
void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const;
// Helpers for loading / storing into AMF / 3MF files.
static Type type_from_string(const std::string &s);
static std::string type_to_string(const Type t);
private:
// Parent object owning this ModelVolume.
ModelObject* object;
// Is it an object to be printed, or a modifier volume?
Type m_type;
t_model_material_id _material_id;
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object)
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object)
{
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {}
ModelVolume(ModelObject *object, const ModelVolume &other) :
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object)
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object)
{
this->material_id(other.material_id());
}
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object)
name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object)
{
this->material_id(other.material_id());
if (mesh.stl.stats.number_of_facets > 1)
@ -225,15 +243,32 @@ public:
friend class ModelObject;
#if ENABLE_MODELINSTANCE_3D_OFFSET
private:
Vec3d m_offset; // in unscaled coordinates
public:
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
double rotation; // Rotation around the Z axis, in radians around mesh center point
double scaling_factor;
#if !ENABLE_MODELINSTANCE_3D_OFFSET
Vec2d offset; // in unscaled coordinates
#endif // !ENABLE_MODELINSTANCE_3D_OFFSET
// flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state())
EPrintVolumeState print_volume_state;
ModelObject* get_object() const { return this->object; }
#if ENABLE_MODELINSTANCE_3D_OFFSET
const Vec3d& get_offset() const { return m_offset; }
double get_offset(Axis axis) const { return m_offset(axis); }
void set_offset(const Vec3d& offset) { m_offset = offset; }
void set_offset(Axis axis, double offset) { m_offset(axis) = offset; }
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// To be called on an external mesh
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
// Calculate a bounding box of a transformed mesh. To be called on an external mesh.
@ -253,9 +288,15 @@ private:
// Parent object, owning this instance.
ModelObject* object;
#if ENABLE_MODELINSTANCE_3D_OFFSET
ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), m_offset(Vec3d::Zero()), object(object), print_volume_state(PVS_Inside) {}
ModelInstance(ModelObject *object, const ModelInstance &other) :
rotation(other.rotation), scaling_factor(other.scaling_factor), m_offset(other.m_offset), object(object), print_volume_state(PVS_Inside) {}
#else
ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), offset(Vec2d::Zero()), object(object), print_volume_state(PVS_Inside) {}
ModelInstance(ModelObject *object, const ModelInstance &other) :
rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object), print_volume_state(PVS_Inside) {}
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
};

View File

@ -130,6 +130,7 @@ objfunc(const PointImpl& bincenter,
double norm, // A norming factor for physical dimensions
// a spatial index to quickly get neighbors of the candidate item
const SpatIndex& spatindex,
const SpatIndex& smalls_spatindex,
const ItemGroup& remaining
)
{
@ -161,7 +162,7 @@ objfunc(const PointImpl& bincenter,
// Will hold the resulting score
double score = 0;
if(isBig(item.area())) {
if(isBig(item.area()) || spatindex.empty()) {
// This branch is for the bigger items..
auto minc = ibb.minCorner(); // bottom left corner
@ -183,6 +184,8 @@ objfunc(const PointImpl& bincenter,
// The smalles distance from the arranged pile center:
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
dist = 0.8*dist + 0.2*bindist;
// Density is the pack density: how big is the arranged pile
double density = 0;
@ -207,14 +210,20 @@ objfunc(const PointImpl& bincenter,
// candidate to be aligned with only one item.
auto alignment_score = 1.0;
density = (fullbb.width()*fullbb.height()) / (norm*norm);
density = std::sqrt((fullbb.width() / norm )*
(fullbb.height() / norm));
auto querybb = item.boundingBox();
// Query the spatial index for the neighbors
std::vector<SpatElement> result;
result.reserve(spatindex.size());
if(isBig(item.area())) {
spatindex.query(bgi::intersects(querybb),
std::back_inserter(result));
} else {
smalls_spatindex.query(bgi::intersects(querybb),
std::back_inserter(result));
}
for(auto& e : result) { // now get the score for the best alignment
auto idx = e.second;
@ -235,12 +244,8 @@ objfunc(const PointImpl& bincenter,
if(result.empty())
score = 0.5 * dist + 0.5 * density;
else
score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score;
score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score;
}
} else if( !isBig(item.area()) && spatindex.empty()) {
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
// Bindist is surprisingly enough...
score = bindist;
} else {
// Here there are the small items that should be placed around the
// already processed bigger items.
@ -291,6 +296,7 @@ protected:
PConfig pconf_; // Placement configuration
double bin_area_;
SpatIndex rtree_;
SpatIndex smallsrtree_;
double norm_;
Pile merged_pile_;
Box pilebb_;
@ -299,7 +305,8 @@ protected:
public:
_ArrBase(const TBin& bin, Distance dist,
std::function<void(unsigned)> progressind):
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
pck_(bin, dist), bin_area_(sl::area(bin)),
norm_(std::sqrt(sl::area(bin)))
{
@ -317,6 +324,7 @@ public:
pilebb_ = sl::boundingBox(merged_pile);
rtree_.clear();
smallsrtree_.clear();
// We will treat big items (compared to the print bed) differently
auto isBig = [this](double a) {
@ -326,10 +334,12 @@ public:
for(unsigned idx = 0; idx < items.size(); ++idx) {
Item& itm = items[idx];
if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx});
smallsrtree_.insert({itm.boundingBox(), idx});
}
};
pck_.progressIndicator(progressind);
pck_.stopCondition(stopcond);
}
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
@ -343,8 +353,9 @@ class AutoArranger<Box>: public _ArrBase<Box> {
public:
AutoArranger(const Box& bin, Distance dist,
std::function<void(unsigned)> progressind):
_ArrBase<Box>(bin, dist, progressind)
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
_ArrBase<Box>(bin, dist, progressind, stopcond)
{
pconf_.object_function = [this, bin] (const Item &item) {
@ -357,6 +368,7 @@ public:
bin_area_,
norm_,
rtree_,
smallsrtree_,
remaining_);
double score = std::get<0>(result);
@ -380,8 +392,9 @@ class AutoArranger<lnCircle>: public _ArrBase<lnCircle> {
public:
AutoArranger(const lnCircle& bin, Distance dist,
std::function<void(unsigned)> progressind):
_ArrBase<lnCircle>(bin, dist, progressind) {
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
_ArrBase<lnCircle>(bin, dist, progressind, stopcond) {
pconf_.object_function = [this, &bin] (const Item &item) {
@ -393,6 +406,7 @@ public:
bin_area_,
norm_,
rtree_,
smallsrtree_,
remaining_);
double score = std::get<0>(result);
@ -421,8 +435,9 @@ template<>
class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> {
public:
AutoArranger(const PolygonImpl& bin, Distance dist,
std::function<void(unsigned)> progressind):
_ArrBase<PolygonImpl>(bin, dist, progressind)
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
_ArrBase<PolygonImpl>(bin, dist, progressind, stopcond)
{
pconf_.object_function = [this, &bin] (const Item &item) {
@ -435,6 +450,7 @@ public:
bin_area_,
norm_,
rtree_,
smallsrtree_,
remaining_);
double score = std::get<0>(result);
@ -449,8 +465,9 @@ template<> // Specialization with no bin
class AutoArranger<bool>: public _ArrBase<Box> {
public:
AutoArranger(Distance dist, std::function<void(unsigned)> progressind):
_ArrBase<Box>(Box(0, 0), dist, progressind)
AutoArranger(Distance dist, std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
_ArrBase<Box>(Box(0, 0), dist, progressind, stopcond)
{
this->pconf_.object_function = [this] (const Item &item) {
@ -462,6 +479,7 @@ public:
0,
norm_,
rtree_,
smallsrtree_,
remaining_);
return std::get<0>(result);
};
@ -511,8 +529,13 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
if(item.vertexCount() > 3) {
item.rotation(objinst->rotation);
item.translation( {
#if ENABLE_MODELINSTANCE_3D_OFFSET
ClipperLib::cInt(objinst->get_offset(X) / SCALING_FACTOR),
ClipperLib::cInt(objinst->get_offset(Y) / SCALING_FACTOR)
#else
ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR),
ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR)
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
});
ret.emplace_back(objinst, item);
}
@ -649,11 +672,19 @@ void applyResult(
// appropriately
auto off = item.translation();
Radians rot = item.rotation();
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec3d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR, 0.0);
#else
Vec2d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// write the tranformation data into the model instance
inst_ptr->rotation = rot;
#if ENABLE_MODELINSTANCE_3D_OFFSET
inst_ptr->set_offset(foff);
#else
inst_ptr->offset = foff;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
}
@ -680,12 +711,16 @@ void applyResult(
* remaining items which do not fit onto the print area next to the print
* bed or leave them untouched (let the user arrange them by hand or remove
* them).
* \param progressind Progress indicator callback called when an object gets
* packed. The unsigned argument is the number of items remaining to pack.
* \param stopcondition A predicate returning true if abort is needed.
*/
bool arrange(Model &model, coordf_t min_obj_distance,
const Slic3r::Polyline& bed,
BedShapeHint bedhint,
bool first_bin_only,
std::function<void(unsigned)> progressind)
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcondition)
{
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
@ -710,6 +745,8 @@ bool arrange(Model &model, coordf_t min_obj_distance,
BoundingBox bbb(bed);
auto& cfn = stopcondition;
auto binbb = Box({
static_cast<libnest2d::Coord>(bbb.min(0)),
static_cast<libnest2d::Coord>(bbb.min(1))
@ -723,7 +760,7 @@ bool arrange(Model &model, coordf_t min_obj_distance,
case BedShapeType::BOX: {
// Create the arranger for the box shaped bed
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind);
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind, cfn);
// Arrange and return the items with their respective indices within the
// input sequence.
@ -735,7 +772,7 @@ bool arrange(Model &model, coordf_t min_obj_distance,
auto c = bedhint.shape.circ;
auto cc = lnCircle(c);
AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind);
AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind, cfn);
result = arrange(shapes.begin(), shapes.end());
break;
}
@ -747,7 +784,7 @@ bool arrange(Model &model, coordf_t min_obj_distance,
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
P irrbed = sl::create<PolygonImpl>(std::move(ctour));
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind);
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind, cfn);
// Arrange and return the items with their respective indices within the
// input sequence.
@ -756,7 +793,7 @@ bool arrange(Model &model, coordf_t min_obj_distance,
}
};
if(result.empty()) return false;
if(result.empty() || stopcondition()) return false;
if(first_bin_only) {
applyResult(result.front(), 0, shapemap);

View File

@ -35,6 +35,8 @@ public:
Point first_point() const;
virtual Point last_point() const = 0;
virtual Lines lines() const = 0;
size_t size() const { return points.size(); }
bool empty() const { return points.empty(); }
double length() const;
bool is_valid() const { return this->points.size() >= 2; }

View File

@ -22,6 +22,7 @@ typedef Point Vector;
// Vector types with a fixed point coordinate base type.
typedef Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign> Vec2crd;
typedef Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign> Vec3crd;
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
typedef Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign> Vec2i64;
typedef Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign> Vec3i64;

View File

@ -103,6 +103,12 @@ inline void polygons_rotate(Polygons &polys, double angle)
p.rotate(cos_angle, sin_angle);
}
inline void polygons_reverse(Polygons &polys)
{
for (Polygon &p : polys)
p.reverse();
}
inline Points to_points(const Polygon &poly)
{
return poly.points;

View File

@ -184,15 +184,13 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
bool Polyline::is_straight() const
{
/* Check that each segment's direction is equal to the line connecting
first point and last point. (Checking each line against the previous
one would cause the error to accumulate.) */
// Check that each segment's direction is equal to the line connecting
// first point and last point. (Checking each line against the previous
// one would cause the error to accumulate.)
double dir = Line(this->first_point(), this->last_point()).direction();
Lines lines = this->lines();
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
if (!line->parallel_to(dir)) return false;
}
for (const auto &line: this->lines())
if (! line.parallel_to(dir))
return false;
return true;
}

View File

@ -21,6 +21,8 @@ public:
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {}
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {}
explicit Polyline(const Point &p1, const Point &p2) { points.reserve(2); points.emplace_back(p1); points.emplace_back(p2); }
explicit Polyline(const Points &points) : MultiPoint(points) {}
explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {}
Polyline& operator=(const Polyline &other) { points = other.points; return *this; }
Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; }
static Polyline new_scale(const std::vector<Vec2d> &points) {

View File

@ -392,9 +392,12 @@ void Print::add_model_object(ModelObject* model_object, int idx)
//FIXME lock mutex!
this->invalidate_all_steps();
for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
size_t volume_id = 0;
for (const ModelVolume *volume : model_object->volumes) {
if (! volume->is_model_part() && ! volume->is_modifier())
continue;
// Get the config applied to this volume.
PrintRegionConfig config = this->_region_config_from_model_volume(*model_object->volumes[volume_id]);
PrintRegionConfig config = this->_region_config_from_model_volume(*volume);
// Find an existing print region with the same config.
size_t region_id = size_t(-1);
for (size_t i = 0; i < m_regions.size(); ++ i)
@ -409,6 +412,7 @@ void Print::add_model_object(ModelObject* model_object, int idx)
}
// Assign volume to a region.
object->add_region_volume(region_id, volume_id);
++ volume_id;
}
// Apply config to print object.
@ -893,7 +897,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
ModelVolume *volume = model_object->volumes[volume_id];
//FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned.
if (! volume->material_id().empty() && ! volume->config.has("extruder"))
if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder"))
volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1);
}
}
@ -1295,6 +1299,9 @@ void Print::_make_wipe_tower()
}
m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
wipe_tower.tool_change((unsigned int)-1, false));
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
}
std::string Print::output_filename() const

View File

@ -135,7 +135,10 @@ public:
const Print* print() const { return m_print; }
const PrintRegionConfig& config() const { return m_config; }
Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
// Average diameter of nozzles participating on extruding this region.
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
// Average diameter of nozzles participating on extruding this region.
coordf_t bridging_height_avg(const PrintConfig &print_config) const;
// Methods modifying the PrintRegion's state:
public:
@ -252,6 +255,10 @@ public:
// Called when slicing to SVG (see Print.pm sub export_svg), and used by perimeters.t
void slice();
// Helpers to slice support enforcer / blocker meshes by the support generator.
std::vector<ExPolygons> slice_support_enforcers() const;
std::vector<ExPolygons> slice_support_blockers() const;
private:
void make_perimeters();
void prepare_infill();
@ -297,6 +304,7 @@ private:
void set_started(PrintObjectStep step);
void set_done(PrintObjectStep step);
std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier);
std::vector<ExPolygons> _slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
};
struct WipeTowerData
@ -309,6 +317,8 @@ struct WipeTowerData
std::unique_ptr<WipeTower::ToolChangeResult> priming;
std::vector<std::vector<WipeTower::ToolChangeResult>> tool_changes;
std::unique_ptr<WipeTower::ToolChangeResult> final_purge;
std::vector<float> used_filament;
int number_of_toolchanges;
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
float depth;
@ -318,6 +328,8 @@ struct WipeTowerData
priming.reset(nullptr);
tool_changes.clear();
final_purge.reset(nullptr);
used_filament.clear();
number_of_toolchanges = -1;
depth = 0.f;
}
};
@ -331,6 +343,8 @@ struct PrintStatistics
double total_extruded_volume;
double total_cost;
double total_weight;
double total_wipe_tower_cost;
double total_wipe_tower_filament;
std::map<size_t, float> filament_stats;
void clear() {
@ -340,6 +354,8 @@ struct PrintStatistics
total_extruded_volume = 0.;
total_cost = 0.;
total_weight = 0.;
total_wipe_tower_cost = 0.;
total_wipe_tower_filament = 0.;
filament_stats.clear();
}
};
@ -463,7 +479,7 @@ private:
// If the background processing stop was requested, throw CanceledException.
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
void throw_if_canceled() { if (m_canceled) throw CanceledException(); }
void throw_if_canceled() const { if (m_canceled) throw CanceledException(); }
void _make_skirt();
void _make_brim();

View File

@ -161,7 +161,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Speed for printing bridges.");
def->sidetext = L("mm/s");
def->cli = "bridge-speed=f";
def->aliases.push_back("bridge_feed_rate");
def->aliases = { "bridge_feed_rate" };
def->min = 0;
def->default_value = new ConfigOptionFloat(60);
@ -274,7 +274,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Distance used for the auto-arrange feature of the plater.");
def->sidetext = L("mm");
def->cli = "duplicate-distance=f";
def->aliases.push_back("multiply_distance");
def->aliases = { "multiply_distance" };
def->min = 0;
def->default_value = new ConfigOptionFloat(6);
@ -335,7 +335,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Archimedean Chords"));
def->enum_labels.push_back(L("Octagram Spiral"));
// solid_fill_pattern is an obsolete equivalent to external_fill_pattern.
def->aliases.push_back("solid_fill_pattern");
def->aliases = { "solid_fill_pattern" };
def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
def = this->add("external_perimeter_extrusion_width", coFloatOrPercent);
@ -923,8 +923,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Speed for printing the internal fill. Set to zero for auto.");
def->sidetext = L("mm/s");
def->cli = "infill-speed=f";
def->aliases.push_back("print_feed_rate");
def->aliases.push_back("infill_feed_rate");
def->aliases = { "print_feed_rate", "infill_feed_rate" };
def->min = 0;
def->default_value = new ConfigOptionFloat(80);
@ -1272,7 +1271,7 @@ void PrintConfigDef::init_fff_params()
def->category = L("Extruders");
def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1.");
def->cli = "perimeter-extruder=i";
def->aliases.push_back("perimeters_extruder");
def->aliases = { "perimeters_extruder" };
def->min = 1;
def->default_value = new ConfigOptionInt(1);
@ -1285,7 +1284,7 @@ void PrintConfigDef::init_fff_params()
"If expressed as percentage (for example 200%) it will be computed over layer height.");
def->sidetext = L("mm or % (leave 0 for default)");
def->cli = "perimeter-extrusion-width=s";
def->aliases.push_back("perimeters_extrusion_width");
def->aliases = { "perimeters_extrusion_width" };
def->default_value = new ConfigOptionFloatOrPercent(0, false);
def = this->add("perimeter_speed", coFloat);
@ -1294,7 +1293,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto.");
def->sidetext = L("mm/s");
def->cli = "perimeter-speed=f";
def->aliases.push_back("perimeter_feed_rate");
def->aliases = { "perimeter_feed_rate" };
def->min = 0;
def->default_value = new ConfigOptionFloat(60);
@ -1307,7 +1306,7 @@ void PrintConfigDef::init_fff_params()
"if the Extra Perimeters option is enabled.");
def->sidetext = L("(minimum)");
def->cli = "perimeters=i";
def->aliases.push_back("perimeter_offsets");
def->aliases = { "perimeter_offsets" };
def->min = 0;
def->default_value = new ConfigOptionInt(3);
@ -1635,7 +1634,7 @@ void PrintConfigDef::init_fff_params()
def->sidetext = L("mm/s or %");
def->cli = "solid-infill-speed=s";
def->ratio_over = "infill_speed";
def->aliases.push_back("solid_infill_feed_rate");
def->aliases = { "solid_infill_feed_rate" };
def->min = 0;
def->default_value = new ConfigOptionFloatOrPercent(20, false);
@ -1717,6 +1716,14 @@ void PrintConfigDef::init_fff_params()
def->cli = "support-material!";
def->default_value = new ConfigOptionBool(false);
def = this->add("support_material_auto", coBool);
def->label = L("Auto generated supports");
def->category = L("Support material");
def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\
" If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only.");
def->cli = "support-material-auto!";
def->default_value = new ConfigOptionBool(true);
def = this->add("support_material_xy_spacing", coFloatOrPercent);
def->label = L("XY separation between an object and its support");
def->category = L("Support material");
@ -1755,7 +1762,7 @@ void PrintConfigDef::init_fff_params()
"for the first object layer.");
def->sidetext = L("mm");
def->cli = "support-material-contact-distance=f";
def->min = 0;
// def->min = 0;
def->enum_values.push_back("0");
def->enum_values.push_back("0.2");
def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str());
@ -1981,7 +1988,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Speed for travel moves (jumps between distant extrusion points).");
def->sidetext = L("mm/s");
def->cli = "travel-speed=f";
def->aliases.push_back("travel_feed_rate");
def->aliases = { "travel_feed_rate" };
def->min = 1;
def->default_value = new ConfigOptionFloat(130);

View File

@ -345,6 +345,7 @@ public:
ConfigOptionFloatOrPercent extrusion_width;
ConfigOptionFloatOrPercent first_layer_height;
ConfigOptionBool infill_only_where_needed;
// Force the generation of solid shells between adjacent materials/volumes.
ConfigOptionBool interface_shells;
ConfigOptionFloat layer_height;
ConfigOptionInt raft_layers;
@ -352,6 +353,9 @@ public:
// ConfigOptionFloat seam_preferred_direction;
// ConfigOptionFloat seam_preferred_direction_jitter;
ConfigOptionBool support_material;
// Automatic supports (generated based on support_material_threshold).
ConfigOptionBool support_material_auto;
// Direction of the support pattern (in XY plane).
ConfigOptionFloat support_material_angle;
ConfigOptionBool support_material_buildplate_only;
ConfigOptionFloat support_material_contact_distance;
@ -361,12 +365,15 @@ public:
ConfigOptionBool support_material_interface_contact_loops;
ConfigOptionInt support_material_interface_extruder;
ConfigOptionInt support_material_interface_layers;
// Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
ConfigOptionFloat support_material_interface_spacing;
ConfigOptionFloatOrPercent support_material_interface_speed;
ConfigOptionEnum<SupportMaterialPattern> support_material_pattern;
// Spacing between support material lines (the hatching distance).
ConfigOptionFloat support_material_spacing;
ConfigOptionFloat support_material_speed;
ConfigOptionBool support_material_synchronize_layers;
// Overhang angle threshold.
ConfigOptionInt support_material_threshold;
ConfigOptionBool support_material_with_sheath;
ConfigOptionFloatOrPercent support_material_xy_spacing;
@ -389,6 +396,7 @@ protected:
// OPT_PTR(seam_preferred_direction);
// OPT_PTR(seam_preferred_direction_jitter);
OPT_PTR(support_material);
OPT_PTR(support_material_auto);
OPT_PTR(support_material_angle);
OPT_PTR(support_material_buildplate_only);
OPT_PTR(support_material_contact_distance);
@ -436,10 +444,12 @@ public:
ConfigOptionInt infill_every_layers;
ConfigOptionFloatOrPercent infill_overlap;
ConfigOptionFloat infill_speed;
// Detect bridging perimeters
ConfigOptionBool overhangs;
ConfigOptionInt perimeter_extruder;
ConfigOptionFloatOrPercent perimeter_extrusion_width;
ConfigOptionFloat perimeter_speed;
// Total number of perimeters.
ConfigOptionInt perimeters;
ConfigOptionFloatOrPercent small_perimeter_speed;
ConfigOptionFloat solid_infill_below_area;
@ -447,6 +457,7 @@ public:
ConfigOptionFloatOrPercent solid_infill_extrusion_width;
ConfigOptionInt solid_infill_every_layers;
ConfigOptionFloatOrPercent solid_infill_speed;
// Detect thin walls.
ConfigOptionBool thin_walls;
ConfigOptionFloatOrPercent top_infill_extrusion_width;
ConfigOptionInt top_solid_layers;

View File

@ -112,8 +112,18 @@ bool PrintObject::reload_model_instances()
Points copies;
copies.reserve(m_model_object->instances.size());
for (const ModelInstance *mi : m_model_object->instances)
{
#if ENABLE_MODELINSTANCE_3D_OFFSET
if (mi->is_printable())
{
const Vec3d& offset = mi->get_offset();
copies.emplace_back(Point::new_scale(offset(0), offset(1)));
}
#else
if (mi->is_printable())
copies.emplace_back(Point::new_scale(mi->offset(0), mi->offset(1)));
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
return this->set_copies(copies);
}
@ -490,6 +500,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
steps.emplace_back(posSlice);
} else if (
opt_key == "support_material"
|| opt_key == "support_material_auto"
|| opt_key == "support_material_angle"
|| opt_key == "support_material_buildplate_only"
|| opt_key == "support_material_enforce_layers"
@ -1560,19 +1571,52 @@ end:
std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier)
{
std::vector<ExPolygons> layers;
std::vector<const ModelVolume*> volumes;
if (region_id < this->region_volumes.size()) {
std::vector<int> &volumes = this->region_volumes[region_id];
for (int volume_id : this->region_volumes[region_id]) {
const ModelVolume *volume = this->model_object()->volumes[volume_id];
if (modifier ? volume->is_modifier() : volume->is_model_part())
volumes.emplace_back(volume);
}
}
return this->_slice_volumes(z, volumes);
}
std::vector<ExPolygons> PrintObject::slice_support_enforcers() const
{
std::vector<const ModelVolume*> volumes;
for (const ModelVolume *volume : this->model_object()->volumes)
if (volume->is_support_enforcer())
volumes.emplace_back(volume);
std::vector<float> zs;
zs.reserve(this->layers().size());
for (const Layer *l : this->layers())
zs.emplace_back(l->slice_z);
return this->_slice_volumes(zs, volumes);
}
std::vector<ExPolygons> PrintObject::slice_support_blockers() const
{
std::vector<const ModelVolume*> volumes;
for (const ModelVolume *volume : this->model_object()->volumes)
if (volume->is_support_blocker())
volumes.emplace_back(volume);
std::vector<float> zs;
zs.reserve(this->layers().size());
for (const Layer *l : this->layers())
zs.emplace_back(l->slice_z);
return this->_slice_volumes(zs, volumes);
}
std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const
{
std::vector<ExPolygons> layers;
if (! volumes.empty()) {
// Compose mesh.
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
TriangleMesh mesh;
for (int volume_id : volumes) {
ModelVolume *volume = this->model_object()->volumes[volume_id];
if (volume->modifier == modifier)
mesh.merge(volume->mesh);
m_print->throw_if_canceled();
}
for (const ModelVolume *v : volumes)
mesh.merge(v->mesh);
if (mesh.stl.stats.number_of_facets > 0) {
// transform mesh
// we ignore the per-instance transformations currently and only
@ -1582,14 +1626,13 @@ std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::
mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), - float(this->model_object()->bounding_box().min(2)));
// perform actual slicing
TriangleMeshSlicer mslicer;
Print *print = this->print();
const Print *print = this->print();
auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();});
mslicer.init(&mesh, callback);
mslicer.slice(z, &layers, callback);
m_print->throw_if_canceled();
}
}
}
return layers;
}

View File

@ -56,4 +56,9 @@ coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
print_config.nozzle_diameter.get_at(m_config.solid_infill_extruder.value - 1)) / 3.;
}
coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const
{
return this->nozzle_dmr_avg(print_config) * sqrt(m_config.bridge_flow_ratio.value);
}
}

View File

@ -224,9 +224,9 @@ std::vector<coordf_t> layer_height_profile_adaptive(
// 1) Initialize the SlicingAdaptive class with the object meshes.
SlicingAdaptive as;
as.set_slicing_parameters(slicing_params);
for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it)
if (! (*it)->modifier)
as.add_mesh(&(*it)->mesh);
for (const ModelVolume *volume : volumes)
if (volume->is_model_part())
as.add_mesh(&volume->mesh);
as.prepare();
// 2) Generate layers using the algorithm of @platsch

View File

@ -248,10 +248,10 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
#ifdef SLIC3R_DEBUG
static int iRun = 0;
iRun ++;
for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it)
for (const MyLayer *layer : top_contacts)
Slic3r::SVG::export_expolygons(
debug_out_path("support-top-contacts-%d-%lf.svg", iRun, (*it)->print_z),
union_ex((*it)->polygons, false));
debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z),
union_ex(layer->polygons, false));
#endif /* SLIC3R_DEBUG */
BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts";
@ -282,7 +282,17 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers(
object, bottom_contacts, top_contacts, layer_storage);
this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
this->trim_support_layers_by_object(object, top_contacts,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
#ifdef SLIC3R_DEBUG
for (const MyLayer *layer : top_contacts)
Slic3r::SVG::export_expolygons(
debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z),
union_ex(layer->polygons, false));
#endif
BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers";
@ -420,29 +430,17 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t
{
// 1) Count the new polygons first.
size_t n_polygons_new = 0;
for (const LayerRegion* pregion : layer.regions()) {
const LayerRegion &region = *pregion;
const SurfaceCollection &slices = region.slices;
for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) {
const Surface &surface = *it;
for (const LayerRegion *region : layer.regions())
for (const Surface &surface : region->slices.surfaces)
if (surface.surface_type == surface_type)
n_polygons_new += surface.expolygon.holes.size() + 1;
}
}
// 2) Collect the new polygons.
Polygons out;
out.reserve(n_polygons_new);
for (const LayerRegion *pregion : layer.regions()) {
const LayerRegion &region = *pregion;
const SurfaceCollection &slices = region.slices;
for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) {
const Surface &surface = *it;
for (const LayerRegion *region : layer.regions())
for (const Surface &surface : region->slices.surfaces)
if (surface.surface_type == surface_type)
polygons_append(out, surface.expolygon);
}
}
return out;
}
@ -452,8 +450,8 @@ Polygons collect_slices_outer(const Layer &layer)
{
Polygons out;
out.reserve(out.size() + layer.slices.expolygons.size());
for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it)
out.push_back(it->contour);
for (const ExPolygon &expoly : layer.slices.expolygons)
out.emplace_back(expoly.contour);
return out;
}
@ -461,8 +459,11 @@ class SupportGridPattern
{
public:
SupportGridPattern(
// Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy)
const Polygons &support_polygons,
// Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons.
const Polygons &trimming_polygons,
// Grid spacing, given by "support_material_spacing" + m_support_material_flow.spacing()
coordf_t support_spacing,
coordf_t support_angle) :
m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons),
@ -485,7 +486,8 @@ public:
m_grid.set_bbox(bbox);
m_grid.create(*m_support_polygons, grid_resolution);
m_grid.calculate_sdf();
// Extract a bounding contour from the grid, trim by the object.
// Sample a single point per input support polygon, keep it as a reference to maintain corresponding
// polygons if ever these polygons get split into parts by the trimming polygons.
m_island_samples = island_samples(*m_support_polygons);
}
@ -493,22 +495,22 @@ public:
// and trim the extracted polygons by trimming_polygons.
// Trimming by the trimming_polygons may split the extracted polygons into pieces.
// Remove all the pieces, which do not contain any of the island_samples.
Polygons extract_support(const coord_t offset_in_grid)
Polygons extract_support(const coord_t offset_in_grid, bool fill_holes)
{
// Generate islands, so each island may be tested for overlap with m_island_samples.
assert(std::abs(2 * offset_in_grid) < m_grid.resolution());
ExPolygons islands = diff_ex(
m_grid.contours_simplified(offset_in_grid),
m_grid.contours_simplified(offset_in_grid, fill_holes),
*m_trimming_polygons, false);
// Extract polygons, which contain some of the m_island_samples.
Polygons out;
std::vector<std::pair<Point,bool>> samples_inside;
for (ExPolygon &island : islands) {
BoundingBox bbox = get_extents(island.contour);
// Samples are sorted lexicographically.
auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.min - Point(1, 1)));
auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.max + Point(1, 1)));
samples_inside.clear();
std::vector<std::pair<Point,bool>> samples_inside;
for (auto it = it_lower; it != it_upper; ++ it)
if (bbox.contains(*it))
samples_inside.push_back(std::make_pair(*it, false));
@ -569,8 +571,10 @@ public:
private:
SupportGridPattern& operator=(const SupportGridPattern &rhs);
#if 0
// Get some internal point of an expolygon, to be used as a representative
// sample to test, whether this island is inside another island.
//FIXME this was quick, but not sufficiently robust.
static Point island_sample(const ExPolygon &expoly)
{
// Find the lowest point lexicographically.
@ -591,7 +595,10 @@ private:
double coef = 20. / sqrt(l2);
return Point(p2(0) + coef * v(0), p2(1) + coef * v(1));
}
#endif
// Sample one internal point per expolygon.
// FIXME this is quite an overkill to calculate a complete offset just to get a single point, but at least it is robust.
static Points island_samples(const ExPolygons &expolygons)
{
Points pts;
@ -629,9 +636,164 @@ private:
coordf_t m_support_spacing;
Slic3r::EdgeGrid::Grid m_grid;
// Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding
// to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons.
Points m_island_samples;
};
namespace SupportMaterialInternal {
static inline bool has_bridging_perimeters(const ExtrusionLoop &loop)
{
for (const ExtrusionPath &ep : loop.paths)
if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty())
return ep.size() >= (ep.is_closed() ? 3 : 2);
return false;
}
static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters)
{
for (const ExtrusionEntity *ee : perimeters.entities) {
if (ee->is_collection()) {
for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
assert(! ee2->is_collection());
if (ee2->is_loop())
if (has_bridging_perimeters(*static_cast<const ExtrusionLoop*>(ee2)))
return true;
}
} else if (ee->is_loop() && has_bridging_perimeters(*static_cast<const ExtrusionLoop*>(ee)))
return true;
}
return false;
}
static bool has_bridging_fills(const ExtrusionEntityCollection &fills)
{
for (const ExtrusionEntity *ee : fills.entities) {
assert(ee->is_collection());
for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
assert(! ee2->is_collection());
assert(! ee2->is_loop());
if (ee2->role() == erBridgeInfill)
return true;
}
}
return false;
}
static bool has_bridging_extrusions(const Layer &layer)
{
for (const LayerRegion *region : layer.regions()) {
if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters))
return true;
if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills))
return true;
}
return false;
}
static inline void collect_bridging_perimeter_areas(const ExtrusionLoop &loop, const float expansion_scaled, Polygons &out)
{
assert(expansion_scaled >= 0.f);
for (const ExtrusionPath &ep : loop.paths)
if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) {
float exp = 0.5f * scale_(ep.width) + expansion_scaled;
if (ep.is_closed()) {
if (ep.size() >= 3) {
// This is a complete loop.
// Add the outer contour first.
Polygon poly;
poly.points = ep.polyline.points;
poly.points.pop_back();
if (poly.area() < 0)
poly.reverse();
polygons_append(out, offset(poly, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS));
Polygons holes = offset(poly, - exp, SUPPORT_SURFACES_OFFSET_PARAMETERS);
polygons_reverse(holes);
polygons_append(out, holes);
}
} else if (ep.size() >= 2) {
// Offset the polyline.
polygons_append(out, offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
}
}
static void collect_bridging_perimeter_areas(const ExtrusionEntityCollection &perimeters, const float expansion_scaled, Polygons &out)
{
for (const ExtrusionEntity *ee : perimeters.entities) {
if (ee->is_collection()) {
for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
assert(! ee2->is_collection());
if (ee2->is_loop())
collect_bridging_perimeter_areas(*static_cast<const ExtrusionLoop*>(ee2), expansion_scaled, out);
}
} else if (ee->is_loop())
collect_bridging_perimeter_areas(*static_cast<const ExtrusionLoop*>(ee), expansion_scaled, out);
}
}
static void remove_bridges_from_contacts(
const PrintConfig &print_config,
const Layer &lower_layer,
const Polygons &lower_layer_polygons,
LayerRegion *layerm,
float fw,
Polygons &contact_polygons)
{
// compute the area of bridging perimeters
Polygons bridges;
{
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
Polygons lower_grown_slices = offset(lower_layer_polygons,
//FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder-1))),
SUPPORT_SURFACES_OFFSET_PARAMETERS);
// Collect perimeters of this layer.
//FIXME split_at_first_point() could split a bridge mid-way
#if 0
Polylines overhang_perimeters = layerm->perimeters.as_polylines();
// workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
for (Polyline &polyline : overhang_perimeters)
polyline.points[0].x += 1;
// Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters.
overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices);
#else
Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices);
#endif
// only consider straight overhangs
// only consider overhangs having endpoints inside layer's slices
// convert bridging polylines into polygons by inflating them with their thickness
// since we're dealing with bridges, we can't assume width is larger than spacing,
// so we take the largest value and also apply safety offset to be ensure no gaps
// are left in between
Flow bridge_flow = layerm->flow(frPerimeter, true);
float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()));
for (Polyline &polyline : overhang_perimeters)
if (polyline.is_straight()) {
// This is a bridge
polyline.extend_start(fw);
polyline.extend_end(fw);
// Is the straight perimeter segment supported at both sides?
if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point()))
// Offset a polyline into a thick line.
polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
}
bridges = union_(bridges);
}
// remove the entire bridges and only support the unsupported edges
//FIXME the brided regions are already collected as layerm->bridged. Use it?
for (const Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1)
polygons_append(bridges, surface.expolygon);
//FIXME add the gap filled areas. Extrude the gaps with a bridge flow?
contact_polygons = diff(contact_polygons, bridges, true);
// Add the bridge anchors into the region.
//FIXME add supports at regular intervals to support long bridges!
polygons_append(contact_polygons,
intersection(
// Offset unsupported edges into polygons.
offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS),
bridges));
}
}
// Generate top contact layers supporting overhangs.
// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
// If supports over bed surface only are requested, don't generate contact layers over an object.
@ -643,9 +805,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
++ iRun;
#endif /* SLIC3R_DEBUG */
// Slice support enforcers / support blockers.
std::vector<ExPolygons> enforcers = object.slice_support_enforcers();
std::vector<ExPolygons> blockers = object.slice_support_blockers();
// Output layers, sorted by top Z.
MyLayersPtr contact_out;
const bool support_auto = m_object_config->support_material_auto.value;
// If user specified a custom angle threshold, convert it to radians.
// Zero means automatic overhang detection.
const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ?
@ -680,10 +847,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers.
// So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers.
size_t num_layers = this->has_support() ? object.layer_count() : 1;
contact_out.assign(num_layers, nullptr);
// For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow,
// and the other for the overhangs extruded with a normal flow.
contact_out.assign(num_layers * 2, nullptr);
tbb::spin_mutex layer_storage_mutex;
tbb::parallel_for(tbb::blocked_range<size_t>(this->has_raft() ? 0 : 1, num_layers),
[this, &object, &buildplate_covered, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range<size_t>& range) {
[this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out]
(const tbb::blocked_range<size_t>& range) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id)
{
const Layer &layer = *object.layers()[layer_id];
@ -694,6 +864,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
Polygons contact_polygons;
Polygons slices_margin_cached;
float slices_margin_cached_offset = -1.;
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices.expolygons);
// Offset of the lower layer, to trim the support polygons with to calculate dense supports.
float no_interface_offset = 0.f;
if (layer_id == 0) {
// This is the first object layer, so the object is being printed on a raft and
// we're here just to get the object footprint for the raft.
@ -708,6 +881,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Extrusion width accounts for the roundings of the extrudates.
// It is the maximum widh of the extrudate.
float fw = float(layerm->flow(frExternalPerimeter).scaled_width());
no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw);
float lower_layer_offset =
(layer_id < m_object_config->support_material_enforce_layers.value) ?
// Enforce a full possible support, ignore the overhang angle.
@ -720,7 +894,6 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Overhang polygons for this layer and region.
Polygons diff_polygons;
Polygons layerm_polygons = to_polygons(layerm->slices);
Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons);
if (lower_layer_offset == 0.f) {
// Support everything.
diff_polygons = diff(layerm_polygons, lower_layer_polygons);
@ -730,24 +903,57 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]);
}
} else {
if (support_auto) {
// Get the regions needing a suport, collapse very tiny spots.
//FIXME cache the lower layer offset if this layer has multiple regions.
#if 1
diff_polygons = offset2(
diff(layerm_polygons,
offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
-0.1f*fw, +0.1f*fw);
offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
//FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to
// no support at all for not so steep overhangs.
- 0.1f * fw, 0.1f * fw);
#else
diff_polygons =
diff(layerm_polygons,
offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
#endif
if (! buildplate_covered.empty()) {
// Don't support overhangs above the top surfaces.
// This step is done before the contact surface is calculated by growing the overhang region.
diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]);
}
if (diff_polygons.empty())
continue;
if (! diff_polygons.empty()) {
// Offset the support regions back to a full overhang, restrict them to the full overhang.
// This is done to increase size of the supporting columns below, as they are calculated by
// propagating these contact surfaces downwards.
diff_polygons = diff(
intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons),
lower_layer_polygons);
}
}
if (! enforcers.empty()) {
// Apply the "support enforcers".
//FIXME add the "enforcers" to the sparse support regions only.
const ExPolygons &enforcer = enforcers[layer_id - 1];
if (! enforcer.empty()) {
// Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(enforcer)),
offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (! new_contacts.empty()) {
if (diff_polygons.empty())
diff_polygons = std::move(new_contacts);
else
diff_polygons = union_(diff_polygons, new_contacts);
}
}
}
}
// Apply the "support blockers".
if (! diff_polygons.empty() && ! blockers.empty() && ! blockers[layer_id].empty()) {
// Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
diff_polygons = diff(diff_polygons, to_polygons(blockers[layer_id]));
}
if (diff_polygons.empty())
continue;
@ -762,73 +968,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
}
#endif /* SLIC3R_DEBUG */
if (m_object_config->dont_support_bridges) {
// compute the area of bridging perimeters
// Note: this is duplicate code from GCode.pm, we need to refactor
if (true) {
Polygons bridged_perimeters;
{
Flow bridge_flow = layerm->flow(frPerimeter, true);
coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder-1);
Polygons lower_grown_slices = offset(lower_layer_polygons, 0.5f*float(scale_(nozzle_diameter)), SUPPORT_SURFACES_OFFSET_PARAMETERS);
// Collect perimeters of this layer.
// TODO: split_at_first_point() could split a bridge mid-way
Polylines overhang_perimeters;
for (ExtrusionEntity* extrusion_entity : layerm->perimeters.entities) {
const ExtrusionEntityCollection *island = dynamic_cast<ExtrusionEntityCollection*>(extrusion_entity);
assert(island != NULL);
for (size_t i = 0; i < island->entities.size(); ++ i) {
ExtrusionEntity *entity = island->entities[i];
ExtrusionLoop *loop = dynamic_cast<Slic3r::ExtrusionLoop*>(entity);
overhang_perimeters.push_back(loop ?
loop->as_polyline() :
dynamic_cast<const Slic3r::ExtrusionPath*>(entity)->polyline);
}
}
// workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
for (Polyline &polyline : overhang_perimeters)
polyline.points[0](0) += 1;
// Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters.
overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices);
// only consider straight overhangs
// only consider overhangs having endpoints inside layer's slices
// convert bridging polylines into polygons by inflating them with their thickness
// since we're dealing with bridges, we can't assume width is larger than spacing,
// so we take the largest value and also apply safety offset to be ensure no gaps
// are left in between
float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()));
for (Polyline &polyline : overhang_perimeters)
if (polyline.is_straight()) {
// This is a bridge
polyline.extend_start(fw);
polyline.extend_end(fw);
// Is the straight perimeter segment supported at both sides?
if (layer.slices.contains(polyline.first_point()) && layer.slices.contains(polyline.last_point()))
// Offset a polyline into a thick line.
polygons_append(bridged_perimeters, offset(polyline, 0.5f * w + 10.f));
}
bridged_perimeters = union_(bridged_perimeters);
}
// remove the entire bridges and only support the unsupported edges
Polygons bridges;
for (const Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1)
polygons_append(bridges, surface.expolygon);
diff_polygons = diff(diff_polygons, bridges, true);
polygons_append(bridges, bridged_perimeters);
polygons_append(diff_polygons,
intersection(
// Offset unsupported edges into polygons.
offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS),
bridges));
} else {
// just remove bridged areas
diff_polygons = diff(diff_polygons, layerm->bridged, true);
}
} // if (m_objconfig->dont_support_bridges)
if (this->m_object_config->dont_support_bridges)
SupportMaterialInternal::remove_bridges_from_contacts(
*m_print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons);
if (diff_polygons.empty())
continue;
@ -842,7 +984,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
union_ex(diff_polygons, false));
#endif /* SLIC3R_DEBUG */
if (this->has_contact_loops())
//FIXME the overhang_polygons are used to construct the support towers as well.
//if (this->has_contact_loops())
// Store the exact contour of the overhang for the contact loops.
polygons_append(overhang_polygons, diff_polygons);
// Let's define the required contact area by using a max gap of half the upper
@ -851,12 +995,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// on the other side of the object (if it's very thin).
{
//FIMXE 1) Make the offset configurable, 2) Make the Z span configurable.
//FIXME one should trim with the layer span colliding with the support layer, this layer
// may be lower than lower_layer, so the support area needed may need to be actually bigger!
// For the same reason, the non-bridging support area may be smaller than the bridging support area!
float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy)));
if (slices_margin_cached_offset != slices_margin_offset) {
slices_margin_cached_offset = slices_margin_offset;
slices_margin_cached = (slices_margin_offset == 0.f) ?
to_polygons(lower_layer.slices.expolygons) :
offset(lower_layer.slices.expolygons, slices_margin_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS);
lower_layer_polygons :
offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! buildplate_covered.empty()) {
// Trim the inflated contact surfaces by the top surfaces as well.
polygons_append(slices_margin_cached, buildplate_covered[layer_id]);
@ -879,43 +1026,27 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
} // for each layer.region
} // end of Generate overhang/contact_polygons for non-raft layers.
// now apply the contact areas to the layer were they need to be made
// Now apply the contact areas to the layer where they need to be made.
if (! contact_polygons.empty()) {
// get the average nozzle diameter used on this layer
MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact);
new_layer.idx_object_layer_above = layer_id;
if (m_slicing_params.soluble_interface) {
// Align the contact surface height with a layer immediately below the supported layer.
new_layer.print_z = layer.print_z - layer.height;
if (layer_id == 0) {
// This is a raft contact layer sitting directly on the print bed.
new_layer.height = m_slicing_params.contact_raft_layer_height;
new_layer.bottom_z = m_slicing_params.raft_interface_top_z;
} else {
// Interface layer will be synchronized with the object.
assert(layer_id > 0);
new_layer.height = object.layers()[layer_id - 1]->height;
new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z;
}
} else {
// Contact layer will be printed with a normal flow, but
// it will support layers printed with a bridging flow.
//FIXME Probably printing with the bridge flow? How about the unsupported perimeters? Are they printed with the bridging flow?
// In the future we may switch to a normal extrusion flow for the supported bridges.
// Get the average nozzle diameter used on this layer.
coordf_t nozzle_dmr = 0.;
for (const LayerRegion *region : layer.regions())
nozzle_dmr += region->region()->nozzle_dmr_avg(*m_print_config);
nozzle_dmr /= coordf_t(layer.regions().size());
new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance;
new_layer.bottom_z = new_layer.print_z;
new_layer.height = 0.;
MyLayer *bridging_layer = nullptr;
if (layer_id == 0) {
// This is a raft contact layer sitting directly on the print bed.
assert(this->has_raft());
new_layer.print_z = m_slicing_params.raft_contact_top_z;
new_layer.bottom_z = m_slicing_params.raft_interface_top_z;
new_layer.height = m_slicing_params.contact_raft_layer_height;
} else if (m_slicing_params.soluble_interface) {
// Align the contact surface height with a layer immediately below the supported layer.
// Interface layer will be synchronized with the object.
new_layer.print_z = layer.print_z - layer.height;
new_layer.height = object.layers()[layer_id - 1]->height;
new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z;
} else {
new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance;
new_layer.bottom_z = new_layer.print_z;
new_layer.height = 0.;
// Ignore this contact area if it's too low.
// Don't want to print a layer below the first layer height as it may not stick well.
//FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact
@ -932,6 +1063,36 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and
// its height will be set adaptively later on.
}
// Contact layer will be printed with a normal flow, but
// it will support layers printed with a bridging flow.
if (SupportMaterialInternal::has_bridging_extrusions(layer)) {
coordf_t bridging_height = 0.;
for (const LayerRegion *region : layer.regions())
bridging_height += region->region()->bridging_height_avg(*m_print_config);
bridging_height /= coordf_t(layer.regions().size());
coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance;
if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) {
// Not below the first layer height means this layer is printable.
if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) {
// Align the layer with the 1st layer height.
bridging_print_z = m_slicing_params.first_print_layer_height;
}
if (bridging_print_z < new_layer.print_z - EPSILON) {
// Allocate the new layer.
bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact);
bridging_layer->idx_object_layer_above = layer_id;
bridging_layer->print_z = bridging_print_z;
if (bridging_print_z == m_slicing_params.first_print_layer_height) {
bridging_layer->bottom_z = 0;
bridging_layer->height = m_slicing_params.first_print_layer_height;
} else {
// Don't know the height yet.
bridging_layer->bottom_z = bridging_print_z;
bridging_layer->height = 0;
}
}
}
}
}
@ -940,27 +1101,112 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
contact_polygons,
// Trimming polygons, to trim the stretched support islands.
slices_margin_cached,
// How much to offset the extracted contour outside of the grid.
// Grid resolution.
m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
Geometry::deg2rad(m_object_config->support_material_angle.value));
// 1) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5);
// 2) Contact polygons will be projected down. To keep the interface and base layers to grow, return a contour a tiny bit smaller than the grid cells.
new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3));
// 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells.
new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true));
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
if (layer_id == 0 || m_slicing_params.soluble_interface) {
// if (no_interface_offset == 0.f) {
new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true);
} else {
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
Polygons dense_interface_polygons = diff(overhang_polygons,
offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS));
// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (! dense_interface_polygons.empty()) {
//FIXME do it for the bridges only?
SupportGridPattern support_grid_pattern(
// Support islands, to be stretched into a grid.
dense_interface_polygons,
// Trimming polygons, to trim the stretched support islands.
slices_margin_cached,
// Grid resolution.
m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
Geometry::deg2rad(m_object_config->support_material_angle.value));
new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false);
}
}
// Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded.
// Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons.
// Store the overhang polygons.
// The overhang polygons are used in the path generator for planning of the contact loops.
// if (this->has_contact_loops())
// if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug.
new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons));
contact_out[layer_id] = &new_layer;
contact_out[layer_id * 2] = &new_layer;
if (bridging_layer != nullptr) {
bridging_layer->polygons = new_layer.polygons;
bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons);
bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons);
contact_out[layer_id * 2 + 1] = bridging_layer;
}
}
}
});
// Compress contact_out, remove the nullptr items.
remove_nulls(contact_out);
// Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z.
std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; });
// Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter),
// the top contact layer is merged into the bottom contact layer.
{
int i = 0;
int k = 0;
{
// Find the span of layers, which are to be printed at the first layer height.
int j = 0;
for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j);
if (j > 0) {
// Merge the contact_out layers (0) to (j - 1) into the contact_out[0].
MyLayer &dst = *contact_out.front();
for (int u = 1; u < j; ++ u) {
MyLayer &src = *contact_out[u];
// The union_() does not support move semantic yet, but maybe one day it will.
dst.polygons = union_(dst.polygons, std::move(src.polygons));
*dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons));
*dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons));
// Source polygon is no more needed, it will not be refrenced. Release its data.
src.reset();
}
// Snap the first layer to the 1st layer height.
dst.print_z = m_slicing_params.first_print_layer_height;
dst.height = m_slicing_params.first_print_layer_height;
dst.bottom_z = 0;
++ k;
}
i = j;
}
for (; i < int(contact_out.size()); ++ k) {
// Find the span of layers closer than m_support_layer_height_min.
int j = i + 1;
coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON;
for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ;
if (i + 1 < j) {
// Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i].
MyLayer &dst = *contact_out[i];
for (int u = i + 1; u < j; ++ u) {
MyLayer &src = *contact_out[u];
// The union_() does not support move semantic yet, but maybe one day it will.
dst.polygons = union_(dst.polygons, std::move(src.polygons));
*dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons));
*dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons));
// Source polygon is no more needed, it will not be refrenced. Release its data.
src.reset();
}
}
if (k < i)
contact_out[k] = contact_out[i];
i = j;
}
if (k < contact_out.size())
contact_out.erase(contact_out.begin() + k, contact_out.end());
}
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end";
return contact_out;
@ -996,7 +1242,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id;
const Layer &layer = *object.get_layer(layer_id);
// Collect projections of all contact areas above or at the same level as this top surface.
for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) {
for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) {
Polygons polygons_new;
// Contact surfaces are expanded away from the object, trimmed by the object.
// Use a slight positive offset to overlap the touching regions.
@ -1004,7 +1250,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
// Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form.
polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON));
#else
// Consume the contact_polygons. The contact polygons are already expanded into a grid form.
// Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller
// than the grid cells.
polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons));
#endif
// These are the overhang surfaces. They are touching the object and they are not expanded away from the object.
@ -1016,9 +1263,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
continue;
Polygons projection_raw = union_(projection);
// Top surfaces of this layer, to be used to stop the surface volume from growing down.
tbb::task_group task_group;
if (! m_object_config->support_material_buildplate_only)
// Find the bottom contact layers above the top surfaces of this layer.
task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] {
Polygons top = collect_region_slices_by_type(layer, stTop);
#ifdef SLIC3R_DEBUG
@ -1046,28 +1293,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
// Grow top surfaces so that interface and support generation are generated
// with some spacing from object - it looks we don't need the actual
// top shapes so this can be done here
//FIXME calculate layer height based on the actual thickness of the layer:
// If the layer is extruded with no bridging flow, support just the normal extrusions.
layer_new.height = m_slicing_params.soluble_interface ?
// Align the interface layer with the object's layer height.
object.layers()[layer_id + 1]->height :
// Place a bridge flow interface layer over the top surface.
//FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?)
// According to Jindrich the bottom surfaces work well.
//FIXME test the bridging flow instead?
m_support_material_interface_flow.nozzle_diameter;
layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z :
layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value;
layer_new.bottom_z = layer.print_z;
layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = ! m_slicing_params.soluble_interface;
//FIXME how much to inflate the top surface?
//FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
//FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! m_slicing_params.soluble_interface) {
// Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
// so there will be no support surfaces generated with thickness lower than m_support_layer_height_min.
for (size_t top_idx = size_t(std::max<int>(0, contact_idx));
top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + m_support_layer_height_min;
top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min + EPSILON;
++ top_idx) {
if (top_contacts[top_idx]->print_z > layer_new.print_z - m_support_layer_height_min) {
if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min - EPSILON) {
// A top layer has been found, which is close to the new bottom layer.
coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z;
assert(std::abs(diff) <= m_support_layer_height_min);
assert(std::abs(diff) <= this->m_support_layer_height_min + EPSILON);
if (diff > 0.) {
// The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer.
assert(diff < layer_new.height + EPSILON);
@ -1091,10 +1344,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
union_ex(layer_new.polygons, false));
#endif /* SLIC3R_DEBUG */
// Trim the already created base layers above the current layer intersecting with the new bottom contacts layer.
//FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage?
touching = offset(touching, float(SCALED_EPSILON));
for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
const Layer &layer_above = *object.layers()[layer_id_above];
if (layer_above.print_z > layer_new.print_z + EPSILON)
if (layer_above.print_z > layer_new.print_z - EPSILON)
break;
if (! layer_support_areas[layer_id_above].empty()) {
#ifdef SLIC3R_DEBUG
@ -1147,7 +1401,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
projection,
// Trimming polygons, to trim the stretched support islands.
trimming,
// How much to offset the extracted contour outside of the grid.
// Grid spacing.
m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
Geometry::deg2rad(m_object_config->support_material_angle.value));
tbb::task_group task_group_inner;
@ -1158,7 +1412,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
, &layer
#endif /* SLIC3R_DEBUG */
] {
layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25);
layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true);
#ifdef SLIC3R_DEBUG
Slic3r::SVG::export_expolygons(
debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z),
@ -1172,7 +1426,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
, &layer
#endif /* SLIC3R_DEBUG */
] {
projection_new = support_grid_pattern.extract_support(-5);
projection_new = support_grid_pattern.extract_support(-5, true);
#ifdef SLIC3R_DEBUG
Slic3r::SVG::export_expolygons(
debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z),
@ -1185,7 +1439,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
task_group.wait();
}
std::reverse(bottom_contacts.begin(), bottom_contacts.end());
trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy);
trim_support_layers_by_object(object, bottom_contacts,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
} // ! top_contacts.empty()
return bottom_contacts;
@ -1502,9 +1760,6 @@ void PrintObjectSupportMaterial::generate_base_layers(
assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z);
// Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new.
idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above,
[&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; });
// New polygons for layer_intermediate.
Polygons polygons_new;
@ -1523,12 +1778,10 @@ void PrintObjectSupportMaterial::generate_base_layers(
// 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here.
// 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen.
// 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top.
int idx_top_contact_overlapping = idx_top_contact_above;
while (idx_top_contact_overlapping >= 0 &&
top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON)
-- idx_top_contact_overlapping;
idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above,
[&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; });
// Collect all the top_contact layer intersecting with this layer.
for (; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) {
for ( int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) {
MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping];
if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON)
break;
@ -1608,7 +1861,10 @@ void PrintObjectSupportMaterial::generate_base_layers(
++ iRun;
#endif /* SLIC3R_DEBUG */
trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_gap_xy);
// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy);
this->trim_support_layers_by_object(object, intermediate_layers,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
}
void PrintObjectSupportMaterial::trim_support_layers_by_object(
@ -1653,19 +1909,23 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
const Layer &object_layer = *object.layers()[i];
if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
break;
polygons_append(polygons_trimming, (Polygons)object_layer.slices);
polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
if (! m_slicing_params.soluble_interface) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers().size(); ++ i) {
const Layer &object_layer = *object.layers()[i];
bool some_region_overlaps = false;
for (LayerRegion* region : object_layer.regions()) {
coordf_t nozzle_dmr = region->region()->nozzle_dmr_avg(*m_print_config);
if (object_layer.print_z - nozzle_dmr > support_layer.print_z + gap_extra_above - EPSILON)
for (LayerRegion *region : object_layer.regions()) {
coordf_t bridging_height = region->region()->bridging_height_avg(*this->m_print_config);
if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON)
break;
some_region_overlaps = true;
polygons_append(polygons_trimming, to_polygons(region->slices.filter_by_type(stBottomBridge)));
polygons_append(polygons_trimming,
offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)),
gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (region->region()->config().overhangs.value)
SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming);
}
if (! some_region_overlaps)
break;
@ -1675,9 +1935,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
// perimeter's width. $support contains the full shape of support
// material, thus including the width of its foremost extrusion.
// We leave a gap equal to a full extrusion width.
support_layer.polygons = diff(
support_layer.polygons,
offset(polygons_trimming, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
support_layer.polygons = diff(support_layer.polygons, polygons_trimming);
}
});
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end";
@ -1800,11 +2058,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_int
coordf_t top_z = intermediate_layers[std::min<int>(intermediate_layers.size()-1, idx_intermediate_layer + m_object_config->support_material_interface_layers - 1)]->print_z;
coordf_t bottom_z = intermediate_layers[std::max<int>(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z;
// Move idx_top_contact_first up until above the current print_z.
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; });
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
// Collect the top contact areas above this intermediate layer, below top_z.
Polygons polygons_top_contact_projected;
for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) {
const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
//FIXME maybe this adds one interface layer in excess?
if (top_contact_layer.bottom_z - EPSILON > top_z)
break;
polygons_append(polygons_top_contact_projected, top_contact_layer.polygons);
@ -1861,8 +2120,8 @@ static inline void fill_expolygons_generate_paths(
fill_params.density = density;
fill_params.complete = true;
fill_params.dont_adjust = true;
for (ExPolygons::const_iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) {
Surface surface(stInternal, *it_expolygon);
for (const ExPolygon &expoly : expolygons) {
Surface surface(stInternal, expoly);
extrusion_entities_append_paths(
dst,
filler->fill_surface(&surface, fill_params),
@ -1883,8 +2142,8 @@ static inline void fill_expolygons_generate_paths(
fill_params.density = density;
fill_params.complete = true;
fill_params.dont_adjust = true;
for (ExPolygons::iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) {
Surface surface(stInternal, std::move(*it_expolygon));
for (ExPolygon &expoly : expolygons) {
Surface surface(stInternal, std::move(expoly));
extrusion_entities_append_paths(
dst,
filler->fill_surface(&surface, fill_params),
@ -2359,7 +2618,7 @@ void modulate_extrusion_by_overlapping_layers(
(fragment_end.is_start ? &polyline.points.front() : &polyline.points.back());
}
private:
ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) = delete;
ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {}
const std::vector<ExtrusionPathFragment> &m_path_fragments;
};
const coord_t search_radius = 7;
@ -2711,6 +2970,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
continue;
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0;
//FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
Flow interface_flow(
float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)),
float(layer_ex.layer->height),

View File

@ -12,6 +12,7 @@ class PrintConfig;
class PrintObjectConfig;
// how much we extend support around the actual contact area
//FIXME this should be dependent on the nozzle diameter!
#define SUPPORT_MATERIAL_MARGIN 1.5
// This class manages raft and supports for a single PrintObject.
@ -71,6 +72,21 @@ public:
overhang_polygons = nullptr;
}
void reset() {
layer_type = sltUnknown;
print_z = 0.;
bottom_z = 0.;
height = 0.;
idx_object_layer_above = size_t(-1);
idx_object_layer_below = size_t(-1);
bridging = false;
polygons.clear();
delete contact_polygons;
contact_polygons = nullptr;
delete overhang_polygons;
overhang_polygons = nullptr;
}
bool operator==(const MyLayer &layer2) const {
return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
}

View File

@ -37,6 +37,11 @@ public:
void clear() { surfaces.clear(); }
bool empty() const { return surfaces.empty(); }
bool has(SurfaceType type) const {
for (const Surface &surface : this->surfaces)
if (surface.surface_type == type) return true;
return false;
}
void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; }
void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); }

View File

@ -0,0 +1,12 @@
#ifndef _technologies_h_
#define _technologies_h_
// 1.42.0 techs
#define ENABLE_1_42_0 1
// Add z coordinate to model instances' offset
#define ENABLE_MODELINSTANCE_3D_OFFSET (1 && ENABLE_1_42_0)
#endif // _technologies_h_

View File

@ -21,16 +21,20 @@
#include <Eigen/Dense>
// for SLIC3R_DEBUG_SLICE_PROCESSING
#include "libslic3r.h"
#if 0
#define DEBUG
#define _DEBUG
#undef NDEBUG
#define SLIC3R_DEBUG
// #define SLIC3R_TRIANGLEMESH_DEBUG
#endif
#include <assert.h>
#ifdef SLIC3R_DEBUG
// #define SLIC3R_TRIANGLEMESH_DEBUG
#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
#include "SVG.hpp"
#endif
@ -156,7 +160,6 @@ void TriangleMesh::repair()
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
}
float TriangleMesh::volume()
{
if (this->stl.stats.volume == -1)
@ -320,7 +323,7 @@ bool TriangleMesh::has_multiple_patches() const
facet_visited[facet_idx] = true;
for (int j = 0; j < 3; ++ j) {
int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
if (! facet_visited[neighbor_idx])
if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
facet_queue[facet_queue_cnt ++] = neighbor_idx;
}
}
@ -363,7 +366,7 @@ size_t TriangleMesh::number_of_patches() const
facet_visited[facet_idx] = true;
for (int j = 0; j < 3; ++ j) {
int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
if (! facet_visited[neighbor_idx])
if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
facet_queue[facet_queue_cnt ++] = neighbor_idx;
}
}
@ -623,6 +626,20 @@ void TriangleMesh::require_shared_vertices()
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices";
stl_generate_shared_vertices(&(this->stl));
}
#ifdef _DEBUG
// Verify validity of neighborship data.
for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) {
const stl_neighbors &nbr = stl.neighbors_start[facet_idx];
const int *vertices = stl.v_indices[facet_idx].vertex;
for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) {
int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx];
if (nbr_face != -1) {
assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]);
assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]);
}
}
}
#endif /* _DEBUG */
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
}
@ -782,12 +799,29 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
static int iRun = 0;
for (size_t i = 0; i < z.size(); ++ i) {
Polygons &polygons = (*layers)[i];
SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), union_ex(polygons, true));
ExPolygons expolygons = union_ex(polygons, true);
SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons);
{
BoundingBox bbox;
for (const IntersectionLine &l : lines[i]) {
bbox.merge(l.a);
bbox.merge(l.b);
}
SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox);
svg.draw(expolygons);
for (const IntersectionLine &l : lines[i])
svg.draw(l, "red", 0);
svg.draw_outline(expolygons, "black", "blue", 0);
svg.Close();
}
#if 0
//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
for (Polygon &poly : polygons) {
for (size_t i = 1; i < poly.points.size(); ++ i)
assert(poly.points[i-1] != poly.points[i]);
assert(poly.points.front() != poly.points.back());
}
#endif
}
++ iRun;
}
@ -803,32 +837,34 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLin
const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2)));
const float max_z = fmaxf(facet.vertex[0](2), fmaxf(facet.vertex[1](2), facet.vertex[2](2)));
#ifdef SLIC3R_DEBUG
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx,
facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0](2),
facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1](2),
facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2](2));
printf("z: min = %.2f, max = %.2f\n", min_z, max_z);
#endif
#endif /* SLIC3R_TRIANGLEMESH_DEBUG */
// find layer extents
std::vector<float>::const_iterator min_layer, max_layer;
min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z
max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z
#ifdef SLIC3R_DEBUG
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin()));
#endif
#endif /* SLIC3R_TRIANGLEMESH_DEBUG */
for (std::vector<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) {
std::vector<float>::size_type layer_idx = it - z.begin();
IntersectionLine il;
if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il)) {
if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) {
boost::lock_guard<boost::mutex> l(*lines_mutex);
if (il.edge_type == feHorizontal) {
// Insert all three edges of the face.
// Insert all marked edges of the face. The marked edges do not share an edge with another horizontal face
// (they may not have a nighbor, or their neighbor is vertical)
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
const bool reverse = this->mesh->stl.facet_start[facet_idx].normal(2) < 0;
for (int j = 0; j < 3; ++ j) {
for (int j = 0; j < 3; ++ j)
if (il.flags & ((IntersectionLine::EDGE0_NO_NEIGHBOR | IntersectionLine::EDGE0_FOLD) << j)) {
int a_id = vertices[j % 3];
int b_id = vertices[(j+1) % 3];
if (reverse)
@ -841,6 +877,9 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLin
il.b(1) = b(1);
il.a_id = a_id;
il.b_id = b_id;
assert(il.a != il.b);
// This edge will not be used as a seed for loop extraction if it was added due to a fold of two overlapping horizontal faces.
il.set_no_seed((IntersectionLine::EDGE0_FOLD << j) != 0);
(*lines)[layer_idx].emplace_back(il);
}
} else
@ -871,23 +910,22 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygo
}
// Return true, if the facet has been sliced and line_out has been filled.
bool TriangleMeshSlicer::slice_facet(
TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
float slice_z, const stl_facet &facet, const int facet_idx,
const float min_z, const float max_z,
IntersectionLine *line_out) const
{
IntersectionPoint points[3];
size_t num_points = 0;
size_t points_on_layer[3];
size_t num_points_on_layer = 0;
size_t point_on_layer = size_t(-1);
// Reorder vertices so that the first one is the one with lowest Z.
// This is needed to get all intersection lines in a consistent order
// (external on the right of the line)
int i = (facet.vertex[1](2) == min_z) ? 1 : ((facet.vertex[2](2) == min_z) ? 2 : 0);
for (int j = i; j - i < 3; ++ j) { // loop through facet edges
int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)];
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
int i = (facet.vertex[1](2) == min_z) ? 1 : ((facet.vertex[2](2) == min_z) ? 2 : 0);
for (int j = i; j - i < 3; ++j ) { // loop through facet edges
int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)];
int a_id = vertices[j % 3];
int b_id = vertices[(j+1) % 3];
const stl_vertex &a = this->v_scaled_shared[a_id];
@ -900,116 +938,279 @@ bool TriangleMeshSlicer::slice_facet(
const stl_vertex &v1 = this->v_scaled_shared[vertices[1]];
const stl_vertex &v2 = this->v_scaled_shared[vertices[2]];
bool swap = false;
const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal;
// We may ignore this edge for slicing purposes, but we may still use it for object cutting.
FacetSliceType result = Slicing;
const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx];
if (min_z == max_z) {
// All three vertices are aligned with slice_z.
line_out->edge_type = feHorizontal;
if (this->mesh->stl.facet_start[facet_idx].normal(2) < 0) {
// Mark neighbor edges, which do not have a neighbor.
uint32_t edges = 0;
for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx) {
// If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no
// opposite face, add it to the edges to process when slicing.
if (nbr.neighbor[nbr_idx] == -1) {
// Mark this edge to be added to the slice.
edges |= (IntersectionLine::EDGE0_NO_NEIGHBOR << nbr_idx);
}
#if 1
else if (normal(2) > 0) {
// Produce edges for opposite faced overlapping horizontal faces aka folds.
// This method often produces connecting lines (noise) at the cutting plane.
// Produce the edges for the top facing face of the pair of top / bottom facing faces.
// Index of a neighbor face.
const int nbr_face = nbr.neighbor[nbr_idx];
const int *nbr_vertices = this->mesh->stl.v_indices[nbr_face].vertex;
int idx_vertex_opposite = nbr_vertices[nbr.which_vertex_not[nbr_idx]];
const stl_vertex &c2 = this->v_scaled_shared[idx_vertex_opposite];
if (c2(2) == slice_z) {
// Edge shared by facet_idx and nbr_face.
int a_id = vertices[nbr_idx];
int b_id = vertices[(nbr_idx + 1) % 3];
int c1_id = vertices[(nbr_idx + 2) % 3];
const stl_vertex &a = this->v_scaled_shared[a_id];
const stl_vertex &b = this->v_scaled_shared[b_id];
const stl_vertex &c1 = this->v_scaled_shared[c1_id];
// Verify that the two neighbor faces share a common edge.
assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id);
assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id);
double n1 = (double(c1(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c1(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
double n2 = (double(c2(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c2(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
if (n1 * n2 > 0)
// The two faces overlap. This indicates an invalid mesh geometry (non-manifold),
// but these are the real world objects, and leaving out these edges leads to missing contours.
edges |= (IntersectionLine::EDGE0_FOLD << nbr_idx);
}
}
#endif
}
// Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face.
result = (edges == 0) ? Cutting : Slicing;
line_out->flags |= edges;
if (normal(2) < 0) {
// If normal points downwards this is a bottom horizontal facet so we reverse its point order.
swap = true;
}
} else if (v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z) {
// Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
} else {
// Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane.
int nbr_idx = j % 3;
int nbr_face = nbr.neighbor[nbr_idx];
// Is the third vertex below the cutting plane?
bool third_below = v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z;
// Is this a concave corner?
if (nbr_face == -1) {
#ifdef _DEBUG
printf("Face has no neighbor!\n");
#endif
} else {
assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id);
assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id);
int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]];
const stl_vertex &c = this->v_scaled_shared[idx_vertex_opposite];
if (c(2) == slice_z) {
double normal_nbr = (double(c(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
#if 0
if ((normal_nbr < 0) == third_below) {
printf("Flipped normal?\n");
}
#endif
result =
// A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner.
// Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge,
// and leth the code downstream hopefully handle it.
#if 1
// Ignore concave corners for slicing.
// This method has the unfortunate property, that folds in a horizontal plane create concave corners,
// leading to broken contours, if these concave corners are not replaced by edges of the folds, see above.
((normal_nbr < 0) == third_below) ? Cutting : Slicing;
#else
// Use concave corners for slicing. This leads to the test 01_trianglemesh.t "slicing a top tangent plane includes its area" failing,
// and rightly so.
Slicing;
#endif
} else {
// For a pair of faces touching exactly at the cutting plane, ignore one of them. An arbitrary rule is to ignore the face with a higher index.
result = (facet_idx < nbr_face) ? Slicing : Cutting;
}
}
if (third_below) {
line_out->edge_type = feTop;
swap = true;
} else {
// Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
} else
line_out->edge_type = feBottom;
}
line_out->a = to_2d(swap ? b : a).cast<coord_t>();
line_out->b = to_2d(swap ? a : b).cast<coord_t>();
line_out->a_id = swap ? b_id : a_id;
line_out->b_id = swap ? a_id : b_id;
return true;
assert(line_out->a != line_out->b);
return result;
}
if (a(2) == slice_z) {
// Only point a alings with the cutting plane.
points_on_layer[num_points_on_layer ++] = num_points;
if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
point_on_layer = num_points;
IntersectionPoint &point = points[num_points ++];
point(0) = a(0);
point(1) = a(1);
point.point_id = a_id;
}
} else if (b(2) == slice_z) {
// Only point b alings with the cutting plane.
points_on_layer[num_points_on_layer ++] = num_points;
if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
point_on_layer = num_points;
IntersectionPoint &point = points[num_points ++];
point(0) = b(0);
point(1) = b(1);
point.point_id = b_id;
}
} else if ((a(2) < slice_z && b(2) > slice_z) || (b(2) < slice_z && a(2) > slice_z)) {
// A general case. The face edge intersects the cutting plane. Calculate the intersection point.
IntersectionPoint &point = points[num_points ++];
point(0) = b(0) + (a(0) - b(0)) * (slice_z - b(2)) / (a(2) - b(2));
point(1) = b(1) + (a(1) - b(1)) * (slice_z - b(2)) / (a(2) - b(2));
assert(a_id != b_id);
// Sort the edge to give a consistent answer.
const stl_vertex *pa = &a;
const stl_vertex *pb = &b;
if (a_id > b_id) {
std::swap(a_id, b_id);
std::swap(pa, pb);
}
IntersectionPoint &point = points[num_points];
double t = (double(slice_z) - double((*pb)(2))) / (double((*pa)(2)) - double((*pb)(2)));
if (t <= 0.) {
if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
point(0) = (*pa)(0);
point(1) = (*pa)(1);
point_on_layer = num_points ++;
point.point_id = a_id;
}
} else if (t >= 1.) {
if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
point(0) = (*pb)(0);
point(1) = (*pb)(1);
point_on_layer = num_points ++;
point.point_id = b_id;
}
} else {
point(0) = coord_t(floor(double((*pb)(0)) + (double((*pa)(0)) - double((*pb)(0))) * t + 0.5));
point(1) = coord_t(floor(double((*pb)(1)) + (double((*pa)(1)) - double((*pb)(1))) * t + 0.5));
point.edge_id = edge_id;
++ num_points;
}
}
}
// We can't have only one point on layer because each vertex gets detected
// twice (once for each edge), and we can't have three points on layer,
// because we assume this code is not getting called for horizontal facets.
assert(num_points_on_layer == 0 || num_points_on_layer == 2);
if (num_points_on_layer > 0) {
assert(points[points_on_layer[0]].point_id == points[points_on_layer[1]].point_id);
assert(num_points == 2 || num_points == 3);
if (num_points < 3)
// This triangle touches the cutting plane with a single vertex. Ignore it.
return false;
// Erase one of the duplicate points.
-- num_points;
for (int i = points_on_layer[1]; i < num_points; ++ i)
points[i] = points[i + 1];
}
// Facets must intersect each plane 0 or 2 times.
assert(num_points == 0 || num_points == 2);
// Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only.
assert(num_points < 3);
if (num_points == 2) {
line_out->edge_type = feNone;
line_out->edge_type = feGeneral;
line_out->a = (Point)points[1];
line_out->b = (Point)points[0];
line_out->a_id = points[1].point_id;
line_out->b_id = points[0].point_id;
line_out->edge_a_id = points[1].edge_id;
line_out->edge_b_id = points[0].edge_id;
return true;
// Not a zero lenght edge.
//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
//assert(line_out->a != line_out->b);
// The plane cuts at least one edge in a general position.
assert(line_out->a_id == -1 || line_out->b_id == -1);
assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1);
// General slicing position, use the segment for both slicing and object cutting.
#if 0
if (line_out->a_id != -1 && line_out->b_id != -1) {
// Solving a degenerate case, where both the intersections snapped to an edge.
// Correctly classify the face as below or above based on the position of the 3rd point.
int i = vertices[0];
if (i == line_out->a_id || i == line_out->b_id)
i = vertices[1];
if (i == line_out->a_id || i == line_out->b_id)
i = vertices[2];
assert(i != line_out->a_id && i != line_out->b_id);
line_out->edge_type = (this->v_scaled_shared[i].z < slice_z) ? feTop : feBottom;
}
#endif
return Slicing;
}
return NoSlice;
}
//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing
// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces.
// So the following code makes only sense now to handle degenerate meshes with more than two faces
// sharing a single edge.
static inline void remove_tangent_edges(std::vector<IntersectionLine> &lines)
{
std::vector<IntersectionLine*> by_vertex_pair;
by_vertex_pair.reserve(lines.size());
for (IntersectionLine& line : lines)
if (line.edge_type != feGeneral && line.a_id != -1)
// This is a face edge. Check whether there is its neighbor stored in lines.
by_vertex_pair.emplace_back(&line);
auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) {
// Sort vertices of l1, l2 lexicographically
int l1a = l1->a_id;
int l1b = l1->b_id;
int l2a = l2->a_id;
int l2b = l2->b_id;
if (l1a > l1b)
std::swap(l1a, l1b);
if (l2a > l2b)
std::swap(l2a, l2b);
// Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored.
return l1a < l2a || (l1a == l2a && l1b < l2b);
};
std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted);
for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) {
IntersectionLine &l1 = **line;
if (! l1.skip()) {
// Iterate as long as line and line2 edges share the same end points.
for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) {
// Lines must share the end points.
assert(! edges_lower_sorted(*line, *line2));
assert(! edges_lower_sorted(*line2, *line));
IntersectionLine &l2 = **line2;
if (l2.skip())
continue;
if (l1.a_id == l2.a_id) {
assert(l1.b_id == l2.b_id);
l2.set_skip();
// If they are both oriented upwards or downwards (like a 'V'),
// then we can remove both edges from this layer since it won't
// affect the sliced shape.
// If one of them is oriented upwards and the other is oriented
// downwards, let's only keep one of them (it doesn't matter which
// one since all 'top' lines were reversed at slicing).
if (l1.edge_type == l2.edge_type) {
l1.set_skip();
break;
}
} else {
assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id);
// If this edge joins two horizontal facets, remove both of them.
if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) {
l1.set_skip();
l2.set_skip();
break;
}
}
}
}
}
return false;
}
void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const
{
// Remove tangent edges.
//FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane.
for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line)
if (! line->skip && line->edge_type != feNone) {
// This line is af facet edge. There may be a duplicate line with the same end vertices.
// If the line is is an edge connecting two facets, find another facet edge
// having the same endpoints but in reverse order.
for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2)
if (! line2->skip && line2->edge_type != feNone) {
// Are these facets adjacent? (sharing a common edge on this layer)
if (line->a_id == line2->a_id && line->b_id == line2->b_id) {
line2->skip = true;
/* if they are both oriented upwards or downwards (like a 'V')
then we can remove both edges from this layer since it won't
affect the sliced shape */
/* if one of them is oriented upwards and the other is oriented
downwards, let's only keep one of them (it doesn't matter which
one since all 'top' lines were reversed at slicing) */
if (line->edge_type == line2->edge_type) {
line->skip = true;
break;
}
} else if (line->a_id == line2->b_id && line->b_id == line2->a_id) {
/* if this edge joins two horizontal facets, remove both of them */
if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) {
line->skip = true;
line2->skip = true;
break;
}
}
}
}
#if 0
//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
//#ifdef _DEBUG
for (const Line &l : lines)
assert(l.a != l.b);
#endif /* _DEBUG */
remove_tangent_edges(lines);
struct OpenPolyline {
OpenPolyline() {};
@ -1032,7 +1233,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
by_edge_a_id.reserve(lines.size());
by_a_id.reserve(lines.size());
for (IntersectionLine &line : lines) {
if (! line.skip) {
if (! line.skip()) {
if (line.edge_a_id != -1)
by_edge_a_id.emplace_back(&line);
if (line.a_id != -1)
@ -1049,13 +1250,14 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
// take first spare line and start a new loop
IntersectionLine *first_line = nullptr;
for (; it_line_seed != lines.end(); ++ it_line_seed)
if (! it_line_seed->skip) {
if (it_line_seed->is_seed_candidate()) {
//if (! it_line_seed->skip()) {
first_line = &(*it_line_seed ++);
break;
}
if (first_line == nullptr)
break;
first_line->skip = true;
first_line->set_skip();
Points loop_pts;
loop_pts.emplace_back(first_line->a);
IntersectionLine *last_line = first_line;
@ -1076,7 +1278,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
if (it_begin != by_edge_a_id.end()) {
auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower);
for (auto it_line = it_begin; it_line != it_end; ++ it_line)
if (! (*it_line)->skip) {
if (! (*it_line)->skip()) {
next_line = *it_line;
break;
}
@ -1088,7 +1290,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
if (it_begin != by_a_id.end()) {
auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower);
for (auto it_line = it_begin; it_line != it_end; ++ it_line)
if (! (*it_line)->skip) {
if (! (*it_line)->skip()) {
next_line = *it_line;
break;
}
@ -1119,7 +1321,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
*/
loop_pts.emplace_back(next_line->a);
last_line = next_line;
next_line->skip = true;
next_line->set_skip();
}
}
}
@ -1211,8 +1413,8 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) ||
(ip1.point_id != -1 && ip1.point_id == ip2.point_id)) {
// The current loop is complete. Add it to the output.
/*assert(opl.points.front().point_id == opl.points.back().point_id);
assert(opl.points.front().edge_id == opl.points.back().edge_id);*/
//assert(opl.points.front().point_id == opl.points.back().point_id);
//assert(opl.points.front().edge_id == opl.points.back().edge_id);
// Remove the duplicate last point.
opl.points.pop_back();
if (opl.points.size() >= 3) {
@ -1412,7 +1614,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
// intersect facet with cutting plane
IntersectionLine line;
if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line)) {
if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) {
// Save intersection lines for generating correct triangulations.
if (line.edge_type == feTop) {
lower_lines.emplace_back(line);

View File

@ -83,7 +83,7 @@ private:
enum FacetEdgeType {
// A general case, the cutting plane intersect a face at two different edges.
feNone,
feGeneral,
// Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
feTop,
// Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
@ -117,6 +117,14 @@ public:
class IntersectionLine : public Line
{
public:
IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {}
bool skip() const { return (this->flags & SKIP) != 0; }
void set_skip() { this->flags |= SKIP; }
bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); }
void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; }
// Inherits Point a, b
// For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1.
// Vertex indices of the line end points.
@ -125,11 +133,23 @@ public:
// Source mesh edges of the line end points.
int edge_a_id;
int edge_b_id;
// feNone, feTop, feBottom, feHorizontal
// feGeneral, feTop, feBottom, feHorizontal
FacetEdgeType edge_type;
// Used by TriangleMeshSlicer::make_loops() to skip duplicate edges.
bool skip;
IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {};
// Used by TriangleMeshSlicer::slice() to skip duplicate edges.
enum {
// Triangle edge added, because it has no neighbor.
EDGE0_NO_NEIGHBOR = 0x001,
EDGE1_NO_NEIGHBOR = 0x002,
EDGE2_NO_NEIGHBOR = 0x004,
// Triangle edge added, because it makes a fold with another horizontal edge.
EDGE0_FOLD = 0x010,
EDGE1_FOLD = 0x020,
EDGE2_FOLD = 0x040,
// The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds).
NO_SEED = 0x100,
SKIP = 0x200,
};
uint32_t flags;
};
typedef std::vector<IntersectionLine> IntersectionLines;
typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
@ -144,7 +164,12 @@ public:
void init(TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel);
void slice(const std::vector<float> &z, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
bool slice_facet(float slice_z, const stl_facet &facet, const int facet_idx,
enum FacetSliceType {
NoSlice = 0,
Slicing = 1,
Cutting = 2
};
FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx,
const float min_z, const float max_z, IntersectionLine *line_out) const;
void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const;

View File

@ -9,6 +9,7 @@ namespace Slic3r {
extern void set_logging_level(unsigned int level);
extern void trace(unsigned int level, const char *message);
extern void disable_multi_threading();
// Set a path with GUI resource files.
void set_var_dir(const std::string &path);

View File

@ -13,6 +13,8 @@
#include <vector>
#include <boost/thread.hpp>
#include "Technologies.hpp"
#define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
#define SLIC3R_VERSION "1.41.0"
#define SLIC3R_BUILD "UNKNOWN"

View File

@ -25,6 +25,8 @@
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <tbb/task_scheduler_init.h>
namespace Slic3r {
static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error;
@ -83,6 +85,14 @@ void trace(unsigned int level, const char *message)
(::boost::log::keywords::severity = severity)) << message;
}
void disable_multi_threading()
{
// Disable parallelization so the Shiny profiler works
static tbb::task_scheduler_init *tbb_init = nullptr;
if (tbb_init == nullptr)
tbb_init = new tbb::task_scheduler_init(1);
}
static std::string g_var_dir;
void set_var_dir(const std::string &dir)

View File

@ -11,12 +11,11 @@
#include <ModelArrange.hpp>
#include <slic3r/GUI/PresetBundle.hpp>
#include <Print.hpp>
#include <PrintExport.hpp>
#include <Geometry.hpp>
#include <PrintConfig.hpp>
#include <Print.hpp>
#include <Model.hpp>
#include <Utils.hpp>
#include <SLABasePool.hpp>
namespace Slic3r {
@ -44,15 +43,6 @@ namespace GUI {
PresetBundle* get_preset_bundle();
}
static const PrintObjectStep STEP_SLICE = posSlice;
static const PrintObjectStep STEP_PERIMETERS = posPerimeters;
static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill;
static const PrintObjectStep STEP_INFILL = posInfill;
static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial;
static const PrintStep STEP_SKIRT = psSkirt;
static const PrintStep STEP_BRIM = psBrim;
static const PrintStep STEP_WIPE_TOWER = psWipeTower;
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::global_progress_indicator() {
ProgresIndicatorPtr ret;
@ -72,343 +62,8 @@ void AppControllerBoilerplate::global_progress_indicator(
pri_data_->m.unlock();
}
#if 0
void PrintController::make_skirt()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects()) make_perimeters(obj);
for(auto obj : print_->objects()) infill(obj);
for(auto obj : print_->objects()) gen_support_material(obj);
if(!print_->state.is_done(STEP_SKIRT)) {
print_->state.set_started(STEP_SKIRT);
print_->skirt.clear();
if(print_->has_skirt()) print_->_make_skirt();
print_->state.set_done(STEP_SKIRT);
}
}
void PrintController::make_brim()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects()) make_perimeters(obj);
for(auto obj : print_->objects()) infill(obj);
for(auto obj : print_->objects()) gen_support_material(obj);
make_skirt();
if(!print_->state.is_done(STEP_BRIM)) {
print_->state.set_started(STEP_BRIM);
// since this method must be idempotent, we clear brim paths *before*
// checking whether we need to generate them
print_->brim.clear();
if(print_->config.brim_width > 0) print_->_make_brim();
print_->state.set_done(STEP_BRIM);
}
}
void PrintController::make_wipe_tower()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects()) make_perimeters(obj);
for(auto obj : print_->objects()) infill(obj);
for(auto obj : print_->objects()) gen_support_material(obj);
make_skirt();
make_brim();
if(!print_->state.is_done(STEP_WIPE_TOWER)) {
print_->state.set_started(STEP_WIPE_TOWER);
// since this method must be idempotent, we clear brim paths *before*
// checking whether we need to generate them
print_->brim.clear();
if(print_->has_wipe_tower()) print_->_make_wipe_tower();
print_->state.set_done(STEP_WIPE_TOWER);
}
}
void PrintController::slice(PrintObject *pobj)
{
assert(pobj != nullptr && print_ != nullptr);
if(pobj->state.is_done(STEP_SLICE)) return;
pobj->state.set_started(STEP_SLICE);
pobj->_slice();
auto msg = pobj->_fix_slicing_errors();
if(!msg.empty()) report_issue(IssueType::WARN, msg);
// simplify slices if required
if (print_->config.resolution)
pobj->_simplify_slices(scale_(print_->config.resolution));
if(pobj->layers.empty())
report_issue(IssueType::ERR,
_(L("No layers were detected. You might want to repair your "
"STL file(s) or check their size or thickness and retry"))
);
pobj->state.set_done(STEP_SLICE);
}
void PrintController::make_perimeters(PrintObject *pobj)
{
assert(pobj != nullptr);
slice(pobj);
if (!pobj->state.is_done(STEP_PERIMETERS)) {
pobj->_make_perimeters();
}
}
void PrintController::infill(PrintObject *pobj)
{
assert(pobj != nullptr);
make_perimeters(pobj);
if (!pobj->state.is_done(STEP_PREPARE_INFILL)) {
pobj->state.set_started(STEP_PREPARE_INFILL);
pobj->_prepare_infill();
pobj->state.set_done(STEP_PREPARE_INFILL);
}
pobj->_infill();
}
void PrintController::gen_support_material(PrintObject *pobj)
{
assert(pobj != nullptr);
// prerequisites
slice(pobj);
if(!pobj->state.is_done(STEP_SUPPORTMATERIAL)) {
pobj->state.set_started(STEP_SUPPORTMATERIAL);
pobj->clear_support_layers();
if((pobj->config.support_material || pobj->config.raft_layers > 0)
&& pobj->layers.size() > 1) {
pobj->_generate_support_material();
}
pobj->state.set_done(STEP_SUPPORTMATERIAL);
}
}
PrintController::PngExportData
PrintController::query_png_export_data(const DynamicPrintConfig& conf)
{
PngExportData ret;
auto zippath = query_destination_path("Output zip file", "*.zip", "out");
ret.zippath = zippath;
ret.width_mm = conf.opt_float("display_width");
ret.height_mm = conf.opt_float("display_height");
ret.width_px = conf.opt_int("display_pixels_x");
ret.height_px = conf.opt_int("display_pixels_y");
auto opt_corr = conf.opt<ConfigOptionFloats>("printer_correction");
if(opt_corr) {
ret.corr_x = opt_corr->values[0];
ret.corr_y = opt_corr->values[1];
ret.corr_z = opt_corr->values[2];
}
ret.exp_time_first_s = conf.opt_float("initial_exposure_time");
ret.exp_time_s = conf.opt_float("exposure_time");
return ret;
}
void PrintController::slice(AppControllerBoilerplate::ProgresIndicatorPtr pri)
{
auto st = pri->state();
Slic3r::trace(3, "Starting the slicing process.");
pri->update(st+20, _(L("Generating perimeters")));
for(auto obj : print_->objects()) make_perimeters(obj);
pri->update(st+60, _(L("Infilling layers")));
for(auto obj : print_->objects()) infill(obj);
pri->update(st+70, _(L("Generating support material")));
for(auto obj : print_->objects()) gen_support_material(obj);
pri->message_fmt(_(L("Weight: %.1fg, Cost: %.1f")),
print_->total_weight, print_->total_cost);
pri->state(st+85);
pri->update(st+88, _(L("Generating skirt")));
make_skirt();
pri->update(st+90, _(L("Generating brim")));
make_brim();
pri->update(st+95, _(L("Generating wipe tower")));
make_wipe_tower();
pri->update(st+100, _(L("Done")));
// time to make some statistics..
Slic3r::trace(3, _(L("Slicing process finished.")));
}
void PrintController::slice()
{
auto pri = global_progress_indicator();
if(!pri) pri = create_progress_indicator(100, L("Slicing"));
slice(pri);
}
void PrintController::slice_to_png()
{
using Pointf3 = Vec3d;
auto presetbundle = GUI::get_preset_bundle();
assert(presetbundle);
auto pt = presetbundle->printers.get_selected_preset().printer_technology();
if(pt != ptSLA) {
report_issue(IssueType::ERR, _("Printer technology is not SLA!"),
_("Error"));
return;
}
auto conf = presetbundle->full_config();
conf.validate();
auto exd = query_png_export_data(conf);
if(exd.zippath.empty()) return;
try {
print_->apply_config(conf);
print_->validate();
} catch(std::exception& e) {
report_issue(IssueType::ERR, e.what(), "Error");
return;
}
// TODO: copy the model and work with the copy only
bool correction = false;
if(exd.corr_x != 1.0 || exd.corr_y != 1.0 || exd.corr_z != 1.0) {
correction = true;
print_->invalidate_all_steps();
for(auto po : print_->objects()) {
po->model_object()->scale(
Pointf3(exd.corr_x, exd.corr_y, exd.corr_z)
);
po->model_object()->invalidate_bounding_box();
po->reload_model_instances();
po->invalidate_all_steps();
}
}
// Turn back the correction scaling on the model.
auto scale_back = [this, correction, exd]() {
if(correction) { // scale the model back
print_->invalidate_all_steps();
for(auto po : print_->objects()) {
po->model_object()->scale(
Pointf3(1.0/exd.corr_x, 1.0/exd.corr_y, 1.0/exd.corr_z)
);
po->model_object()->invalidate_bounding_box();
po->reload_model_instances();
po->invalidate_all_steps();
}
}
};
auto print_bb = print_->bounding_box();
Vec2d punsc = unscale(print_bb.size());
// If the print does not fit into the print area we should cry about it.
if(px(punsc) > exd.width_mm || py(punsc) > exd.height_mm) {
std::stringstream ss;
ss << _(L("Print will not fit and will be truncated!")) << "\n"
<< _(L("Width needed: ")) << px(punsc) << " mm\n"
<< _(L("Height needed: ")) << py(punsc) << " mm\n";
if(!report_issue(IssueType::WARN_Q, ss.str(), _(L("Warning")))) {
scale_back();
return;
}
}
// std::async(supports_asynch()? std::launch::async : std::launch::deferred,
// [this, exd, scale_back]()
// {
auto pri = create_progress_indicator(
200, _(L("Slicing to zipped png files...")));
try {
pri->update(0, _(L("Slicing...")));
slice(pri);
} catch (std::exception& e) {
pri->cancel();
report_issue(IssueType::ERR, e.what(), _(L("Exception occured")));
scale_back();
return;
}
auto pbak = print_->progressindicator;
print_->progressindicator = pri;
try {
print_to<FilePrinterFormat::PNG>( *print_, exd.zippath,
exd.width_mm, exd.height_mm,
exd.width_px, exd.height_px,
exd.exp_time_s, exd.exp_time_first_s);
} catch (std::exception& e) {
pri->cancel();
report_issue(IssueType::ERR, e.what(), _(L("Exception occured")));
}
print_->progressindicator = pbak;
scale_back();
// });
}
const PrintConfig &PrintController::config() const
{
return print_->config;
}
#endif
void ProgressIndicator::message_fmt(
const wxString &fmtstr, ...) {
const std::string &fmtstr, ...) {
std::stringstream ss;
va_list args;
va_start(args, fmtstr);
@ -438,6 +93,11 @@ void AppController::arrange_model()
{
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
if(arranging_.load()) return;
// to prevent UI reentrancies
arranging_.store(true);
unsigned count = 0;
for(auto obj : model_->objects) count += obj->instances.size();
@ -451,8 +111,8 @@ void AppController::arrange_model()
// Set the range of the progress to the object count
pind->max(count);
pind->on_cancel([](){
std::cout << "Cannot be cancelled!" << std::endl;
pind->on_cancel([this](){
arranging_.store(false);
});
}
@ -466,7 +126,7 @@ void AppController::arrange_model()
for(auto& v : bedpoints)
bed.append(Point::new_scale(v(0), v(1)));
if(pind) pind->update(0, _(L("Arranging objects...")));
if(pind) pind->update(0, L("Arranging objects..."));
try {
arr::BedShapeHint hint;
@ -478,24 +138,30 @@ void AppController::arrange_model()
bed,
hint,
false, // create many piles not just one pile
[pind, count](unsigned rem) {
[this, pind, count](unsigned rem) {
if(pind)
pind->update(count - rem, _(L("Arranging objects...")));
});
pind->update(count - rem, L("Arranging objects..."));
process_events();
}, [this] () { return !arranging_.load(); });
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
report_issue(IssueType::ERR,
_(L("Could not arrange model objects! "
"Some geometries may be invalid.")),
_(L("Exception occurred")));
L("Could not arrange model objects! "
"Some geometries may be invalid."),
L("Exception occurred"));
}
// Restore previous max value
if(pind) {
pind->max(pmax);
pind->update(0, _(L("Arranging done.")));
pind->update(0, arranging_.load() ? L("Arranging done.") :
L("Arranging canceled."));
pind->on_cancel(/*remove cancel function*/);
}
arranging_.store(false);
}
}

View File

@ -18,6 +18,7 @@ class Print;
class PrintObject;
class PrintConfig;
class ProgressStatusBar;
class DynamicPrintConfig;
/**
* @brief A boilerplate class for creating application logic. It should provide
@ -48,7 +49,7 @@ public:
AppControllerBoilerplate();
~AppControllerBoilerplate();
using Path = wxString;
using Path = std::string;
using PathList = std::vector<Path>;
/// Common runtime issue types
@ -69,20 +70,20 @@ public:
* @return Returns a list of paths choosed by the user.
*/
PathList query_destination_paths(
const wxString& title,
const std::string& title,
const std::string& extensions) const;
/**
* @brief Same as query_destination_paths but works for directories only.
*/
PathList query_destination_dirs(
const wxString& title) const;
const std::string& title) const;
/**
* @brief Same as query_destination_paths but returns only one path.
*/
Path query_destination_path(
const wxString& title,
const std::string& title,
const std::string& extensions,
const std::string& hint = "") const;
@ -97,11 +98,11 @@ public:
* title.
*/
bool report_issue(IssueType issuetype,
const wxString& description,
const wxString& brief);
const std::string& description,
const std::string& brief);
bool report_issue(IssueType issuetype,
const wxString& description);
const std::string& description);
/**
* @brief Return the global progress indicator for the current controller.
@ -152,12 +153,12 @@ protected:
*/
ProgresIndicatorPtr create_progress_indicator(
unsigned statenum,
const wxString& title,
const wxString& firstmsg) const;
const std::string& title,
const std::string& firstmsg) const;
ProgresIndicatorPtr create_progress_indicator(
unsigned statenum,
const wxString& title) const;
const std::string& title) const;
// This is a global progress indicator placeholder. In the Slic3r UI it can
// contain the progress indicator on the statusbar.
@ -170,43 +171,6 @@ protected:
*/
class PrintController: public AppControllerBoilerplate {
Print *print_ = nullptr;
protected:
void make_skirt();
void make_brim();
void make_wipe_tower();
void make_perimeters(PrintObject *pobj);
void infill(PrintObject *pobj);
void gen_support_material(PrintObject *pobj);
// Data structure with the png export input data
struct PngExportData {
std::string zippath; // output zip file
unsigned long width_px = 1440; // resolution - rows
unsigned long height_px = 2560; // resolution columns
double width_mm = 68.0, height_mm = 120.0; // dimensions in mm
double exp_time_first_s = 35.0; // first exposure time
double exp_time_s = 8.0; // global exposure time
double corr_x = 1.0; // offsetting in x
double corr_y = 1.0; // offsetting in y
double corr_z = 1.0; // offsetting in y
};
// Should display a dialog with the input fields for printing to png
PngExportData query_png_export_data(const DynamicPrintConfig&);
// The previous export data, to pre-populate the dialog
PngExportData prev_expdata_;
/**
* @brief Slice one pront object.
* @param pobj The print object.
*/
void slice(PrintObject *pobj);
void slice(ProgresIndicatorPtr pri);
public:
// Must be public for perl to use it
@ -221,15 +185,8 @@ public:
return PrintController::Ptr( new PrintController(print) );
}
/**
* @brief Slice the loaded print scene.
*/
void slice();
/**
* @brief Slice the print into zipped png files.
*/
void slice_to_png();
void slice() {}
void slice_to_png() {}
const PrintConfig& config() const;
};
@ -253,6 +210,7 @@ public:
class AppController: public AppControllerBoilerplate {
Model *model_ = nullptr;
PrintController::Ptr printctl;
std::atomic<bool> arranging_;
public:
/**
@ -288,7 +246,7 @@ public:
* In perl we have a progress indicating status bar on the bottom of the
* window which is defined and created in perl. We can pass the ID-s of the
* gauge and the statusbar id and make a wrapper implementation of the
* IProgressIndicator interface so we can use this GUI widget from C++.
* ProgressIndicator interface so we can use this GUI widget from C++.
*
* This function should be called from perl.
*

View File

@ -28,16 +28,16 @@ bool AppControllerBoilerplate::supports_asynch() const
void AppControllerBoilerplate::process_events()
{
wxSafeYield();
wxYieldIfNeeded();
}
AppControllerBoilerplate::PathList
AppControllerBoilerplate::query_destination_paths(
const wxString &title,
const std::string &title,
const std::string &extensions) const
{
wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
dlg.SetWildcard(extensions);
dlg.ShowModal();
@ -53,11 +53,11 @@ AppControllerBoilerplate::query_destination_paths(
AppControllerBoilerplate::Path
AppControllerBoilerplate::query_destination_path(
const wxString &title,
const std::string &title,
const std::string &extensions,
const std::string& hint) const
{
wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
dlg.SetWildcard(extensions);
dlg.SetFilename(hint);
@ -72,8 +72,8 @@ AppControllerBoilerplate::query_destination_path(
}
bool AppControllerBoilerplate::report_issue(IssueType issuetype,
const wxString &description,
const wxString &brief)
const std::string &description,
const std::string &brief)
{
auto icon = wxICON_INFORMATION;
auto style = wxOK|wxCENTRE;
@ -85,15 +85,15 @@ bool AppControllerBoilerplate::report_issue(IssueType issuetype,
case IssueType::FATAL: icon = wxICON_ERROR;
}
auto ret = wxMessageBox(description, brief, icon | style);
auto ret = wxMessageBox(_(description), _(brief), icon | style);
return ret != wxCANCEL;
}
bool AppControllerBoilerplate::report_issue(
AppControllerBoilerplate::IssueType issuetype,
const wxString &description)
const std::string &description)
{
return report_issue(issuetype, description, wxString());
return report_issue(issuetype, description, std::string());
}
wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
@ -126,7 +126,9 @@ class GuiProgressIndicator:
void _state( unsigned st) {
if(!gauge_.IsShown()) gauge_.ShowModal();
Base::state(st);
gauge_.Update(static_cast<int>(st), message_);
if(!gauge_.Update(static_cast<int>(st), message_)) {
cancel();
}
}
public:
@ -140,7 +142,8 @@ public:
inline GuiProgressIndicator(int range, const wxString& title,
const wxString& firstmsg) :
gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(),
wxPD_APP_MODAL | wxPD_AUTO_HIDE),
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT),
message_(firstmsg),
range_(range), title_(title)
{
@ -152,11 +155,6 @@ public:
this, id_);
}
virtual void cancel() override {
update(max(), "Abort");
ProgressIndicator::cancel();
}
virtual void state(float val) override {
state(static_cast<unsigned>(val));
}
@ -171,26 +169,28 @@ public:
} else _state(st);
}
virtual void message(const wxString & msg) override {
message_ = msg;
virtual void message(const std::string & msg) override {
message_ = _(msg);
}
virtual void messageFmt(const wxString& fmt, ...) {
virtual void messageFmt(const std::string& fmt, ...) {
va_list arglist;
va_start(arglist, fmt);
message_ = wxString::Format(wxString(fmt), arglist);
message_ = wxString::Format(_(fmt), arglist);
va_end(arglist);
}
virtual void title(const wxString & title) override {
title_ = title;
virtual void title(const std::string & title) override {
title_ = _(title);
}
};
}
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::create_progress_indicator(
unsigned statenum, const wxString& title, const wxString& firstmsg) const
unsigned statenum,
const std::string& title,
const std::string& firstmsg) const
{
auto pri =
std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg);
@ -203,10 +203,10 @@ AppControllerBoilerplate::create_progress_indicator(
}
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::create_progress_indicator(unsigned statenum,
const wxString &title) const
AppControllerBoilerplate::create_progress_indicator(
unsigned statenum, const std::string &title) const
{
return create_progress_indicator(statenum, title, wxString());
return create_progress_indicator(statenum, title, std::string());
}
namespace {
@ -214,7 +214,7 @@ namespace {
class Wrapper: public ProgressIndicator, public wxEvtHandler {
ProgressStatusBar *sbar_;
using Base = ProgressIndicator;
std::string message_;
wxString message_;
AppControllerBoilerplate& ctl_;
void showProgress(bool show = true) {
@ -271,18 +271,18 @@ public:
}
}
virtual void message(const wxString & msg) override {
message_ = msg;
virtual void message(const std::string & msg) override {
message_ = _(msg);
}
virtual void message_fmt(const wxString& fmt, ...) override {
virtual void message_fmt(const std::string& fmt, ...) override {
va_list arglist;
va_start(arglist, fmt);
message_ = wxString::Format(fmt, arglist);
message_ = wxString::Format(_(fmt), arglist);
va_end(arglist);
}
virtual void title(const wxString & /*title*/) override {}
virtual void title(const std::string & /*title*/) override {}
virtual void on_cancel(CancelFn fn) override {
sbar_->set_cancel_callback(fn);

View File

@ -648,7 +648,7 @@ std::vector<int> GLVolumeCollection::load_object(
const ModelVolume *model_volume = model_object->volumes[volume_idx];
int extruder_id = -1;
if (!model_volume->modifier)
if (model_volume->is_model_part())
{
extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0;
if (extruder_id == 0)
@ -661,7 +661,16 @@ std::vector<int> GLVolumeCollection::load_object(
volumes_idx.push_back(int(this->volumes.size()));
float color[4];
memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
color[3] = model_volume->modifier ? 0.5f : 1.f;
if (model_volume->is_support_blocker()) {
color[0] = 1.0f;
color[1] = 0.2f;
color[2] = 0.2f;
} else if (model_volume->is_support_enforcer()) {
color[0] = 0.2f;
color[1] = 0.2f;
color[2] = 1.0f;
}
color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back();
if (use_VBOs)
@ -675,16 +684,20 @@ std::vector<int> GLVolumeCollection::load_object(
v.composite_id = obj_idx * 1000000 + volume_idx * 1000 + instance_idx;
v.set_select_group_id(select_by);
v.set_drag_group_id(drag_by);
if (!model_volume->modifier)
if (model_volume->is_model_part())
{
v.set_convex_hull(model_volume->get_convex_hull());
v.layer_height_texture = layer_height_texture;
if (extruder_id != -1)
v.extruder_id = extruder_id;
}
v.is_modifier = model_volume->modifier;
v.shader_outside_printer_detection_enabled = !model_volume->modifier;
v.is_modifier = ! model_volume->is_model_part();
v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
#if ENABLE_MODELINSTANCE_3D_OFFSET
v.set_offset(instance->get_offset());
#else
v.set_offset(Vec3d(instance->offset(0), instance->offset(1), 0.0));
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
v.set_rotation(instance->rotation);
v.set_scaling_factor(instance->scaling_factor);
}
@ -2164,6 +2177,11 @@ int _3DScene::get_first_volume_id(wxGLCanvas* canvas, int obj_idx)
return s_canvas_mgr.get_first_volume_id(canvas, obj_idx);
}
int _3DScene::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx)
{
return s_canvas_mgr.get_in_object_volume_id(canvas, scene_vol_idx);
}
void _3DScene::reload_scene(wxGLCanvas* canvas, bool force)
{
s_canvas_mgr.reload_scene(canvas, force);

View File

@ -577,6 +577,7 @@ public:
static std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
static int get_first_volume_id(wxGLCanvas* canvas, int obj_idx);
static int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx);
static void reload_scene(wxGLCanvas* canvas, bool force);

View File

@ -60,6 +60,14 @@ void AppConfig::set_defaults()
if (get("remember_output_path").empty())
set("remember_output_path", "1");
// Remove legacy window positions/sizes
erase("", "main_frame_maximized");
erase("", "main_frame_pos");
erase("", "main_frame_size");
erase("", "object_settings_maximized");
erase("", "object_settings_pos");
erase("", "object_settings_size");
}
void AppConfig::load()

View File

@ -72,6 +72,14 @@ public:
bool has(const std::string &key) const
{ return this->has("", key); }
void erase(const std::string &section, const std::string &key)
{
auto it = m_storage.find(section);
if (it != m_storage.end()) {
it->second.erase(key);
}
}
void clear_section(const std::string &section)
{ m_storage[section].clear(); }

View File

@ -409,11 +409,10 @@ PageFirmware::PageFirmware(ConfigWizard *parent) :
void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
{
ConfigOptionEnum<GCodeFlavor> opt;
auto sel = gcode_picker->GetSelection();
if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) {
config.set_key_value("gcode_flavor", &opt);
if (sel >= 0 && sel < gcode_opt.enum_labels.size()) {
auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
config.set_key_value("gcode_flavor", opt);
}
}
@ -871,10 +870,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) :
// If the screen is smaller, resize wizrad to match, which will enable scrollbars.
auto wizard_size = GetSize();
unsigned width, height;
GUI::get_current_screen_size(width, height);
if (GUI::get_current_screen_size(this, width, height)) {
wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN)));
wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN)));
SetMinSize(wizard_size);
}
Fit();
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });

View File

@ -367,7 +367,7 @@ void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT;
return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT;
}), ports.end());
if (ports.size() == 1) {
@ -390,23 +390,22 @@ void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
void FirmwareDialog::priv::lookup_port_mmu()
{
static const auto msg_not_found =
"The Multi Material Control device was not found.\n"
"If the device is connected, please press the Reset button next to the USB connector ...";
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
return port.id_vendor != USB_VID_PRUSA &&
return port.id_vendor != USB_VID_PRUSA ||
port.id_product != USB_PID_MMU_BOOT &&
port.id_product != USB_PID_MMU_APP;
}), ports.end());
if (ports.size() == 0) {
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(
"The Multi Material Control device was not found.\n"
"If the device is connected, please press the Reset button next to the USB connector ..."
)));
queue_status(_(L(msg_not_found)));
wait_for_mmu_bootloader(30);
} else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
@ -417,6 +416,13 @@ void FirmwareDialog::priv::lookup_port_mmu()
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
mmu_reboot(ports[0]);
wait_for_mmu_bootloader(10);
if (! port) {
// The device in bootloader mode was not found, inform the user and wait some more...
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(msg_not_found)));
wait_for_mmu_bootloader(30);
}
} else {
port = ports[0];
}
@ -702,7 +708,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
panel->SetSizer(vsizer);
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY);
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr,
"Hex files (*.hex)|*.hex|All files|*.*");
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY);

View File

@ -1142,8 +1142,10 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent)
if (!gizmo->init())
return false;
#if !ENABLE_MODELINSTANCE_3D_OFFSET
// temporary disable z grabber
gizmo->disable_grabber(2);
#endif // !ENABLE_MODELINSTANCE_3D_OFFSET
m_gizmos.insert(GizmosMap::value_type(Move, gizmo));
@ -2377,7 +2379,11 @@ void GLCanvas3D::update_gizmos_data()
ModelInstance* model_instance = model_object->instances[0];
if (model_instance != nullptr)
{
#if ENABLE_MODELINSTANCE_3D_OFFSET
m_gizmos.set_position(model_instance->get_offset());
#else
m_gizmos.set_position(Vec3d(model_instance->offset(0), model_instance->offset(1), 0.0));
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
m_gizmos.set_scale(model_instance->scaling_factor);
m_gizmos.set_angle_z(model_instance->rotation);
m_gizmos.set_flattening_data(model_object);
@ -2495,6 +2501,11 @@ int GLCanvas3D::get_first_volume_id(int obj_idx) const
return -1;
}
int GLCanvas3D::get_in_object_volume_id(int scene_vol_idx) const
{
return ((0 <= scene_vol_idx) && (scene_vol_idx < (int)m_volumes.volumes.size())) ? m_volumes.volumes[scene_vol_idx]->volume_idx() : -1;
}
void GLCanvas3D::reload_scene(bool force)
{
if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
@ -5361,8 +5372,12 @@ void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs)
ModelObject* model_object = m_model->objects[obj_idx];
if (model_object != nullptr)
{
#if ENABLE_MODELINSTANCE_3D_OFFSET
model_object->instances[instance_idx]->set_offset(volume->get_offset());
#else
const Vec3d& offset = volume->get_offset();
model_object->instances[instance_idx]->offset = Vec2d(offset(0), offset(1));
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
model_object->invalidate_bounding_box();
update_position_values();
object_moved = true;
@ -5409,6 +5424,7 @@ void GLCanvas3D::_on_select(int volume_idx, int object_idx)
}
m_on_select_object_callback.call(obj_id, vol_id);
Slic3r::GUI::select_current_volume(obj_id, vol_id);
}
std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors)

View File

@ -105,7 +105,6 @@ class GLCanvas3D
void reset() { first_volumes.clear(); }
};
public:
struct Camera
{
enum EType : unsigned char
@ -441,7 +440,6 @@ public:
void render(const GLCanvas3D& canvas) const;
};
private:
wxGLCanvas* m_canvas;
wxGLContext* m_context;
LegendTexture m_legend_texture;
@ -605,6 +603,7 @@ public:
std::vector<int> load_object(const Model& model, int obj_idx);
int get_first_volume_id(int obj_idx) const;
int get_in_object_volume_id(int scene_vol_idx) const;
void reload_scene(bool force);

View File

@ -548,6 +548,12 @@ int GLCanvas3DManager::get_first_volume_id(wxGLCanvas* canvas, int obj_idx) cons
return (it != m_canvases.end()) ? it->second->get_first_volume_id(obj_idx) : -1;
}
int GLCanvas3DManager::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_in_object_volume_id(scene_vol_idx) : -1;
}
void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force)
{
CanvasesMap::iterator it = _get_canvas(canvas);

View File

@ -138,6 +138,7 @@ public:
std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
int get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const;
int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const;
void reload_scene(wxGLCanvas* canvas, bool force);

View File

@ -20,47 +20,47 @@ static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f
namespace Slic3r {
namespace GUI {
// returns the intersection of the given ray with the plane parallel to plane XY and passing through the given center
// coordinates are local to the plane
Vec3d intersection_on_plane_xy(const Linef3& ray, const Vec3d& center)
{
// returns the intersection of the given ray with the plane parallel to plane XY and passing through the given center
// coordinates are local to the plane
Vec3d intersection_on_plane_xy(const Linef3& ray, const Vec3d& center)
{
Transform3d m = Transform3d::Identity();
m.translate(-center);
Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0));
return Vec3d(mouse_pos_2d(0), mouse_pos_2d(1), 0.0);
}
}
// returns the intersection of the given ray with the plane parallel to plane XZ and passing through the given center
// coordinates are local to the plane
Vec3d intersection_on_plane_xz(const Linef3& ray, const Vec3d& center)
{
// returns the intersection of the given ray with the plane parallel to plane XZ and passing through the given center
// coordinates are local to the plane
Vec3d intersection_on_plane_xz(const Linef3& ray, const Vec3d& center)
{
Transform3d m = Transform3d::Identity();
m.rotate(Eigen::AngleAxisd(-0.5 * (double)PI, Vec3d::UnitX()));
m.translate(-center);
Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0));
return Vec3d(mouse_pos_2d(0), 0.0, mouse_pos_2d(1));
}
}
// returns the intersection of the given ray with the plane parallel to plane YZ and passing through the given center
// coordinates are local to the plane
Vec3d intersection_on_plane_yz(const Linef3& ray, const Vec3d& center)
{
// returns the intersection of the given ray with the plane parallel to plane YZ and passing through the given center
// coordinates are local to the plane
Vec3d intersection_on_plane_yz(const Linef3& ray, const Vec3d& center)
{
Transform3d m = Transform3d::Identity();
m.rotate(Eigen::AngleAxisd(-0.5f * (double)PI, Vec3d::UnitY()));
m.translate(-center);
Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0));
return Vec3d(0.0, mouse_pos_2d(1), -mouse_pos_2d(0));
}
}
// return an index:
// 0 for plane XY
// 1 for plane XZ
// 2 for plane YZ
// which indicates which plane is best suited for intersecting the given unit vector
// giving precedence to the plane with the given index
unsigned int select_best_plane(const Vec3d& unit_vector, unsigned int preferred_plane)
{
// return an index:
// 0 for plane XY
// 1 for plane XZ
// 2 for plane YZ
// which indicates which plane is best suited for intersecting the given unit vector
// giving precedence to the plane with the given index
unsigned int select_best_plane(const Vec3d& unit_vector, unsigned int preferred_plane)
{
unsigned int ret = preferred_plane;
// 1st checks if the given vector is not parallel to the given preferred plane
@ -101,9 +101,10 @@ namespace GUI {
}
return ret;
}
}
const float GLGizmoBase::Grabber::HalfSize = 2.0f;
const float GLGizmoBase::Grabber::SizeFactor = 0.025f;
const float GLGizmoBase::Grabber::MinHalfSize = 1.5f;
const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f;
GLGizmoBase::Grabber::Grabber()
@ -117,7 +118,7 @@ GLGizmoBase::Grabber::Grabber()
color[2] = 1.0f;
}
void GLGizmoBase::Grabber::render(bool hover) const
void GLGizmoBase::Grabber::render(bool hover, const BoundingBoxf3& box) const
{
float render_color[3];
if (hover)
@ -129,12 +130,15 @@ void GLGizmoBase::Grabber::render(bool hover) const
else
::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float));
render(render_color, true);
render(box, render_color, true);
}
void GLGizmoBase::Grabber::render(const float* render_color, bool use_lighting) const
void GLGizmoBase::Grabber::render(const BoundingBoxf3& box, const float* render_color, bool use_lighting) const
{
float half_size = dragging ? HalfSize * DraggingScaleFactor : HalfSize;
float max_size = (float)box.max_size();
float half_size = dragging ? max_size * SizeFactor * DraggingScaleFactor : max_size * SizeFactor;
half_size = std::max(half_size, MinHalfSize);
if (use_lighting)
::glEnable(GL_LIGHTING);
@ -291,16 +295,16 @@ float GLGizmoBase::picking_color_component(unsigned int id) const
return (float)color / 255.0f;
}
void GLGizmoBase::render_grabbers() const
void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const
{
for (int i = 0; i < (int)m_grabbers.size(); ++i)
{
if (m_grabbers[i].enabled)
m_grabbers[i].render(m_hover_id == i);
m_grabbers[i].render((m_hover_id == i), box);
}
}
void GLGizmoBase::render_grabbers_for_picking() const
void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const
{
for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i)
{
@ -309,7 +313,7 @@ void GLGizmoBase::render_grabbers_for_picking() const
m_grabbers[i].color[0] = 1.0f;
m_grabbers[i].color[1] = 1.0f;
m_grabbers[i].color[2] = picking_color_component(i);
m_grabbers[i].render_for_picking();
m_grabbers[i].render_for_picking(box);
}
}
}
@ -440,7 +444,7 @@ void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
if (m_hover_id != -1)
render_angle();
render_grabber();
render_grabber(box);
::glPopMatrix();
}
@ -452,7 +456,7 @@ void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const
::glPushMatrix();
transform_to_local();
render_grabbers_for_picking();
render_grabbers_for_picking(box);
::glPopMatrix();
}
@ -544,7 +548,7 @@ void GLGizmoRotate::render_angle() const
::glEnd();
}
void GLGizmoRotate::render_grabber() const
void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const
{
double grabber_radius = (double)(m_radius + GrabberOffset);
m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0);
@ -558,7 +562,7 @@ void GLGizmoRotate::render_grabber() const
::glEnd();
::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float));
render_grabbers();
render_grabbers(box);
}
void GLGizmoRotate::transform_to_local() const
@ -689,6 +693,7 @@ void GLGizmoRotate3D::on_render(const BoundingBoxf3& box) const
}
const float GLGizmoScale3D::Offset = 5.0f;
const Vec3d GLGizmoScale3D::OffsetVec = (double)GLGizmoScale3D::Offset * Vec3d::Ones();
GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent)
: GLGizmoBase(parent)
@ -738,7 +743,7 @@ void GLGizmoScale3D::on_start_dragging(const BoundingBoxf3& box)
{
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_show_starting_box = true;
m_starting_box = box;
m_starting_box = BoundingBoxf3(box.min - OffsetVec, box.max + OffsetVec);
}
}
@ -772,9 +777,7 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
::glEnable(GL_DEPTH_TEST);
Vec3d offset_vec = (double)Offset * Vec3d::Ones();
m_box = BoundingBoxf3(box.min - offset_vec, box.max + offset_vec);
m_box = BoundingBoxf3(box.min - OffsetVec, box.max + OffsetVec);
const Vec3d& center = m_box.center();
// x axis
@ -829,7 +832,7 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
render_grabbers_connection(4, 5);
}
// draw grabbers
render_grabbers();
render_grabbers(m_box);
}
else if ((m_hover_id == 0) || (m_hover_id == 1))
{
@ -846,8 +849,8 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
::glColor3fv(m_grabbers[0].color);
render_grabbers_connection(0, 1);
// draw grabbers
m_grabbers[0].render(true);
m_grabbers[1].render(true);
m_grabbers[0].render(true, m_box);
m_grabbers[1].render(true, m_box);
}
else if ((m_hover_id == 2) || (m_hover_id == 3))
{
@ -864,8 +867,8 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
::glColor3fv(m_grabbers[2].color);
render_grabbers_connection(2, 3);
// draw grabbers
m_grabbers[2].render(true);
m_grabbers[3].render(true);
m_grabbers[2].render(true, m_box);
m_grabbers[3].render(true, m_box);
}
else if ((m_hover_id == 4) || (m_hover_id == 5))
{
@ -882,8 +885,8 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
::glColor3fv(m_grabbers[4].color);
render_grabbers_connection(4, 5);
// draw grabbers
m_grabbers[4].render(true);
m_grabbers[5].render(true);
m_grabbers[4].render(true, m_box);
m_grabbers[5].render(true, m_box);
}
else if (m_hover_id >= 6)
{
@ -899,7 +902,7 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
// draw grabbers
for (int i = 6; i < 10; ++i)
{
m_grabbers[i].render(true);
m_grabbers[i].render(true, m_box);
}
}
}
@ -908,7 +911,7 @@ void GLGizmoScale3D::on_render_for_picking(const BoundingBoxf3& box) const
{
::glDisable(GL_DEPTH_TEST);
render_grabbers_for_picking();
render_grabbers_for_picking(box);
}
void GLGizmoScale3D::render_box(const BoundingBoxf3& box) const
@ -1029,6 +1032,7 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent)
, m_position(Vec3d::Zero())
, m_starting_drag_position(Vec3d::Zero())
, m_starting_box_center(Vec3d::Zero())
, m_starting_box_bottom_center(Vec3d::Zero())
{
}
@ -1062,17 +1066,19 @@ void GLGizmoMove3D::on_start_dragging(const BoundingBoxf3& box)
{
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
m_starting_box_bottom_center(2) = box.min(2);
}
}
void GLGizmoMove3D::on_update(const Linef3& mouse_ray)
{
if (m_hover_id == 0)
m_position(0) = 2.0 * m_starting_box_center(0) + calc_displacement(1, mouse_ray) - m_starting_drag_position(0);
m_position(0) = 2.0 * m_starting_box_center(0) + calc_projection(X, 1, mouse_ray) - m_starting_drag_position(0);
else if (m_hover_id == 1)
m_position(1) = 2.0 * m_starting_box_center(1) + calc_displacement(2, mouse_ray) - m_starting_drag_position(1);
m_position(1) = 2.0 * m_starting_box_center(1) + calc_projection(Y, 2, mouse_ray) - m_starting_drag_position(1);
else if (m_hover_id == 2)
m_position(2) = 2.0 * m_starting_box_center(2) + calc_displacement(1, mouse_ray) - m_starting_drag_position(2);
m_position(2) = 2.0 * m_starting_box_bottom_center(2) + calc_projection(Z, 1, mouse_ray) - m_starting_drag_position(2);
}
void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const
@ -1118,7 +1124,7 @@ void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const
}
// draw grabbers
render_grabbers();
render_grabbers(box);
}
else
{
@ -1130,7 +1136,7 @@ void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const
::glEnd();
// draw grabber
m_grabbers[m_hover_id].render(true);
m_grabbers[m_hover_id].render(true, box);
}
}
@ -1138,17 +1144,17 @@ void GLGizmoMove3D::on_render_for_picking(const BoundingBoxf3& box) const
{
::glDisable(GL_DEPTH_TEST);
render_grabbers_for_picking();
render_grabbers_for_picking(box);
}
double GLGizmoMove3D::calc_displacement(unsigned int preferred_plane_id, const Linef3& mouse_ray) const
double GLGizmoMove3D::calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const
{
double displacement = 0.0;
double projection = 0.0;
Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
Vec3d starting_vec = (axis == Z) ? m_starting_drag_position - m_starting_box_bottom_center : m_starting_drag_position - m_starting_box_center;
double len_starting_vec = starting_vec.norm();
if (len_starting_vec == 0.0)
return displacement;
return projection;
Vec3d starting_vec_dir = starting_vec.normalized();
Vec3d mouse_dir = mouse_ray.unit_vector();
@ -1159,22 +1165,22 @@ double GLGizmoMove3D::calc_displacement(unsigned int preferred_plane_id, const L
{
case 0:
{
displacement = starting_vec_dir.dot(intersection_on_plane_xy(mouse_ray, m_starting_box_center));
projection = starting_vec_dir.dot(intersection_on_plane_xy(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center));
break;
}
case 1:
{
displacement = starting_vec_dir.dot(intersection_on_plane_xz(mouse_ray, m_starting_box_center));
projection = starting_vec_dir.dot(intersection_on_plane_xz(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center));
break;
}
case 2:
{
displacement = starting_vec_dir.dot(intersection_on_plane_yz(mouse_ray, m_starting_box_center));
projection = starting_vec_dir.dot(intersection_on_plane_yz(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center));
break;
}
}
return displacement;
return projection;
}
GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent)
@ -1230,10 +1236,19 @@ void GLGizmoFlatten::on_render(const BoundingBoxf3& box) const
else
::glColor4f(0.9f, 0.9f, 0.9f, 0.5f);
#if ENABLE_MODELINSTANCE_3D_OFFSET
for (Vec3d offset : m_instances_positions) {
offset += dragged_offset;
#else
for (Vec2d offset : m_instances_positions) {
offset += to_2d(dragged_offset);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
::glPushMatrix();
#if ENABLE_MODELINSTANCE_3D_OFFSET
::glTranslated(offset(0), offset(1), offset(2));
#else
::glTranslatef((GLfloat)offset(0), (GLfloat)offset(1), 0.0f);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
::glBegin(GL_POLYGON);
for (const Vec3d& vertex : m_planes[i].vertices)
::glVertex3f((GLfloat)vertex(0), (GLfloat)vertex(1), (GLfloat)vertex(2));
@ -1252,9 +1267,17 @@ void GLGizmoFlatten::on_render_for_picking(const BoundingBoxf3& box) const
for (unsigned int i = 0; i < m_planes.size(); ++i)
{
::glColor3f(1.0f, 1.0f, picking_color_component(i));
#if ENABLE_MODELINSTANCE_3D_OFFSET
for (const Vec3d& offset : m_instances_positions) {
#else
for (const Vec2d& offset : m_instances_positions) {
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
::glPushMatrix();
#if ENABLE_MODELINSTANCE_3D_OFFSET
::glTranslated(offset(0), offset(1), offset(2));
#else
::glTranslatef((GLfloat)offset(0), (GLfloat)offset(1), 0.0f);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
::glBegin(GL_POLYGON);
for (const Vec3d& vertex : m_planes[i].vertices)
::glVertex3f((GLfloat)vertex(0), (GLfloat)vertex(1), (GLfloat)vertex(2));
@ -1272,7 +1295,11 @@ void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object)
if (m_model_object && !m_model_object->instances.empty()) {
m_instances_positions.clear();
for (const auto* instance : m_model_object->instances)
#if ENABLE_MODELINSTANCE_3D_OFFSET
m_instances_positions.emplace_back(instance->get_offset());
#else
m_instances_positions.emplace_back(instance->offset);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
if (is_plane_update_necessary())

View File

@ -22,7 +22,8 @@ class GLGizmoBase
protected:
struct Grabber
{
static const float HalfSize;
static const float SizeFactor;
static const float MinHalfSize;
static const float DraggingScaleFactor;
Vec3d center;
@ -33,11 +34,11 @@ protected:
Grabber();
void render(bool hover) const;
void render_for_picking() const { render(color, false); }
void render(bool hover, const BoundingBoxf3& box) const;
void render_for_picking(const BoundingBoxf3& box) const { render(box, color, false); }
private:
void render(const float* render_color, bool use_lighting) const;
void render(const BoundingBoxf3& box, const float* render_color, bool use_lighting) const;
void render_face(float half_size) const;
};
@ -109,8 +110,8 @@ protected:
virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
float picking_color_component(unsigned int id) const;
void render_grabbers() const;
void render_grabbers_for_picking() const;
void render_grabbers(const BoundingBoxf3& box) const;
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
void set_tooltip(const std::string& tooltip) const;
std::string format(float value, unsigned int decimals) const;
@ -163,7 +164,7 @@ private:
void render_snap_radii() const;
void render_reference_radius() const;
void render_angle() const;
void render_grabber() const;
void render_grabber(const BoundingBoxf3& box) const;
void transform_to_local() const;
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
@ -234,6 +235,7 @@ protected:
class GLGizmoScale3D : public GLGizmoBase
{
static const float Offset;
static const Vec3d OffsetVec;
mutable BoundingBoxf3 m_box;
@ -285,6 +287,7 @@ class GLGizmoMove3D : public GLGizmoBase
Vec3d m_position;
Vec3d m_starting_drag_position;
Vec3d m_starting_box_center;
Vec3d m_starting_box_bottom_center;
public:
explicit GLGizmoMove3D(GLCanvas3D& parent);
@ -300,7 +303,7 @@ protected:
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
private:
double calc_displacement(unsigned int preferred_plane_id, const Linef3& mouse_ray) const;
double calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const;
};
class GLGizmoFlatten : public GLGizmoBase
@ -326,7 +329,11 @@ private:
SourceDataSummary m_source_data;
std::vector<PlaneData> m_planes;
#if ENABLE_MODELINSTANCE_3D_OFFSET
Pointf3s m_instances_positions;
#else
std::vector<Vec2d> m_instances_positions;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
Vec3d m_starting_center;
const ModelObject* m_model_object = nullptr;

View File

@ -7,6 +7,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h>
@ -121,7 +122,6 @@ wxPanel *g_wxPlater = nullptr;
AppConfig *g_AppConfig = nullptr;
PresetBundle *g_PresetBundle= nullptr;
PresetUpdater *g_PresetUpdater = nullptr;
_3DScene *g_3DScene = nullptr;
wxColour g_color_label_modified;
wxColour g_color_label_sys;
wxColour g_color_label_default;
@ -141,14 +141,9 @@ 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_info_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;
std::vector<wxButton*> g_buttons;
wxStaticBitmap *g_manifold_warning_icon = nullptr;
bool g_show_print_info = false;
bool g_show_manifold_warning_icon = false;
@ -241,27 +236,36 @@ void set_preset_updater(PresetUpdater *updater)
g_PresetUpdater = updater;
}
void set_3DScene(_3DScene *scene)
enum ActionButtons
{
g_3DScene = scene;
}
abExportGCode,
abReslice,
abPrint,
abSendGCode,
};
void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer,
wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer,
void set_objects_from_perl( wxWindow* parent,
wxBoxSizer *frequently_changed_parameters_sizer,
wxBoxSizer *info_sizer,
wxButton *btn_export_gcode,
wxButton *btn_export_stl, wxButton *btn_reslice,
wxButton *btn_print, wxButton *btn_send_gcode,
wxButton *btn_reslice,
wxButton *btn_print,
wxButton *btn_send_gcode,
wxStaticBitmap *manifold_warning_icon)
{
g_right_panel = parent;
g_right_panel = parent->GetParent();
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_info_sizer = info_sizer;
g_buttons.push_back(btn_export_gcode);
g_buttons.push_back(btn_reslice);
g_buttons.push_back(btn_print);
g_buttons.push_back(btn_send_gcode);
// Update font style for buttons
for (auto btn : g_buttons)
btn->SetFont(bold_font());
g_manifold_warning_icon = manifold_warning_icon;
}
@ -273,6 +277,15 @@ void set_show_print_info(bool show)
void set_show_manifold_warning_icon(bool show)
{
g_show_manifold_warning_icon = show;
if (!g_manifold_warning_icon)
return;
// update manifold_warning_icon showing
if (show && !g_info_sizer->IsShown(static_cast<size_t>(0)))
g_show_manifold_warning_icon = false;
g_manifold_warning_icon->Show(g_show_manifold_warning_icon);
g_manifold_warning_icon->GetParent()->Layout();
}
void set_objects_list_sizer(wxBoxSizer *objects_list_sizer){
@ -963,8 +976,7 @@ wxString from_u8(const std::string &str)
return wxString::FromUTF8(str.c_str());
}
void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer,
Model &model,
void set_model_events_from_perl(Model &model,
int event_object_selection_changed,
int event_object_settings_changed,
int event_remove_object,
@ -1105,7 +1117,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
};
optgroup->append_line(line);
sizer->Add(optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 2);
sizer->Add(optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT, 2);
m_optgroups.push_back(optgroup);// ogFrequentlyChangingParameters
@ -1132,24 +1144,23 @@ void show_frequently_changed_parameters(bool show)
void show_buttons(bool show)
{
g_btn_export_stl->Show(show);
g_btn_reslice->Show(show);
g_buttons[abReslice]->Show(show);
for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) {
TabPrinter *tab = dynamic_cast<TabPrinter*>(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());
g_buttons[abPrint]->Show(show && !tab->m_config->opt_string("serial_port").empty());
g_buttons[abSendGCode]->Show(show && !tab->m_config->opt_string("print_host").empty());
}
break;
}
}
void show_info_sizer(bool show)
void show_info_sizer(const bool show)
{
g_scrolled_window_sizer->Show(static_cast<size_t>(0), show);
g_scrolled_window_sizer->Show(1, show && g_show_print_info);
g_info_sizer->Show(static_cast<size_t>(0), show);
g_info_sizer->Show(1, show && g_show_print_info);
g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon);
}
@ -1162,31 +1173,22 @@ void show_object_name(bool show)
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());
}
// ***********************************
wxWindowUpdateLocker noUpdates(g_right_panel->GetParent());
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);
show_object_name(mode == ConfigMenuModeSimple);
show_manipulation_sizer(mode == ConfigMenuModeSimple);
// TODO There is a not the best place of it!
// *** Update showing of the collpane_settings
// show_collpane_settings(mode == ConfigMenuModeExpert);
// *************************
g_right_panel->Layout();
g_right_panel->GetParent()->GetParent()->Layout();
g_right_panel->GetParent()->Layout();
}
bool is_expert_mode(){
@ -1254,12 +1256,77 @@ int get_export_option(wxFileDialog* dlg)
}
void get_current_screen_size(unsigned &width, unsigned &height)
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height)
{
wxDisplay display(wxDisplay::GetFromWindow(g_wxMainFrame));
const auto idx = wxDisplay::GetFromWindow(window);
if (idx == wxNOT_FOUND) {
return false;
}
wxDisplay display(idx);
const auto disp_size = display.GetClientArea();
width = disp_size.GetWidth();
height = disp_size.GetHeight();
return true;
}
void save_window_size(wxTopLevelWindow *window, const std::string &name)
{
const wxSize size = window->GetSize();
const wxPoint pos = window->GetPosition();
const auto maximized = window->IsMaximized() ? "1" : "0";
g_AppConfig->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str());
g_AppConfig->set((boost::format("window_%1%_maximized") % name).str(), maximized);
}
void restore_window_size(wxTopLevelWindow *window, const std::string &name)
{
// XXX: This still doesn't behave nicely in some situations (mostly on Linux).
// The problem is that it's hard to obtain window position with respect to screen geometry reliably
// from wxWidgets. Sometimes wxWidgets claim a window is located on a different screen than on which
// it's actually visible. I suspect this has something to do with window initialization (maybe we
// restore window geometry too early), but haven't yet found a workaround.
const auto display_idx = wxDisplay::GetFromWindow(window);
if (display_idx == wxNOT_FOUND) { return; }
const auto display = wxDisplay(display_idx).GetClientArea();
std::vector<std::string> pair;
try {
const auto key_size = (boost::format("window_%1%_size") % name).str();
if (g_AppConfig->has(key_size)) {
if (unescape_strings_cstyle(g_AppConfig->get(key_size), pair) && pair.size() == 2) {
auto width = boost::lexical_cast<int>(pair[0]);
auto height = boost::lexical_cast<int>(pair[1]);
window->SetSize(width, height);
}
}
} catch(const boost::bad_lexical_cast &) {}
// Maximizing should be the last thing to do.
// This ensure the size and position are sane when the user un-maximizes the window.
const auto key_maximized = (boost::format("window_%1%_maximized") % name).str();
if (g_AppConfig->get(key_maximized) == "1") {
window->Maximize(true);
}
}
void enable_action_buttons(bool enable)
{
if (g_buttons.empty())
return;
// Update background colour for buttons
const wxColour bgrd_color = enable ? wxColour(224, 224, 224/*255, 96, 0*/) : wxColour(204, 204, 204);
for (auto btn : g_buttons) {
btn->Enable(enable);
btn->SetBackgroundColour(bgrd_color);
}
}
void about()

View File

@ -27,6 +27,7 @@ class wxButton;
class wxFileDialog;
class wxStaticBitmap;
class wxFont;
class wxTopLevelWindow;
namespace Slic3r {
@ -38,7 +39,6 @@ class AppConfig;
class PresetUpdater;
class DynamicPrintConfig;
class TabIface;
class _3DScene;
#define _(s) Slic3r::GUI::I18N::translate((s))
@ -106,13 +106,10 @@ void set_plater(wxPanel *plater);
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,
wxBoxSizer *info_sizer,
wxButton *btn_export_gcode,
wxButton *btn_export_stl,
wxButton *btn_reslice,
wxButton *btn_print,
wxButton *btn_send_gcode,
@ -196,6 +193,8 @@ bool select_language(wxArrayString & names, wxArrayLong & identifiers);
// update right panel of the Plater according to view mode
void update_mode();
void show_info_sizer(const bool show);
std::vector<Tab *>& get_tabs_list();
bool checked_tab(Tab* tab);
void delete_tab_from_list(Tab* tab);
@ -214,8 +213,7 @@ 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,
void set_model_events_from_perl(Model &model,
int event_object_selection_changed,
int event_object_settings_changed,
int event_remove_object,
@ -236,7 +234,15 @@ void add_export_option(wxFileDialog* dlg, const std::string& format);
int get_export_option(wxFileDialog* dlg);
// Returns the dimensions of the screen on which the main frame is displayed
void get_current_screen_size(unsigned &width, unsigned &height);
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height);
// Save window size and maximized status into AppConfig
void save_window_size(wxTopLevelWindow *window, const std::string &name);
// Restore the above
void restore_window_size(wxTopLevelWindow *window, const std::string &name);
// Update buttons view according to enable/disable
void enable_action_buttons(bool enable);
// Display an About dialog
extern void about();

View File

@ -13,6 +13,9 @@
#include "Geometry.hpp"
#include "slic3r/Utils/FixModelByWin10.hpp"
#include <wx/glcanvas.h>
#include "3DScene.hpp"
namespace Slic3r
{
namespace GUI
@ -23,10 +26,12 @@ wxSizer *m_sizer_object_movers = nullptr;
wxDataViewCtrl *m_objects_ctrl = nullptr;
PrusaObjectDataViewModel *m_objects_model = nullptr;
wxCollapsiblePane *m_collpane_settings = nullptr;
PrusaDoubleSlider *m_slider = nullptr;
wxGLCanvas *m_preview_canvas = nullptr;
wxIcon m_icon_modifiermesh;
wxIcon m_icon_solidmesh;
wxIcon m_icon_manifold_warning;
wxBitmap m_icon_modifiermesh;
wxBitmap m_icon_solidmesh;
wxBitmap m_icon_manifold_warning;
wxBitmap m_bmp_cog;
wxBitmap m_bmp_split;
@ -64,8 +69,6 @@ bool m_part_settings_changed = false;
wxString g_selected_extruder = "";
#endif //__WXOSX__
// typedef std::map<std::string, std::string> t_category_icon;
typedef std::map<std::string, wxBitmap> t_category_icon;
inline t_category_icon& get_category_icon() {
static t_category_icon CATEGORY_ICON;
if (CATEGORY_ICON.empty()){
@ -138,11 +141,11 @@ void set_objects_from_model(Model &model) {
}
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);
m_icon_modifiermesh = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG);
m_icon_solidmesh = wxBitmap(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);
m_icon_manifold_warning = wxBitmap(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);
@ -233,7 +236,7 @@ wxDataViewColumn* object_ctrl_create_extruder_column(int extruders_count)
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
m_objects_ctrl->SetMinSize(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);
@ -246,8 +249,9 @@ void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz)
#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, 200,
wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE);
// And Icon can be consisting of several bitmaps
m_objects_ctrl->AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(),
0, 200, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE));
// column 1 of the view control:
m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 45,
@ -278,7 +282,7 @@ wxBoxSizer* create_objects_list(wxWindow *win)
m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) {
object_ctrl_context_menu();
event.Skip();
// event.Skip();
});
m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { object_ctrl_key_event(event); }); // doesn't work on OSX
@ -316,15 +320,15 @@ wxBoxSizer* create_edit_object_buttons(wxWindow* win)
//*** button's functions
btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) {
on_btn_load(win);
// on_btn_load(win);
});
btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) {
on_btn_load(win, true);
// on_btn_load(win, true);
});
btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) {
on_btn_load(win, true, true);
// on_btn_load(win, true, true);
});
btn_delete ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_del(); });
@ -660,7 +664,7 @@ void add_object_to_list(const std::string &name, ModelObject* model_object)
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);
const PrusaDataViewBitmapText data(item_name, m_icon_manifold_warning);
wxVariant variant;
variant << data;
m_objects_model->SetValue(variant, item, 0);
@ -724,11 +728,22 @@ void select_current_object(int idx)
{
g_prevent_list_events = true;
m_objects_ctrl->UnselectAll();
if (idx < 0) {
if (idx>=0)
m_objects_ctrl->Select(m_objects_model->GetItemById(idx));
part_selection_changed();
g_prevent_list_events = false;
}
void select_current_volume(int idx, int vol_idx)
{
if (vol_idx < 0) {
select_current_object(idx);
return;
}
m_objects_ctrl->Select(m_objects_model->GetItemById(idx));
g_prevent_list_events = true;
m_objects_ctrl->UnselectAll();
if (idx >= 0)
m_objects_ctrl->Select(m_objects_model->GetItemByVolumeId(idx, vol_idx));
part_selection_changed();
g_prevent_list_events = false;
}
@ -758,8 +773,17 @@ void object_ctrl_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);
event.SetId(m_selected_object_id); // set $obj_idx
const wxDataViewItem item = m_objects_ctrl->GetSelection();
if (!item || m_objects_model->GetParent(item) == wxDataViewItem(0))
event.SetInt(-1); // set $vol_idx
else {
const int vol_idx = m_objects_model->GetVolumeIdByItem(item);
if (vol_idx == -2) // is settings item
event.SetInt(m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item))); // set $vol_idx
else
event.SetInt(vol_idx);
}
get_main_frame()->ProcessWindowEvent(event);
}
@ -772,9 +796,9 @@ void object_ctrl_context_menu()
{
wxDataViewItem item;
wxDataViewColumn* col;
printf("object_ctrl_context_menu\n");
// printf("object_ctrl_context_menu\n");
const wxPoint pt = get_mouse_position_in_control();
printf("mouse_position_in_control: x = %d, y = %d\n", pt.x, pt.y);
// printf("mouse_position_in_control: x = %d, y = %d\n", pt.x, pt.y);
m_objects_ctrl->HitTest(pt, item, col);
if (!item)
#ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX
@ -788,9 +812,9 @@ void object_ctrl_context_menu()
#else
return;
#endif // __WXOSX__
printf("item exists\n");
// printf("item exists\n");
const wxString title = col->GetTitle();
printf("title = *%s*\n", title.data().AsChar());
// printf("title = *%s*\n", title.data().AsChar());
if (title == " ")
show_context_menu();
@ -836,6 +860,15 @@ void object_ctrl_item_value_change(wxDataViewEvent& event)
}
}
void show_manipulation_og(const bool show)
{
wxGridSizer* grid_sizer = get_optgroup(ogFrequentlyObjectSettings)->get_grid_sizer();
if (show == grid_sizer->IsShown(2))
return;
for (size_t id = 2; id < 12; id++)
grid_sizer->Show(id, show);
}
//update_optgroup
void update_settings_list()
{
@ -856,14 +889,19 @@ void update_settings_list()
m_option_sizer->Clear(true);
if (m_config)
bool show_manipulations = true;
const auto item = m_objects_ctrl->GetSelection();
if (m_config && m_objects_model->IsSettingsItem(item))
{
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),
auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
#ifdef __WXMSW__
btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif // __WXMSW__
btn->Bind(wxEVT_BUTTON, [opt_key](wxEvent &event){
(*m_config)->erase(opt_key);
wxTheApp->CallAfter([]() { update_settings_list(); });
@ -873,16 +911,17 @@ void update_settings_list()
std::map<std::string, std::vector<std::string>> cat_options;
auto opt_keys = (*m_config)->keys();
if (opt_keys.size() == 1 && opt_keys[0] == "extruder")
return;
m_og_settings.resize(0);
std::vector<std::string> categories;
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<ConfigOptionFloats>("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;
(category == "Extruders" && extruders_cnt == 1)) continue;
std::vector< std::string > new_category;
@ -892,14 +931,12 @@ void update_settings_list()
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<ConfigOptionsGroup>(parent, cat.first, *m_config, false, ogDEFAULT, extra_column);
optgroup->label_width = 100;
optgroup->label_width = 150;
optgroup->sidetext_width = 70;
for (auto& opt : cat.second)
@ -913,20 +950,36 @@ void update_settings_list()
optgroup->reload_config();
m_option_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
m_og_settings.push_back(optgroup);
categories.push_back(cat.first);
}
}
if (m_og_settings.empty()) {
m_objects_ctrl->Select(m_objects_model->Delete(item));
part_selection_changed();
}
else {
if (!categories.empty())
m_objects_model->UpdateSettingsDigest(item, categories);
show_manipulations = false;
}
}
show_manipulation_og(show_manipulations);
show_info_sizer(show_manipulations && item && m_objects_model->GetParent(item) == wxDataViewItem(0));
#ifdef __linux__
no_updates.reset(nullptr);
#endif
/*get_right_panel()*/parent->Layout();
get_right_panel()->GetParent()->GetParent()->Layout();
parent->Layout();
get_right_panel()->GetParent()->Layout();
}
void get_settings_choice(wxMenu *menu, int id, bool is_part)
{
auto category_name = menu->GetLabel(id);
const auto category_name = menu->GetLabel(id);
wxArrayString names;
wxArrayInt selections;
@ -974,15 +1027,35 @@ void get_settings_choice(wxMenu *menu, int id, bool is_part)
(*m_config)->set_key_value(opt_key, m_default_config.get()->option(opt_key)->clone());
}
// Add settings item for object
const auto item = m_objects_ctrl->GetSelection();
if (item) {
const auto settings_item = m_objects_model->HasSettings(item);
m_objects_ctrl->Select(settings_item ? settings_item :
m_objects_model->AddSettingsChild(item));
#ifndef __WXOSX__
part_selection_changed();
#endif //no __WXOSX__
}
else
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;
void menu_item_add_generic(wxMenuItem* &menu, int id) {
auto sub_menu = new wxMenu;
std::vector<std::string> menu_items = { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") };
for (auto& item : menu_items)
sub_menu->Append(new wxMenuItem(sub_menu, ++id, _(item)));
#ifndef __WXMSW__
sub_menu->Bind(wxEVT_MENU, [sub_menu](wxEvent &event) {
load_lambda(sub_menu->GetLabel(event.GetId()).ToStdString());
});
#endif //no __WXMSW__
menu->SetSubMenu(sub_menu);
}
wxMenuItem* menu_item_split(wxMenu* menu, int id) {
@ -991,11 +1064,11 @@ wxMenuItem* menu_item_split(wxMenu* menu, int id) {
return menu_item;
}
wxMenuItem* menu_item_settings(wxMenu* menu, int id) {
wxMenuItem* menu_item_settings(wxMenu* menu, int id, const bool is_part) {
auto menu_item = new wxMenuItem(menu, id, _(L("Add settings")));
menu_item->SetBitmap(m_bmp_cog);
auto sub_menu = create_add_settings_popupmenu(false);
auto sub_menu = create_add_settings_popupmenu(is_part);
menu_item->SetSubMenu(sub_menu);
return menu_item;
}
@ -1005,44 +1078,54 @@ wxMenu *create_add_part_popupmenu()
wxMenu *menu = new wxMenu;
std::vector<std::string> menu_items = { L("Add part"), L("Add modifier"), L("Add generic") };
wxWindowID config_id_base = wxWindow::NewControlId(menu_items.size()+2);
wxWindowID config_id_base = wxWindow::NewControlId(menu_items.size()+4+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);
if (item == "Add generic")
menu_item_add_generic(menu_item, config_id_base + i);
menu->Append(menu_item);
i++;
}
menu->AppendSeparator();
auto menu_item = menu_item_split(menu, config_id_base + i);
auto menu_item = menu_item_split(menu, config_id_base + i + 4);
menu->Append(menu_item);
menu_item->Enable(!cur_item_hase_children());
menu_item->Enable(is_splittable_object(false));
menu->AppendSeparator();
// Append settings popupmenu
menu->Append(menu_item_settings(menu, config_id_base + i + 1));
menu->Append(menu_item_settings(menu, config_id_base + i + 5, false));
wxWindow* win = get_tab_panel()->GetPage(0);
menu->Bind(wxEVT_MENU, [config_id_base, win, menu](wxEvent &event){
menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){
switch (event.GetId() - config_id_base) {
case 0:
on_btn_load(win);
on_btn_load();
break;
case 1:
on_btn_load(win, true);
on_btn_load(true);
break;
case 2:
on_btn_load(win, true, true);
// on_btn_load(true, true);
break;
case 3:
case 4:
case 5:
case 6:
#ifdef __WXMSW__
load_lambda(menu->GetLabel(event.GetId()).ToStdString());
#endif // __WXMSW__
break;
case 7: //3:
on_btn_split(false);
break;
default:{
default:
#ifdef __WXMSW__
get_settings_choice(menu, event.GetId(), false);
break;}
#endif // __WXMSW__
break;
}
});
@ -1054,11 +1137,13 @@ 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));
auto menu_item = menu_item_split(menu, config_id_base);
menu->Append(menu_item);
menu_item->Enable(is_splittable_object(true));
menu->AppendSeparator();
// Append settings popupmenu
menu->Append(menu_item_settings(menu, config_id_base + 1));
menu->Append(menu_item_settings(menu, config_id_base + 1, true));
menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){
switch (event.GetId() - config_id_base) {
@ -1090,35 +1175,35 @@ wxMenu *create_add_settings_popupmenu(bool is_part)
wxNullBitmap : categories.at(cat.first));
menu->Append(menu_item);
}
menu->Bind(wxEVT_MENU, [menu](wxEvent &event) {
get_settings_choice(menu, event.GetId(), true);
#ifndef __WXMSW__
menu->Bind(wxEVT_MENU, [menu,is_part](wxEvent &event) {
get_settings_choice(menu, event.GetId(), is_part);
});
#endif //no __WXMSW__
return menu;
}
void show_context_menu()
{
auto item = m_objects_ctrl->GetSelection();
const auto item = m_objects_ctrl->GetSelection();
if (item)
{
if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
auto menu = create_add_part_popupmenu();
if (m_objects_model->IsSettingsItem(item))
return;
const auto menu = m_objects_model->GetParent(item) == wxDataViewItem(0) ?
create_add_part_popupmenu() :
create_part_settings_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,
void load_part( ModelObject* model_object,
wxArrayString& part_names, const bool is_modifier)
{
wxWindow* parent = get_tab_panel()->GetPage(0);
wxArrayString input_files;
open_model(parent, input_files);
for (int i = 0; i < input_files.size(); ++i) {
@ -1137,7 +1222,7 @@ void load_part( wxWindow* parent, ModelObject* model_object,
for ( auto object : model.objects) {
for (auto volume : object->volumes) {
auto new_volume = model_object->add_volume(*volume);
new_volume->modifier = is_modifier;
new_volume->set_type(is_modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
boost::filesystem::path(input_file).filename().string();
new_volume->name = boost::filesystem::path(input_file).filename().string();
@ -1156,10 +1241,10 @@ void load_part( wxWindow* parent, ModelObject* model_object,
}
}
void load_lambda( wxWindow* parent, ModelObject* model_object,
void load_lambda( ModelObject* model_object,
wxArrayString& part_names, const bool is_modifier)
{
auto dlg = new LambdaObjectDialog(parent);
auto dlg = new LambdaObjectDialog(m_objects_ctrl->GetMainWindow());
if (dlg->ShowModal() == wxID_CANCEL) {
return;
}
@ -1195,7 +1280,8 @@ void load_lambda( wxWindow* parent, ModelObject* model_object,
mesh.repair();
auto new_volume = model_object->add_volume(mesh);
new_volume->modifier = is_modifier;
new_volume->set_type(is_modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
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));
@ -1205,7 +1291,50 @@ void load_lambda( wxWindow* parent, ModelObject* model_object,
m_parts_changed = true;
}
void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/* = false*/)
void load_lambda(const std::string& type_name)
{
if (m_selected_object_id < 0) return;
auto dlg = new LambdaObjectDialog(m_objects_ctrl->GetMainWindow(), type_name);
if (dlg->ShowModal() == wxID_CANCEL)
return;
const std::string name = "lambda-"+type_name;
TriangleMesh mesh;
const auto params = dlg->ObjectParameters();
if (type_name == _("Box"))
mesh = make_cube(params.dim[0], params.dim[1], params.dim[2]);
else if (type_name == _("Cylinder"))
mesh = make_cylinder(params.cyl_r, params.cyl_h);
else if (type_name == _("Sphere"))
mesh = make_sphere(params.sph_rho);
else if (type_name == _("Slab")){
const auto& size = (*m_objects)[m_selected_object_id]->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);
}
mesh.repair();
auto new_volume = (*m_objects)[m_selected_object_id]->add_volume(mesh);
new_volume->set_type(ModelVolume::PARAMETER_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));
m_parts_changed = true;
parts_changed(m_selected_object_id);
m_objects_ctrl->Select(m_objects_model->AddChild(m_objects_ctrl->GetSelection(),
name, m_icon_modifiermesh));
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
object_ctrl_selection_changed();
#endif //no __WXOSX__ //__WXMSW__
}
void on_btn_load(bool is_modifier /*= false*/, bool is_lambda/* = false*/)
{
auto item = m_objects_ctrl->GetSelection();
if (!item)
@ -1219,19 +1348,54 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/
if (obj_idx < 0) return;
wxArrayString part_names;
if (is_lambda)
load_lambda(parent, (*m_objects)[obj_idx], part_names, is_modifier);
load_lambda((*m_objects)[obj_idx], part_names, is_modifier);
else
load_part(parent, (*m_objects)[obj_idx], part_names, is_modifier);
load_part((*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__
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
object_ctrl_selection_changed();
#endif //__WXMSW__
#endif //no __WXOSX__//__WXMSW__
}
void remove_settings_from_config()
{
auto opt_keys = (*m_config)->keys();
if (opt_keys.size() == 1 && opt_keys[0] == "extruder")
return;
int extruder = -1;
if ((*m_config)->has("extruder"))
extruder = (*m_config)->option<ConfigOptionInt>("extruder")->value;
(*m_config)->clear();
if (extruder >=0 )
(*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder));
}
bool remove_subobject_from_object(const int volume_id)
{
const 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->is_model_part())
++solid_cnt;
if (volume->is_model_part() && solid_cnt == 1) {
Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object.")));
return false;
}
(*m_objects)[m_selected_object_id]->delete_volume(volume_id);
m_parts_changed = true;
parts_changed(m_selected_object_id);
return true;
}
void on_btn_del()
@ -1239,50 +1403,67 @@ 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)
const auto volume_id = m_objects_model->GetVolumeIdByItem(item);
if (volume_id ==-1)
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.")));
if (volume_id ==-2)
remove_settings_from_config();
else if (!remove_subobject_from_object(volume_id))
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__
}
bool get_volume_by_item(const bool split_part, const wxDataViewItem& item, ModelVolume*& volume)
{
if (!item || m_selected_object_id < 0)
return false;
const auto volume_id = m_objects_model->GetVolumeIdByItem(item);
if (volume_id < 0) {
if (split_part) return false;
volume = (*m_objects)[m_selected_object_id]->volumes[0];
}
else
volume = (*m_objects)[m_selected_object_id]->volumes[volume_id];
if (volume)
return true;
return false;
}
bool is_splittable_object(const bool split_part)
{
const wxDataViewItem item = m_objects_ctrl->GetSelection();
if (!item) return false;
wxDataViewItemArray children;
if (!split_part && m_objects_model->GetChildren(item, children) > 0)
return false;
ModelVolume* volume;
if (!get_volume_by_item(split_part, item, volume) || !volume)
return false;
TriangleMeshPtrs meshptrs = volume->mesh.split();
if (meshptrs.size() <= 1) {
delete meshptrs.front();
return false;
}
return true;
}
void on_btn_split(const bool split_part)
{
auto item = m_objects_ctrl->GetSelection();
const 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];
if (!get_volume_by_item(split_part, item, volume)) return;
DynamicPrintConfig& config = get_preset_bundle()->printers.get_edited_preset().config;
auto nozzle_dmrs_cnt = config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
auto split_rez = volume->split(nozzle_dmrs_cnt);
if (split_rez == 1) {
const auto nozzle_dmrs_cnt = config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
if (volume->split(nozzle_dmrs_cnt) == 1) {
wxMessageBox(_(L("The selected object couldn't be split because it contains only one part.")));
return;
}
@ -1295,8 +1476,9 @@ void on_btn_split(const bool split_part)
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<ConfigOptionInt>("extruder")->value,
model_object->volumes[id]->is_modifier() ? m_icon_modifiermesh : m_icon_solidmesh,
model_object->volumes[id]->config.has("extruder") ?
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
false);
m_objects_ctrl->Expand(parent);
@ -1305,10 +1487,14 @@ void on_btn_split(const bool split_part)
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<ConfigOptionInt>("extruder")->value,
model_object->volumes[id]->config.has("extruder") ?
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
false);
m_objects_ctrl->Expand(item);
}
m_parts_changed = true;
parts_changed(m_selected_object_id);
}
void on_btn_move_up(){
@ -1389,33 +1575,49 @@ void part_selection_changed()
auto item = m_objects_ctrl->GetSelection();
int obj_idx = -1;
auto og = get_optgroup(ogFrequentlyObjectSettings);
m_config = nullptr;
wxString object_name = wxEmptyString;
if (item)
{
const bool is_settings_item = m_objects_model->IsSettingsItem(item);
bool is_part = false;
wxString og_name = wxEmptyString;
if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
obj_idx = m_objects_model->GetIdByItem(item);
og->set_name(" " + _(L("Object Settings")) + " ");
og_name = _(L("Object manipulation"));
m_config = std::make_shared<DynamicPrintConfig*>(&(*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<DynamicPrintConfig*>(&(*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>(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part)));
if (is_settings_item) {
if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) {
og_name = _(L("Object Settings to modify"));
m_config = std::make_shared<DynamicPrintConfig*>(&(*m_objects)[obj_idx]->config);
}
else {
wxString empty_str = wxEmptyString;
og->set_value("object_name", empty_str);
m_config = nullptr;
og_name = _(L("Part Settings to modify"));
is_part = true;
auto main_parent = m_objects_model->GetParent(parent);
obj_idx = m_objects_model->GetIdByItem(main_parent);
const auto volume_id = m_objects_model->GetVolumeIdByItem(parent);
m_config = std::make_shared<DynamicPrintConfig*>(&(*m_objects)[obj_idx]->volumes[volume_id]->config);
}
}
else {
og_name = _(L("Part manipulation"));
is_part = true;
const auto volume_id = m_objects_model->GetVolumeIdByItem(item);
m_config = std::make_shared<DynamicPrintConfig*>(&(*m_objects)[obj_idx]->volumes[volume_id]->config);
}
}
og->set_name(" " + og_name + " ");
object_name = m_objects_model->GetName(item);
m_default_config = std::make_shared<DynamicPrintConfig>(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part)));
}
og->set_value("object_name", object_name);
update_settings_list();
@ -1561,9 +1763,15 @@ void update_position_values()
auto og = get_optgroup(ogFrequentlyObjectSettings);
auto instance = (*m_objects)[m_selected_object_id]->instances.front();
#if ENABLE_MODELINSTANCE_3D_OFFSET
og->set_value("position_x", int(instance->get_offset(X)));
og->set_value("position_y", int(instance->get_offset(Y)));
og->set_value("position_z", int(instance->get_offset(Z)));
#else
og->set_value("position_x", int(instance->offset(0)));
og->set_value("position_y", int(instance->offset(1)));
og->set_value("position_z", 0);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
void update_position_values(const Vec3d& position)
@ -1638,7 +1846,7 @@ 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)) {
if (m_objects_model->GetParent(item) == wxDataViewItem(0) || m_objects_model->IsSettingsItem(item)) {
event.Veto();
return;
}
@ -1662,7 +1870,7 @@ void on_drop_possible(wxDataViewEvent &event)
// only allow drags for item or background, not containers
if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) ||
event.GetDataFormat() != wxDF_UNICODETEXT)
event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->IsSettingsItem(item))
event.Veto();
}
@ -1672,7 +1880,7 @@ void on_drop(wxDataViewEvent &event)
// only allow drops for item, not containers
if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) ||
event.GetDataFormat() != wxDF_UNICODETEXT) {
event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->IsSettingsItem(item)) {
event.Veto();
return;
}
@ -1700,6 +1908,9 @@ void on_drop(wxDataViewEvent &event)
for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id+=delta, cnt++)
std::swap(volumes[id], volumes[id +delta]);
m_parts_changed = true;
parts_changed(m_selected_object_id);
g_prevent_list_events = false;
}
@ -1716,5 +1927,115 @@ void update_objects_list_extruder_column(int extruders_count)
set_extruder_column_hidden(extruders_count <= 1);
}
void create_double_slider(wxWindow* parent, wxBoxSizer* sizer, wxGLCanvas* canvas)
{
m_slider = new PrusaDoubleSlider(parent, wxID_ANY, 0, 0, 0, 100);
sizer->Add(m_slider, 0, wxEXPAND, 0);
m_preview_canvas = canvas;
m_preview_canvas->Bind(wxEVT_KEY_DOWN, update_double_slider_from_canvas);
m_slider->Bind(wxEVT_SCROLL_CHANGED, [parent](wxEvent& event) {
_3DScene::set_toolpaths_range(m_preview_canvas, m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6);
if (parent->IsShown())
m_preview_canvas->Refresh();
});
}
void fill_slider_values(std::vector<std::pair<int, double>> &values,
const std::vector<double> &layers_z)
{
std::vector<double> layers_all_z = _3DScene::get_current_print_zs(m_preview_canvas, false);
if (layers_all_z.size() == layers_z.size())
for (int i = 0; i < layers_z.size(); i++)
values.push_back(std::pair<int, double>(i+1, layers_z[i]));
else if (layers_all_z.size() > layers_z.size()) {
int cur_id = 0;
for (int i = 0; i < layers_z.size(); i++)
for (int j = cur_id; j < layers_all_z.size(); j++)
if (layers_z[i] - 1e-6 < layers_all_z[j] && layers_all_z[j] < layers_z[i] + 1e-6) {
values.push_back(std::pair<int, double>(j+1, layers_z[i]));
cur_id = j;
break;
}
}
}
void set_double_slider_thumbs( const bool force_sliders_full_range,
const std::vector<double> &layers_z,
const double z_low, const double z_high)
{
// Force slider full range only when slider is created.
// Support selected diapason on the all next steps
if (/*force_sliders_full_range*/z_high == 0.0) {
m_slider->SetLowerValue(0);
m_slider->SetHigherValue(layers_z.size() - 1);
return;
}
for (int i = layers_z.size() - 1; i >= 0; i--)
if (z_low >= layers_z[i]) {
m_slider->SetLowerValue(i);
break;
}
for (int i = layers_z.size() - 1; i >= 0 ; i--)
if (z_high >= layers_z[i]) {
m_slider->SetHigherValue(i);
break;
}
}
void update_double_slider(bool force_sliders_full_range)
{
std::vector<std::pair<int, double>> values;
std::vector<double> layers_z = _3DScene::get_current_print_zs(m_preview_canvas, true);
fill_slider_values(values, layers_z);
const double z_low = m_slider->GetLowerValueD();
const double z_high = m_slider->GetHigherValueD();
m_slider->SetMaxValue(layers_z.size() - 1);
m_slider->SetSliderValues(values);
set_double_slider_thumbs(force_sliders_full_range, layers_z, z_low, z_high);
}
void reset_double_slider()
{
m_slider->SetHigherValue(0);
m_slider->SetLowerValue(0);
}
void update_double_slider_from_canvas(wxKeyEvent& event)
{
if (event.HasModifiers()) {
event.Skip();
return;
}
const auto key = event.GetKeyCode();
if (key == 'U' || key == 'D') {
const int new_pos = key == 'U' ? m_slider->GetHigherValue() + 1 : m_slider->GetHigherValue() - 1;
m_slider->SetHigherValue(new_pos);
if (event.ShiftDown()) m_slider->SetLowerValue(m_slider->GetHigherValue());
}
else if (key == 'S')
m_slider->ChangeOneLayerLock();
else
event.Skip();
}
void show_manipulation_sizer(const bool is_simple_mode)
{
auto item = m_objects_ctrl->GetSelection();
if (!item || !is_simple_mode)
return;
if (m_objects_model->IsSettingsItem(item)) {
m_objects_ctrl->Select(m_objects_model->GetParent(item));
part_selection_changed();
}
}
} //namespace GUI
} //namespace Slic3r

View File

@ -9,13 +9,15 @@ class wxArrayString;
class wxMenu;
class wxDataViewEvent;
class wxKeyEvent;
class wxControl;
class wxGLCanvas;
class wxBitmap;
namespace Slic3r {
class ModelObject;
class Model;
namespace GUI {
//class wxGLCanvas;
enum ogGroup{
ogFrequentlyChangingParameters,
@ -44,6 +46,9 @@ struct OBJECT_PARAMETERS
double slab_z = 0.0;
};
typedef std::map<std::string, wxBitmap> t_category_icon;
inline t_category_icon& get_category_icon();
void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer);
void add_objects_list(wxWindow* parent, wxBoxSizer* sizer);
void add_object_settings(wxWindow* parent, wxBoxSizer* sizer);
@ -66,16 +71,17 @@ void set_object_count(int idx, int count);
void unselect_objects();
// Select current object in the list on c++ side
void select_current_object(int idx);
// Select current volume in the list on c++ side
void select_current_volume(int idx, int vol_idx);
// Remove objects/sub-object from the list
void remove();
//void create_double_slider(wxWindow* parent, wxControl* slider);
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();
bool is_splittable_object(const bool split_part);
void init_mesh_icons();
void set_event_object_selection_changed(const int& event);
@ -87,13 +93,14 @@ void set_objects_from_model(Model &model);
bool is_parts_changed();
bool is_part_settings_changed();
void load_part( wxWindow* parent, ModelObject* model_object,
void load_part( ModelObject* model_object,
wxArrayString& part_names, const bool is_modifier);
void load_lambda(wxWindow* parent, ModelObject* model_object,
void load_lambda( ModelObject* model_object,
wxArrayString& part_names, const bool is_modifier);
void load_lambda( const std::string& type_name);
void on_btn_load(wxWindow* parent, bool is_modifier = false, bool is_lambda = false);
void on_btn_load(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();
@ -126,6 +133,15 @@ void on_drop(wxDataViewEvent &event);
// update extruder column for objects_ctrl according to extruders count
void update_objects_list_extruder_column(int extruders_count);
// Create/Update/Reset double slider on 3dPreview
void create_double_slider(wxWindow* parent, wxBoxSizer* sizer, wxGLCanvas* canvas);
void update_double_slider(bool force_sliders_full_range);
void reset_double_slider();
// update DoubleSlider after keyDown in canvas
void update_double_slider_from_canvas(wxKeyEvent& event);
void show_manipulation_sizer(const bool is_simple_mode);
} //namespace GUI
} //namespace Slic3r
#endif //slic3r_GUI_ObjectParts_hpp_

View File

@ -10,10 +10,12 @@ namespace GUI
{
static wxString dots("", wxConvUTF8);
LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent,
const wxString type_name):
m_type_name(type_name)
{
Create(parent, wxID_ANY, _(L("Lambda Object")),
wxDefaultPosition, wxDefaultSize,
parent->GetScreenPosition(), wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
// instead of double dim[3] = { 1.0, 1.0, 1.0 };
@ -24,11 +26,20 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
sizer = new wxBoxSizer(wxVERTICAL);
// modificator options
if (m_type_name == wxEmptyString) {
m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxCHB_TOP);
sizer->Add(m_modificator_options_book, 1, wxEXPAND| wxALL, 10);
sizer->Add(m_modificator_options_book, 1, wxEXPAND | wxALL, 10);
}
else {
m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
sizer->Add(m_panel, 1, wxEXPAND | wxALL, 10);
}
ConfigOptionDef def;
def.width = 70;
auto optgroup = init_modificator_options_page(_(L("Box")));
if (optgroup){
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 :
@ -37,8 +48,6 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
object_parameters.dim[opt_id] = boost::any_cast<double>(value);
};
ConfigOptionDef def;
def.width = 70;
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("L");
@ -52,8 +61,10 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
def.label = L("H");
option = Option(def, "h");
optgroup->append_single_option_line(option);
}
optgroup = init_modificator_options_page(_(L("Cylinder")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
int val = boost::any_cast<int>(value);
if (opt_key == "cyl_r")
@ -66,14 +77,16 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
def.type = coInt;
def.default_value = new ConfigOptionInt{ 1 };
def.label = L("Radius");
option = Option(def, "cyl_r");
auto 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")));
if (optgroup){
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<double>(value);
@ -83,10 +96,12 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("Rho");
option = Option(def, "sph_rho");
auto option = Option(def, "sph_rho");
optgroup->append_single_option_line(option);
}
optgroup = init_modificator_options_page(_(L("Slab")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
double val = boost::any_cast<double>(value);
if (opt_key == "slab_z")
@ -96,13 +111,16 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
else return;
};
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("H");
option = Option(def, "slab_h");
auto 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)
{
@ -127,8 +145,7 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
}
}));
auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
const auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btn_OK = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
@ -155,9 +172,11 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
// Called from the constructor.
// Create a panel for a rectangular / circular / custom bed shape.
ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(wxString title){
ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wxString& title){
if (!m_type_name.IsEmpty() && m_type_name != title)
return nullptr;
auto panel = new wxPanel(m_modificator_options_book);
auto panel = m_type_name.IsEmpty() ? new wxPanel(m_modificator_options_book) : m_panel;
ConfigOptionsGroupShp optgroup;
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Add")) + " " +title + " " +dots);
@ -165,8 +184,12 @@ ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(wxString
m_optgroups.push_back(optgroup);
if (m_type_name.IsEmpty()) {
panel->SetSizerAndFit(optgroup->sizer);
m_modificator_options_book->AddPage(panel, title);
}
else
panel->SetSizer(optgroup->sizer);
return optgroup;
}

View File

@ -7,6 +7,8 @@
#include <wx/sizer.h>
#include <wx/choicebk.h>
class wxPanel;
namespace Slic3r
{
namespace GUI
@ -14,16 +16,19 @@ namespace GUI
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
class LambdaObjectDialog : public wxDialog
{
wxChoicebook* m_modificator_options_book;
wxChoicebook* m_modificator_options_book = nullptr;
std::vector <ConfigOptionsGroupShp> m_optgroups;
wxString m_type_name;
wxPanel* m_panel = nullptr;
public:
LambdaObjectDialog(wxWindow* parent);
LambdaObjectDialog(wxWindow* parent,
const wxString type_name = wxEmptyString);
~LambdaObjectDialog(){}
bool CanClose() { return true; } // ???
OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; }
ConfigOptionsGroupShp init_modificator_options_page(wxString title);
ConfigOptionsGroupShp init_modificator_options_page(const wxString& title);
// Note whether the window was already closed, so a pending update is not executed.
bool m_already_closed = false;

View File

@ -158,7 +158,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/*
// 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);
grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3);
}
else {
// if the callback provides no sizer for the extra cell, put a spacer

View File

@ -161,8 +161,10 @@ public:
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){
if (staticbox) {
stb = new wxStaticBox(_parent, wxID_ANY, title);
stb->SetFont(bold_font());
}
sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL));
auto num_columns = 1U;
if (label_width != 0) num_columns++;

View File

@ -295,7 +295,7 @@ const std::vector<std::string>& Preset::print_options()
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height",
"min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers",
"min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
"support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",

View File

@ -4,8 +4,6 @@
#include <string>
#include <functional>
#include <wx/string.h>
namespace Slic3r {
/**
@ -44,13 +42,13 @@ public:
}
/// Message shown on the next status update.
virtual void message(const wxString&) = 0;
virtual void message(const std::string&) = 0;
/// Title of the operation.
virtual void title(const wxString&) = 0;
virtual void title(const std::string&) = 0;
/// Formatted message for the next status update. Works just like sprintf.
virtual void message_fmt(const wxString& fmt, ...);
virtual void message_fmt(const std::string& fmt, ...);
/// Set up a cancel callback for the operation if feasible.
virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; }
@ -62,7 +60,7 @@ public:
virtual void cancel() { cancelfunc_(); }
/// Convenience function to call message and status update in one function.
void update(float st, const wxString& msg) {
void update(float st, const std::string& msg) {
message(msg); state(st);
}
};

View File

@ -134,7 +134,7 @@ void ProgressStatusBar::embed(wxFrame *frame)
mf->SetStatusBar(self);
}
void ProgressStatusBar::set_status_text(const std::string& txt)
void ProgressStatusBar::set_status_text(const wxString& txt)
{
self->SetStatusText(wxString::FromUTF8(txt.c_str()));
}

View File

@ -13,6 +13,7 @@ class wxTimerEvent;
class wxStatusBar;
class wxWindow;
class wxFrame;
class wxString;
namespace Slic3r {
@ -46,7 +47,7 @@ public:
inline void remove_cancel_callback() { set_cancel_callback(); }
void run(int rate);
void embed(wxFrame *frame = nullptr);
void set_status_text(const std::string& txt);
void set_status_text(const wxString& txt);
// Temporary methods to satisfy Perl side
void show_cancel_button();

View File

@ -880,6 +880,7 @@ void TabPrint::build()
page = add_options_page(_(L("Support material")), "building.png");
optgroup = page->new_optgroup(_(L("Support material")));
optgroup->append_single_option_line("support_material");
optgroup->append_single_option_line("support_material_auto");
optgroup->append_single_option_line("support_material_threshold");
optgroup->append_single_option_line("support_material_enforce_layers");
@ -1219,13 +1220,15 @@ void TabPrint::update()
bool have_raft = m_config->opt_int("raft_layers") > 0;
bool have_support_material = m_config->opt_bool("support_material") || have_raft;
bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto");
bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0;
bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0;
for (auto el : {"support_material_threshold", "support_material_pattern", "support_material_with_sheath",
for (auto el : {"support_material_pattern", "support_material_with_sheath",
"support_material_spacing", "support_material_angle", "support_material_interface_layers",
"dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance",
"support_material_xy_spacing" })
get_field(el)->toggle(have_support_material);
get_field("support_material_threshold")->toggle(have_support_material_auto);
for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder",
"support_material_interface_speed", "support_material_interface_contact_loops" })

View File

@ -2,6 +2,7 @@
#include "GUI.hpp"
#include "../../libslic3r/Utils.hpp"
#include "BitmapCache.hpp"
#include <wx/sizer.h>
#include <wx/statline.h>
@ -357,11 +358,53 @@ void PrusaObjectDataViewModelNode::set_part_action_icon() {
m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG);
}
Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr;
bool PrusaObjectDataViewModelNode::update_settings_digest(const std::vector<std::string>& categories)
{
if (m_type != "settings" || m_opt_categories == categories)
return false;
m_opt_categories = categories;
m_name = wxEmptyString;
m_icon = m_empty_icon;
auto categories_icon = Slic3r::GUI::get_category_icon();
for (auto& cat : m_opt_categories)
m_name += cat + "; ";
wxBitmap *bmp = m_bitmap_cache->find(m_name.ToStdString());
if (bmp == nullptr) {
std::vector<wxBitmap> bmps;
for (auto& cat : m_opt_categories)
bmps.emplace_back(categories_icon.find(cat) == categories_icon.end() ?
wxNullBitmap : categories_icon.at(cat));
bmp = m_bitmap_cache->insert(m_name.ToStdString(), bmps);
}
m_bmp = *bmp;
return true;
}
// *****************************************************************************
// ----------------------------------------------------------------------------
// PrusaObjectDataViewModel
// ----------------------------------------------------------------------------
PrusaObjectDataViewModel::PrusaObjectDataViewModel()
{
m_bitmap_cache = new Slic3r::GUI::BitmapCache;
}
PrusaObjectDataViewModel::~PrusaObjectDataViewModel()
{
for (auto object : m_objects)
delete object;
delete m_bitmap_cache;
m_bitmap_cache = nullptr;
}
wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name)
{
auto root = new PrusaObjectDataViewModelNode(name);
@ -386,30 +429,46 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int ins
wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_item,
const wxString &name,
const wxIcon& icon,
const wxBitmap& 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);
const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder);
if (root->GetChildren().Count() == 0 && create_frst_child)
if (create_frst_child && (root->GetChildren().Count() == 0 ||
(root->GetChildren().Count() == 1 && root->GetNthChild(0)->m_type == "settings")))
{
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);
const auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);
const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, extruder_str, 0);
root->Append(node);
// notify control
wxDataViewItem child((void*)node);
const wxDataViewItem child((void*)node);
ItemAdded(parent_item, child);
}
auto volume_id = root->GetChildCount();
auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id);
const auto volume_id = root->GetChildCount() > 0 && root->GetNthChild(0)->m_type == "settings" ?
root->GetChildCount() - 1 : root->GetChildCount();
const auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id);
root->Append(node);
// notify control
wxDataViewItem child((void*)node);
const wxDataViewItem child((void*)node);
ItemAdded(parent_item, child);
return child;
}
wxDataViewItem PrusaObjectDataViewModel::AddSettingsChild(const wxDataViewItem &parent_item)
{
PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID();
if (!root) return wxDataViewItem(0);
const auto node = new PrusaObjectDataViewModelNode(root);
root->Insert(node, 0);
// notify control
const wxDataViewItem child((void*)node);
ItemAdded(parent_item, child);
return child;
}
@ -438,7 +497,7 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item)
//update volume_id value for remaining child-nodes
auto children = node_parent->GetChildren();
for (size_t i = 0; i < node_parent->GetChildCount(); i++)
for (size_t i = 0; i < node_parent->GetChildCount() && v_id>=0; i++)
{
auto volume_id = children[i]->GetVolumeId();
if (volume_id > v_id)
@ -460,8 +519,9 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item)
delete node;
// set m_containet to FALSE if parent has no child
if (node_parent && node_parent->GetChildCount() == 0){
if (node_parent) {
#ifndef __WXGTK__
if (node_parent->GetChildCount() == 0)
node_parent->m_container = false;
#endif //__WXGTK__
ret_item = parent;
@ -522,6 +582,26 @@ wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx)
}
wxDataViewItem PrusaObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_idx)
{
if (obj_idx >= m_objects.size()) {
printf("Error! Out of objects range.\n");
return wxDataViewItem(0);
}
auto parent = m_objects[obj_idx];
if (parent->GetChildCount() == 0) {
printf("Error! Object has no one volume.\n");
return wxDataViewItem(0);
}
for (size_t i = 0; i < parent->GetChildCount(); i++)
if (parent->GetNthChild(i)->m_volume_id == volume_idx)
return wxDataViewItem(parent->GetNthChild(i));
return wxDataViewItem(0);
}
int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item)
{
wxASSERT(item.IsOk());
@ -534,7 +614,7 @@ int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item)
return it - m_objects.begin();
}
int PrusaObjectDataViewModel::GetVolumeIdByItem(wxDataViewItem& item)
int PrusaObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item)
{
wxASSERT(item.IsOk());
@ -568,6 +648,12 @@ wxIcon& PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const
return node->m_icon;
}
wxBitmap& PrusaObjectDataViewModel::GetBitmap(const wxDataViewItem &item) const
{
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
return node->m_bmp;
}
void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const
{
wxASSERT(item.IsOk());
@ -576,7 +662,7 @@ void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem
switch (col)
{
case 0:{
const wxDataViewIconText data(node->m_name, node->m_icon);
const PrusaDataViewBitmapText data(node->m_name, node->m_bmp);
variant << data;
break;}
case 1:
@ -667,26 +753,35 @@ wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_i
if (!node_parent) // happens if item.IsOk()==false
return ret_item;
PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id);
const size_t shift = node_parent->GetChildren().Item(0)->m_type == "settings" ? 1 : 0;
PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id+shift);
node_parent->GetChildren().Remove(deleted_node);
ItemDeleted(parent, wxDataViewItem(deleted_node));
node_parent->Insert(deleted_node, new_volume_id);
node_parent->Insert(deleted_node, new_volume_id+shift);
ItemAdded(parent, wxDataViewItem(deleted_node));
const auto settings_item = HasSettings(wxDataViewItem(deleted_node));
if (settings_item)
ItemAdded(wxDataViewItem(deleted_node), settings_item);
//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);
children[id+shift]->SetVolumeId(id);
return wxDataViewItem(node_parent->GetNthChild(new_volume_id));
return wxDataViewItem(node_parent->GetNthChild(new_volume_id+shift));
}
// bool MyObjectTreeModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const
// {
//
// }
bool PrusaObjectDataViewModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const
{
wxASSERT(item.IsOk());
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
// disable extruder selection for the "Settings" item
return !(col == 2 && node->m_extruder.IsEmpty());
}
wxDataViewItem PrusaObjectDataViewModel::GetParent(const wxDataViewItem &item) const
{
@ -738,6 +833,87 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent,
return count;
}
wxDataViewItem PrusaObjectDataViewModel::HasSettings(const wxDataViewItem &item) const
{
if (!item.IsOk())
return wxDataViewItem(0);
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
if (node->GetChildCount() == 0)
return wxDataViewItem(0);
auto& children = node->GetChildren();
if (children[0]->m_type == "settings")
return wxDataViewItem((void*)children[0]);;
return wxDataViewItem(0);
}
bool PrusaObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const
{
if (!item.IsOk())
return false;
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
return node->m_type == "settings";
}
void PrusaObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item,
const std::vector<std::string>& categories)
{
if (!item.IsOk()) return;
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
if (!node->update_settings_digest(categories))
return;
ItemChanged(item);
}
IMPLEMENT_VARIANT_OBJECT(PrusaDataViewBitmapText)
// ---------------------------------------------------------
// PrusaIconTextRenderer
// ---------------------------------------------------------
bool PrusaBitmapTextRenderer::SetValue(const wxVariant &value)
{
m_value << value;
return true;
}
bool PrusaBitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const
{
return false;
}
bool PrusaBitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state)
{
int xoffset = 0;
const wxBitmap& icon = m_value.GetBitmap();
if (icon.IsOk())
{
dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2);
xoffset = icon.GetWidth() + 4;
}
RenderText(m_value.GetText(), xoffset, rect, dc, state);
return true;
}
wxSize PrusaBitmapTextRenderer::GetSize() const
{
if (!m_value.GetText().empty())
{
wxSize size = GetTextExtent(m_value.GetText());
if (m_value.GetBitmap().IsOk())
size.x += m_value.GetBitmap().GetWidth() + 4;
return size;
}
return wxSize(80, 20);
}
// ----------------------------------------------------------------------------
// PrusaDoubleSlider
@ -829,16 +1005,28 @@ wxSize PrusaDoubleSlider::DoGetBestSize() const
void PrusaDoubleSlider::SetLowerValue(const int lower_val)
{
m_selection = ssLower;
m_lower_value = lower_val;
correct_lower_value();
Refresh();
Update();
wxCommandEvent e(wxEVT_SCROLL_CHANGED);
e.SetEventObject(this);
ProcessWindowEvent(e);
}
void PrusaDoubleSlider::SetHigherValue(const int higher_val)
{
m_selection = ssHigher;
m_higher_value = higher_val;
correct_higher_value();
Refresh();
Update();
wxCommandEvent e(wxEVT_SCROLL_CHANGED);
e.SetEventObject(this);
ProcessWindowEvent(e);
}
void PrusaDoubleSlider::SetMaxValue(const int max_value)
@ -905,6 +1093,13 @@ void PrusaDoubleSlider::get_size(int *w, int *h)
is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim;
}
double PrusaDoubleSlider::get_double_value(const SelectedSlider& selection) const
{
if (m_values.empty())
return 0.0;
return m_values[selection == ssLower ? m_lower_value : m_higher_value].second;
}
void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos)
{
const double step = get_scroll_step();
@ -1010,7 +1205,7 @@ wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const
void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const
{
if (m_is_one_layer && selection != m_selection || !selection)
if ((m_is_one_layer || m_higher_value==m_lower_value) && selection != m_selection || !selection)
return;
wxCoord text_width, text_height;
const wxString label = get_label(selection);
@ -1178,6 +1373,20 @@ bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect)
return false;
}
void PrusaDoubleSlider::ChangeOneLayerLock()
{
m_is_one_layer = !m_is_one_layer;
m_selection == ssLower ? correct_lower_value() : correct_higher_value();
if (!m_selection) m_selection = ssHigher;
Refresh();
Update();
wxCommandEvent e(wxEVT_SCROLL_CHANGED);
e.SetEventObject(this);
ProcessWindowEvent(e);
}
void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event)
{
this->CaptureMouse();
@ -1245,6 +1454,10 @@ void PrusaDoubleSlider::OnMotion(wxMouseEvent& event)
Refresh();
Update();
event.Skip();
wxCommandEvent e(wxEVT_SCROLL_CHANGED);
e.SetEventObject(this);
ProcessWindowEvent(e);
}
void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event)

View File

@ -144,6 +144,49 @@ public:
#endif //__WXMSW__
// *****************************************************************************
// ----------------------------------------------------------------------------
// PrusaDataViewBitmapText: helper class used by PrusaBitmapTextRenderer
// ----------------------------------------------------------------------------
class PrusaDataViewBitmapText : public wxObject
{
public:
PrusaDataViewBitmapText(const wxString &text = wxEmptyString,
const wxBitmap& bmp = wxNullBitmap) :
m_text(text), m_bmp(bmp)
{ }
PrusaDataViewBitmapText(const PrusaDataViewBitmapText &other)
: wxObject(),
m_text(other.m_text),
m_bmp(other.m_bmp)
{ }
void SetText(const wxString &text) { m_text = text; }
wxString GetText() const { return m_text; }
void SetBitmap(const wxIcon &icon) { m_bmp = icon; }
const wxBitmap &GetBitmap() const { return m_bmp; }
bool IsSameAs(const PrusaDataViewBitmapText& other) const {
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
}
bool operator==(const PrusaDataViewBitmapText& other) const {
return IsSameAs(other);
}
bool operator!=(const PrusaDataViewBitmapText& other) const {
return !IsSameAs(other);
}
private:
wxString m_text;
wxBitmap m_bmp;
};
DECLARE_VARIANT_OBJECT(PrusaDataViewBitmapText)
// ----------------------------------------------------------------------------
// PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel
// ----------------------------------------------------------------------------
@ -156,6 +199,8 @@ class PrusaObjectDataViewModelNode
PrusaObjectDataViewModelNode* m_parent;
MyObjectTreeModelNodePtrArray m_children;
wxIcon m_empty_icon;
wxBitmap m_empty_bmp;
std::vector< std::string > m_opt_categories;
public:
PrusaObjectDataViewModelNode(const wxString &name, const int instances_count=1) {
m_parent = NULL;
@ -174,19 +219,32 @@ public:
PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent,
const wxString& sub_obj_name,
const wxIcon& icon,
const wxBitmap& bmp,
const wxString& extruder,
const int volume_id=-1) {
m_parent = parent;
m_name = sub_obj_name;
m_copy = wxEmptyString;
m_icon = icon;
m_bmp = bmp;
m_type = "volume";
m_volume_id = volume_id;
m_extruder = extruder;
#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_part_action_icon();
}
PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent) :
m_parent(parent),
m_name("Settings to modified"),
m_copy(wxEmptyString),
m_type("settings"),
m_extruder(wxEmptyString) {}
~PrusaObjectDataViewModelNode()
{
// free all our children nodes
@ -200,9 +258,10 @@ public:
wxString m_name;
wxIcon& m_icon = m_empty_icon;
wxBitmap& m_bmp = m_empty_bmp;
wxString m_copy;
std::string m_type;
int m_volume_id;
int m_volume_id = -2;
bool m_container = false;
wxString m_extruder = "default";
wxBitmap m_action_icon;
@ -226,6 +285,8 @@ public:
}
void Insert(PrusaObjectDataViewModelNode* child, unsigned int n)
{
if (!m_container)
m_container = true;
m_children.Insert(child, n);
}
void Append(PrusaObjectDataViewModelNode* child)
@ -258,9 +319,9 @@ public:
switch (col)
{
case 0:{
wxDataViewIconText data;
PrusaDataViewBitmapText data;
data << variant;
m_icon = data.GetIcon();
m_bmp = data.GetBitmap();
m_name = data.GetText();
return true;}
case 1:
@ -282,6 +343,11 @@ public:
m_icon = icon;
}
void SetBitmap(const wxBitmap &icon)
{
m_bmp = icon;
}
void SetType(const std::string& type){
m_type = type;
}
@ -326,6 +392,7 @@ public:
// Set action icons for node
void set_object_action_icon();
void set_part_action_icon();
bool update_settings_digest(const std::vector<std::string>& categories);
};
// ----------------------------------------------------------------------------
@ -336,26 +403,24 @@ class PrusaObjectDataViewModel :public wxDataViewModel
{
std::vector<PrusaObjectDataViewModelNode*> m_objects;
public:
PrusaObjectDataViewModel(){}
~PrusaObjectDataViewModel()
{
for (auto object : m_objects)
delete object;
}
PrusaObjectDataViewModel();
~PrusaObjectDataViewModel();
wxDataViewItem Add(const wxString &name);
wxDataViewItem Add(const wxString &name, const int instances_count);
wxDataViewItem AddChild(const wxDataViewItem &parent_item,
const wxString &name,
const wxIcon& icon,
const int = 0,
const wxBitmap& icon,
const int extruder = 0,
const bool create_frst_child = true);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
wxDataViewItem Delete(const wxDataViewItem &item);
void DeleteAll();
void DeleteChildren(wxDataViewItem& parent);
wxDataViewItem GetItemById(int obj_idx);
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
int GetIdByItem(wxDataViewItem& item);
int GetVolumeIdByItem(wxDataViewItem& item);
int GetVolumeIdByItem(const wxDataViewItem& item);
bool IsEmpty() { return m_objects.empty(); }
// helper method for wxLog
@ -363,6 +428,7 @@ public:
wxString GetName(const wxDataViewItem &item) const;
wxString GetCopy(const wxDataViewItem &item) const;
wxIcon& GetIcon(const wxDataViewItem &item) const;
wxBitmap& GetBitmap(const wxDataViewItem &item) const;
// helper methods to change the model
@ -383,8 +449,7 @@ public:
int new_volume_id,
const wxDataViewItem &parent);
// virtual bool IsEnabled(const wxDataViewItem &item,
// unsigned int col) const override;
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;
@ -394,8 +459,35 @@ public:
// 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; }
wxDataViewItem HasSettings(const wxDataViewItem &item) const;
bool IsSettingsItem(const wxDataViewItem &item) const;
void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector<std::string>& categories);
};
// ----------------------------------------------------------------------------
// PrusaBitmapTextRenderer
// ----------------------------------------------------------------------------
class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer
{
public:
PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT,
int align = wxDVR_DEFAULT_ALIGNMENT):
wxDataViewCustomRenderer(wxT("wxObject"), mode, align) {}
bool SetValue(const wxVariant &value);
bool GetValue(wxVariant &value) const;
virtual bool Render(wxRect cell, wxDC *dc, int state);
virtual wxSize GetSize() const;
virtual bool HasEditorCtrl() const { return false; }
private:
// wxDataViewIconText m_value;
PrusaDataViewBitmapText m_value;
};
// ----------------------------------------------------------------------------
@ -516,7 +608,7 @@ public:
int maxValue,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxSL_HORIZONTAL,
long style = wxSL_VERTICAL,
const wxValidator& val = wxDefaultValidator,
const wxString& name = wxEmptyString);
~PrusaDoubleSlider(){}
@ -528,6 +620,8 @@ public:
return m_higher_value;
}
int GetActiveValue() const;
double GetLowerValueD() const { return get_double_value(ssLower); }
double GetHigherValueD() const { return get_double_value(ssHigher); }
wxSize DoGetBestSize() const override;
void SetLowerValue(const int lower_val);
void SetHigherValue(const int higher_val);
@ -538,6 +632,7 @@ public:
void SetSliderValues(const std::vector<std::pair<int, double>>& values) {
m_values = values;
}
void ChangeOneLayerLock();
void OnPaint(wxPaintEvent& ){ render();}
void OnLeftDown(wxMouseEvent& event);
@ -583,6 +678,7 @@ protected:
wxCoord get_position_from_value(const int value);
wxSize get_size();
void get_size(int *w, int *h);
double get_double_value(const SelectedSlider& selection) const;
private:
int m_min_value;

View File

@ -231,7 +231,12 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
spi.port = path;
#ifdef __linux__
auto friendly_name = sysfs_tty_prop(name, "product");
spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path;
if (friendly_name) {
spi.is_printer = looks_like_printer(*friendly_name);
spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str();
} else {
spi.friendly_name = path;
}
auto vid = sysfs_tty_prop_hex(name, "idVendor");
auto pid = sysfs_tty_prop_hex(name, "idProduct");
if (vid && pid) {

View File

@ -96,15 +96,12 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz
void set_print_callback_event(Print *print, int id)
%code%{ Slic3r::GUI::set_print_callback_event(print, id); %};
void add_expert_mode_part( SV *ui_parent, SV *ui_sizer,
Model *model,
void set_model_events_from_perl(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,
%code%{ Slic3r::GUI::set_model_events_from_perl(*model,
event_object_selection_changed,
event_object_settings_changed,
event_remove_object,
@ -112,10 +109,8 @@ void add_expert_mode_part( SV *ui_parent, SV *ui_sizer,
void set_objects_from_perl( SV *ui_parent,
SV *frequently_changed_parameters_sizer,
SV *expert_mode_part_sizer,
SV *scrolled_window_sizer,
SV *info_sizer,
SV *btn_export_gcode,
SV *btn_export_stl,
SV *btn_reslice,
SV *btn_print,
SV *btn_send_gcode,
@ -123,10 +118,8 @@ void set_objects_from_perl( SV *ui_parent,
%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"),
(wxBoxSizer *)wxPli_sv_2_object(aTHX_ info_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"),
@ -161,6 +154,9 @@ void unselect_objects()
void select_current_object(int idx)
%code%{ Slic3r::GUI::select_current_object(idx); %};
void select_current_volume(int idx, int vol_idx)
%code%{ Slic3r::GUI::select_current_volume(idx, vol_idx); %};
void remove_obj()
%code%{ Slic3r::GUI::remove(); %};
@ -179,16 +175,28 @@ void desktop_open_datadir_folder()
void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Model *model_dst)
%code%{ Slic3r::fix_model_by_win10_sdk_gui(*model_object_src, *print, *model_dst); %};
void set_3DScene(SV *scene)
%code%{ Slic3r::GUI::set_3DScene((_3DScene *)wxPli_sv_2_object(aTHX_ scene, "Slic3r::Model::3DScene") ); %};
void register_on_request_update_callback(SV* callback)
%code%{ Slic3r::GUI::g_on_request_update_callback.register_callback(callback); %};
void deregister_on_request_update_callback()
%code%{ Slic3r::GUI::g_on_request_update_callback.deregister_callback(); %};
//void create_double_slider(SV *ui_parent, SV *ui_ds)
// %code%{ Slic3r::GUI::create_double_slider( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"),
// (wxControl*)wxPli_sv_2_object(aTHX_ ui_ds, "Wx::Control")); %};
void create_double_slider(SV *ui_parent, SV *ui_sizer, SV *ui_canvas)
%code%{ Slic3r::GUI::create_double_slider( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"),
(wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"),
(wxGLCanvas*)wxPli_sv_2_object(aTHX_ ui_canvas, "Wx::GLCanvas")); %};
void update_double_slider(bool force_sliders_full_range)
%code%{ Slic3r::GUI::update_double_slider(force_sliders_full_range); %};
void reset_double_slider()
%code%{ Slic3r::GUI::reset_double_slider(); %};
void enable_action_buttons(bool enable)
%code%{ Slic3r::GUI::enable_action_buttons(enable); %};
void save_window_size(SV *window, std::string name)
%code%{ Slic3r::GUI::save_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %};
void restore_window_size(SV *window, std::string name)
%code%{ Slic3r::GUI::restore_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %};

View File

@ -767,6 +767,15 @@ get_first_volume_id(canvas, obj_idx)
OUTPUT:
RETVAL
int
get_in_object_volume_id(canvas, scene_vol_idx)
SV *canvas;
int scene_vol_idx;
CODE:
RETVAL = _3DScene::get_in_object_volume_id((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), scene_vol_idx);
OUTPUT:
RETVAL
std::vector<int>
load_model(canvas, model, obj_idx)
SV *canvas;

View File

@ -17,8 +17,6 @@
%code%{ RETVAL = &THIS->thin_fills; %};
Ref<SurfaceCollection> fill_surfaces()
%code%{ RETVAL = &THIS->fill_surfaces; %};
Ref<SurfaceCollection> perimeter_surfaces()
%code%{ RETVAL = &THIS->perimeter_surfaces; %};
Polygons bridged()
%code%{ RETVAL = THIS->bridged; %};
Ref<PolylineCollection> unsupported_bridge_edges()

View File

@ -340,9 +340,19 @@ ModelMaterial::attributes()
%code%{ RETVAL = &THIS->mesh; %};
bool modifier()
%code%{ RETVAL = THIS->modifier; %};
%code%{ RETVAL = THIS->is_modifier(); %};
void set_modifier(bool modifier)
%code%{ THIS->modifier = modifier; %};
%code%{ THIS->set_type(modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); %};
bool model_part()
%code%{ RETVAL = THIS->is_model_part(); %};
bool support_enforcer()
%code%{ RETVAL = THIS->is_support_enforcer(); %};
void set_support_enforcer()
%code%{ THIS->set_type(ModelVolume::SUPPORT_ENFORCER); %};
bool support_blocker()
%code%{ RETVAL = THIS->is_support_blocker(); %};
void set_support_blocker()
%code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %};
size_t split(unsigned int max_extruders);
@ -358,15 +368,28 @@ ModelMaterial::attributes()
%code%{ RETVAL = THIS->rotation; %};
double scaling_factor()
%code%{ RETVAL = THIS->scaling_factor; %};
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec2d* offset()
%code%{ RETVAL = new Vec2d(THIS->get_offset(X), THIS->get_offset(Y)); %};
#else
Ref<Vec2d> offset()
%code%{ RETVAL = &THIS->offset; %};
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
void set_rotation(double val)
%code%{ THIS->rotation = val; THIS->get_object()->invalidate_bounding_box(); %};
void set_scaling_factor(double val)
%code%{ THIS->scaling_factor = val; THIS->get_object()->invalidate_bounding_box(); %};
#if ENABLE_MODELINSTANCE_3D_OFFSET
void set_offset(Vec2d *offset)
%code%{
THIS->set_offset(X, (*offset)(0));
THIS->set_offset(Y, (*offset)(1));
%};
#else
void set_offset(Vec2d *offset)
%code%{ THIS->offset = *offset; %};
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
void transform_polygon(Polygon* polygon) const;

View File

@ -103,6 +103,12 @@ _constant()
%code%{ RETVAL = THIS->print_statistics().total_weight; %};
double total_cost()
%code%{ RETVAL = THIS->print_statistics().total_cost; %};
double total_wipe_tower_cost()
%code%{ RETVAL = THIS->print_statistics().total_wipe_tower_cost; %};
double total_wipe_tower_filament()
%code%{ RETVAL = THIS->print_statistics().total_wipe_tower_filament; %};
int wipe_tower_number_of_toolchanges()
%code%{ RETVAL = THIS->wipe_tower_data().number_of_toolchanges; %};
PrintObjectPtrs* objects()
%code%{ RETVAL = const_cast<PrintObjectPtrs*>(&THIS->objects()); %};
void clear_objects();

View File

@ -3,6 +3,7 @@
%{
#include <xsinit.h>
#include "slic3r/GUI/ProgressStatusBar.hpp"
#include "slic3r/GUI/GUI.hpp"
%}
%name{Slic3r::GUI::ProgressStatusBar} class ProgressStatusBar {
@ -37,7 +38,7 @@
%code%{ THIS->embed(); %};
void SetStatusText(const char *txt)
%code%{ THIS->set_status_text(txt); %};
%code%{ THIS->set_status_text(_(txt)); %};
void SetCancelCallback(SV* callback)
%code%{ THIS->m_perl_cancel_callback.register_callback(callback); THIS->show_cancel_button();%};

View File

@ -48,6 +48,11 @@ trace(level, message)
CODE:
Slic3r::trace(level, message);
void
disable_multi_threading()
CODE:
Slic3r::disable_multi_threading();
void
set_var_dir(dir)
char *dir;