mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'dev' into lm_sla_supports_ui
This commit is contained in:
		
						commit
						3957c5bd8e
					
				
					 70 changed files with 1711 additions and 1163 deletions
				
			
		|  | @ -356,28 +356,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; | ||||
|  |  | |||
|  | @ -74,7 +74,8 @@ sub new { | |||
|     $self->{statusbar}->Embed; | ||||
|     $self->{statusbar}->SetStatusText(L("Version ").$Slic3r::VERSION.L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases")); | ||||
|     # Make the global status bar and its progress indicator available in C++ | ||||
|     $appController->set_global_progress_indicator($self->{statusbar}); | ||||
| #FIXME Vojtech: Merging error | ||||
| #    $appController->set_global_progress_indicator($self->{statusbar}); | ||||
| 
 | ||||
|     $appController->set_model($self->{plater}->{model}); | ||||
|     $appController->set_print($self->{plater}->{print}); | ||||
|  | @ -92,7 +93,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; | ||||
|     } | ||||
|  | @ -105,7 +106,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; | ||||
|  |  | |||
|  | @ -1804,20 +1804,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}->m_wipe_tower_number_of_toolchanges))  if ($is_wipe_tower); | ||||
| 
 | ||||
|         while ( my $label = shift @info) { | ||||
|             my $value = shift @info; | ||||
|             next if $value eq "N/A"; | ||||
|  |  | |||
|  | @ -238,7 +238,7 @@ sub _update { | |||
|             my @expolygons = (); | ||||
|             foreach my $volume (@{$self->{model_object}->volumes}) { | ||||
|                 next if !$volume->mesh; | ||||
|                 next if $volume->modifier; | ||||
|                 next if !$volume->model_part; | ||||
|                 my $expp = $volume->mesh->slice([ $z_cut ])->[0]; | ||||
|                 push @expolygons, @$expp; | ||||
|             } | ||||
|  |  | |||
|  | @ -16,6 +16,8 @@ use base 'Wx::Panel'; | |||
| use constant ICON_OBJECT        => 0; | ||||
| use constant ICON_SOLIDMESH     => 1; | ||||
| use constant ICON_MODIFIERMESH  => 2; | ||||
| use constant ICON_SUPPORT_ENFORCER => 3; | ||||
| use constant ICON_SUPPORT_BLOCKER => 4; | ||||
| 
 | ||||
| sub new { | ||||
|     my ($class, $parent, %params) = @_; | ||||
|  | @ -35,7 +37,7 @@ sub new { | |||
|         y               => 0, | ||||
|         z               => 0, | ||||
|     }; | ||||
|      | ||||
| 
 | ||||
|     # create TreeCtrl | ||||
|     my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100],  | ||||
|         wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT | ||||
|  | @ -46,6 +48,8 @@ sub new { | |||
|         $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG));     # ICON_OBJECT | ||||
|         $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG));   # ICON_SOLIDMESH | ||||
|         $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG));    # ICON_MODIFIERMESH | ||||
|         $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_enforcer.png"), wxBITMAP_TYPE_PNG));    # ICON_SUPPORT_ENFORCER | ||||
|         $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_blocker.png"), wxBITMAP_TYPE_PNG));    # ICON_SUPPORT_BLOCKER | ||||
|          | ||||
|         my $rootId = $tree->AddRoot("Object", ICON_OBJECT); | ||||
|         $tree->SetPlData($rootId, { type => 'object' }); | ||||
|  | @ -89,7 +93,14 @@ sub new { | |||
|     $self->{btn_move_down}->SetFont($Slic3r::GUI::small_font); | ||||
|      | ||||
|     # part settings panel | ||||
|     $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; $self->_update_canvas; }); | ||||
|     $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { | ||||
|         my ($key, $value) = @_; | ||||
|         wxTheApp->CallAfter(sub {  | ||||
|             $self->set_part_type($value) if ($key eq "part_type"); | ||||
|             $self->{part_settings_changed} = 1; | ||||
|             $self->_update_canvas;  | ||||
|         }); | ||||
|     }); | ||||
|     my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL); | ||||
|     $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0); | ||||
| 
 | ||||
|  | @ -225,8 +236,11 @@ sub reload_tree { | |||
|     my $selectedId = $rootId; | ||||
|     foreach my $volume_id (0..$#{$object->volumes}) { | ||||
|         my $volume = $object->volumes->[$volume_id]; | ||||
|          | ||||
|         my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH; | ||||
|         my $icon =  | ||||
|             $volume->modifier ? ICON_MODIFIERMESH :  | ||||
|             $volume->support_enforcer ? ICON_SUPPORT_ENFORCER : | ||||
|             $volume->support_blocker ? ICON_SUPPORT_BLOCKER : | ||||
|             ICON_SOLIDMESH; | ||||
|         my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon); | ||||
|         if ($volume_id == $selected_volume_idx) { | ||||
|             $selectedId = $itemId; | ||||
|  | @ -288,6 +302,8 @@ sub selection_changed { | |||
|      | ||||
|     if (my $itemData = $self->get_selection) { | ||||
|         my ($config, @opt_keys); | ||||
|         my $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_OBJECT; | ||||
|         my $support = 0; | ||||
|         if ($itemData->{type} eq 'volume') { | ||||
|             # select volume in 3D preview | ||||
|             if ($self->{canvas}) { | ||||
|  | @ -301,16 +317,24 @@ sub selection_changed { | |||
|             # attach volume config to settings panel | ||||
|             my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; | ||||
|     | ||||
|             if ($volume->modifier) { | ||||
|             if (! $volume->model_part) { | ||||
|                 $self->{optgroup_movers}->enable; | ||||
|                 if ($volume->support_enforcer || $volume->support_blocker) { | ||||
|                     $support = 1; | ||||
|                     $type = $volume->support_enforcer ?  | ||||
|                         Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER : | ||||
|                         Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER; | ||||
|                 } else { | ||||
|                     $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER; | ||||
|                 } | ||||
|             } else { | ||||
|                 $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART; | ||||
|                 $self->{optgroup_movers}->disable; | ||||
|             } | ||||
|             $config = $volume->config; | ||||
|             $self->{staticbox}->SetLabel('Part Settings'); | ||||
|              | ||||
|             # get default values | ||||
|             @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys}; | ||||
|             @opt_keys = $support ? () : @{Slic3r::Config::PrintRegion->new->get_keys}; | ||||
|         } elsif ($itemData->{type} eq 'object') { | ||||
|             # select nothing in 3D preview | ||||
|              | ||||
|  | @ -323,33 +347,54 @@ sub selection_changed { | |||
|         # get default values | ||||
|         my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); | ||||
| 
 | ||||
|        # decide which settings will be shown by default | ||||
|         # decide which settings will be shown by default | ||||
|         if ($itemData->{type} eq 'object') { | ||||
|             $config->set_ifndef('wipe_into_objects', 0); | ||||
|             $config->set_ifndef('wipe_into_infill', 0); | ||||
|         } | ||||
| 
 | ||||
|         # append default extruder | ||||
|         push @opt_keys, 'extruder'; | ||||
|         $default_config->set('extruder', 0); | ||||
|         $config->set_ifndef('extruder', 0); | ||||
|         if (! $support) { | ||||
|             push @opt_keys, 'extruder'; | ||||
|             $default_config->set('extruder', 0); | ||||
|             $config->set_ifndef('extruder', 0); | ||||
|         } | ||||
|         $self->{settings_panel}->set_type($type); | ||||
|         $self->{settings_panel}->set_default_config($default_config); | ||||
|         $self->{settings_panel}->set_config($config); | ||||
|         $self->{settings_panel}->set_opt_keys(\@opt_keys); | ||||
| 
 | ||||
|         # disable minus icon to remove the settings | ||||
|         if ($itemData->{type} eq 'object') { | ||||
|             $self->{settings_panel}->set_fixed_options([qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)]); | ||||
| 	} else { | ||||
|             $self->{settings_panel}->set_fixed_options([qw(extruder)]); | ||||
|         } | ||||
| 
 | ||||
|         my $fixed_options =  | ||||
|             ($itemData->{type} eq 'object') ? [qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)] : | ||||
|             $support ? [] : [qw(extruder)]; | ||||
|         $self->{settings_panel}->set_fixed_options($fixed_options); | ||||
|         $self->{settings_panel}->enable; | ||||
|     } | ||||
|      | ||||
|     Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas}; | ||||
| } | ||||
| 
 | ||||
| sub set_part_type | ||||
| { | ||||
|     my ($self, $part_type) = @_; | ||||
|     if (my $itemData = $self->get_selection) { | ||||
|         if ($itemData->{type} eq 'volume') { | ||||
|             my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; | ||||
|             if ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER || | ||||
|                 $part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART) {             | ||||
|                 $volume->set_modifier($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER); | ||||
|             } elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER) { | ||||
|                 $volume->set_support_enforcer; | ||||
|             } elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER) { | ||||
|                 $volume->set_support_blocker; | ||||
|             } | ||||
|             # We want the icon of the selected item to be changed as well. | ||||
|             $self->reload_tree($itemData->{volume_id}); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| sub on_btn_load { | ||||
|     my ($self, $is_modifier) = @_; | ||||
|      | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ sub new { | |||
|         $self->{layers}->Closing; | ||||
|          | ||||
|         # save window size | ||||
|         wxTheApp->save_window_pos($self, "object_settings"); | ||||
|         Slic3r::GUI::save_window_size($self, "object_settings"); | ||||
|          | ||||
|         $self->EndModal(wxID_OK); | ||||
|         $self->{parts}->Destroy; | ||||
|  | @ -49,7 +49,7 @@ sub new { | |||
|      | ||||
|     $self->Layout; | ||||
|      | ||||
|     wxTheApp->restore_window_pos($self, "object_settings"); | ||||
|     Slic3r::GUI::restore_window_size($self, "object_settings"); | ||||
|      | ||||
|     return $self; | ||||
| } | ||||
|  |  | |||
|  | @ -7,15 +7,20 @@ use warnings; | |||
| use utf8; | ||||
| 
 | ||||
| use List::Util qw(first); | ||||
| use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG | ||||
|     wxTheApp); | ||||
| use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU); | ||||
| use Wx qw(:misc :sizer :button :combobox wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp); | ||||
| use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_LEFT_DOWN EVT_MENU); | ||||
| use base 'Wx::ScrolledWindow'; | ||||
| 
 | ||||
| use constant ICON_MATERIAL      => 0; | ||||
| use constant ICON_SOLIDMESH     => 1; | ||||
| use constant ICON_MODIFIERMESH  => 2; | ||||
| 
 | ||||
| use constant TYPE_OBJECT        => -1; | ||||
| use constant TYPE_PART          => 0; | ||||
| use constant TYPE_MODIFIER      => 1; | ||||
| use constant TYPE_SUPPORT_ENFORCER => 2; | ||||
| use constant TYPE_SUPPORT_BLOCKER => 3; | ||||
| 
 | ||||
| my %icons = ( | ||||
|     'Advanced'              => 'wand.png', | ||||
|     'Extruders'             => 'funnel.png', | ||||
|  | @ -36,13 +41,14 @@ sub new { | |||
|     $self->{config} = Slic3r::Config->new; | ||||
|     # On change callback. | ||||
|     $self->{on_change} = $params{on_change}; | ||||
|     $self->{type} = TYPE_OBJECT; | ||||
|     $self->{fixed_options} = {}; | ||||
|      | ||||
|     $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); | ||||
|      | ||||
|     $self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL); | ||||
|     $self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0); | ||||
|      | ||||
| 
 | ||||
|     # option selector | ||||
|     { | ||||
|         # create the button | ||||
|  | @ -110,6 +116,16 @@ sub set_opt_keys { | |||
|     $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; | ||||
| } | ||||
| 
 | ||||
| sub set_type { | ||||
|     my ($self, $type) = @_; | ||||
|     $self->{type} = $type; | ||||
|     if ($type == TYPE_SUPPORT_ENFORCER || $type == TYPE_SUPPORT_BLOCKER) { | ||||
|         $self->{btn_add}->Hide; | ||||
|     } else { | ||||
|         $self->{btn_add}->Show; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| sub set_fixed_options { | ||||
|     my ($self, $opt_keys) = @_; | ||||
|     $self->{fixed_options} = { map {$_ => 1} @$opt_keys }; | ||||
|  | @ -121,12 +137,28 @@ sub update_optgroup { | |||
|      | ||||
|     $self->{options_sizer}->Clear(1); | ||||
|     return if !defined $self->{config}; | ||||
|      | ||||
| 
 | ||||
|     if ($self->{type} != TYPE_OBJECT) { | ||||
|         my $label = Wx::StaticText->new($self, -1, "Type:"), | ||||
|         my $selection = [ "Part", "Modifier", "Support Enforcer", "Support Blocker" ]; | ||||
|         my $field = Wx::ComboBox->new($self, -1, $selection->[$self->{type}], wxDefaultPosition, Wx::Size->new(160, -1), $selection, wxCB_READONLY); | ||||
|         my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); | ||||
|         $sizer->Add($label, 1, wxEXPAND | wxALL, 5); | ||||
|         $sizer->Add($field, 0, wxALL, 5); | ||||
|         EVT_COMBOBOX($self, $field, sub { | ||||
|             my $idx = $field->GetSelection;  # get index of selected value | ||||
|             $self->{on_change}->("part_type", $idx) if $self->{on_change}; | ||||
|         }); | ||||
|         $self->{options_sizer}->Add($sizer, 0, wxEXPAND | wxBOTTOM, 0); | ||||
|     } | ||||
| 
 | ||||
|     my %categories = (); | ||||
|     foreach my $opt_key (@{$self->{config}->get_keys}) { | ||||
|         my $category = $Slic3r::Config::Options->{$opt_key}{category}; | ||||
|         $categories{$category} ||= []; | ||||
|         push @{$categories{$category}}, $opt_key; | ||||
|     if ($self->{type} != TYPE_SUPPORT_ENFORCER && $self->{type} != TYPE_SUPPORT_BLOCKER) { | ||||
|         foreach my $opt_key (@{$self->{config}->get_keys}) { | ||||
|             my $category = $Slic3r::Config::Options->{$opt_key}{category}; | ||||
|             $categories{$category} ||= []; | ||||
|             push @{$categories{$category}}, $opt_key; | ||||
|         } | ||||
|     } | ||||
|     foreach my $category (sort keys %categories) { | ||||
|         my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								resources/icons/support_blocker.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/icons/support_blocker.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 656 B | 
							
								
								
									
										
											BIN
										
									
								
								resources/icons/support_enforcer.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/icons/support_enforcer.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 509 B | 
|  | @ -288,7 +288,6 @@ add_library(libslic3r_gui STATIC | |||
|     ${LIBDIR}/slic3r/AppController.hpp | ||||
|     ${LIBDIR}/slic3r/AppController.cpp | ||||
|     ${LIBDIR}/slic3r/AppControllerWx.cpp | ||||
|     ${LIBDIR}/slic3r/Strings.hpp | ||||
| ) | ||||
| 
 | ||||
| add_library(admesh STATIC | ||||
|  | @ -565,7 +564,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" | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -61,12 +61,11 @@ ExPolygonCollection::rotate(double angle, const Point ¢er) | |||
| } | ||||
| 
 | ||||
| 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; | ||||
|  |  | |||
|  | @ -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,18 +225,18 @@ 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(); } | ||||
|     ExtrusionLoopRole     loop_role() const { return m_loop_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.
 | ||||
|     void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; | ||||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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.;         | ||||
|     } | ||||
|  |  | |||
|  | @ -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)); | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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[] = | ||||
|  | @ -1442,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); | ||||
|             } | ||||
|  | @ -1957,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()) | ||||
|  |  | |||
|  | @ -495,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) { | ||||
|                 // Is this volume a modifier volume?
 | ||||
|                 m_volume->modifier = atoi(m_value[1].c_str()) == 1; | ||||
|             } 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?
 | ||||
|                     // "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) { | ||||
|  | @ -822,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) | ||||
|  |  | |||
|  | @ -276,7 +276,6 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| std::string WipeTowerIntegration::prime(GCode &gcodegen) | ||||
| { | ||||
|     assert(m_layer_idx == 0); | ||||
|  | @ -967,17 +966,20 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) | |||
| 
 | ||||
|     // Get filament stats.
 | ||||
|     print.filament_stats.clear(); | ||||
|     print.total_used_filament    = 0.; | ||||
|     print.total_extruded_volume  = 0.; | ||||
|     print.total_weight           = 0.; | ||||
|     print.total_cost             = 0.; | ||||
|     print.total_used_filament       = 0.; | ||||
|     print.total_extruded_volume     = 0.; | ||||
|     print.total_weight              = 0.; | ||||
|     print.total_cost                = 0.; | ||||
|     print.total_wipe_tower_cost     = 0.; | ||||
|     print.total_wipe_tower_filament = 0.; | ||||
|     print.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); | ||||
|     print.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.m_wipe_tower_used_filament[extruder.id()] : 0.f); | ||||
|         double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.m_wipe_tower_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.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament)); | ||||
|         _write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001); | ||||
|         if (filament_weight > 0.) { | ||||
|  | @ -988,8 +990,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) | |||
|                 _write_format(file, "; filament cost = %.1lf\n", filament_cost); | ||||
|             } | ||||
|         } | ||||
|         print.total_used_filament = print.total_used_filament + used_filament; | ||||
|         print.total_extruded_volume = print.total_extruded_volume + extruded_volume; | ||||
|         print.total_used_filament += used_filament; | ||||
|         print.total_extruded_volume += extruded_volume; | ||||
|         print.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; | ||||
|         print.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.total_cost); | ||||
|     _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); | ||||
|  |  | |||
|  | @ -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&); | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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) { | ||||
|  | @ -935,7 +953,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( | |||
| 			else if (wipe_speed < 2210.f) wipe_speed = 4200.f; | ||||
| 			else wipe_speed = std::min(4800.f, wipe_speed + 50.f); | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		float traversed_x = writer.x(); | ||||
| 		if (m_left_to_right) | ||||
| 			writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); | ||||
|  | @ -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; | ||||
|  | @ -1167,6 +1188,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) | ||||
|  | @ -1208,9 +1231,6 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void WipeTowerPrusaMM::make_wipe_tower_square() | ||||
| { | ||||
| 	const float width = m_wipe_tower_width - 3 * m_perimeter_width; | ||||
|  | @ -1234,7 +1254,6 @@ void WipeTowerPrusaMM::make_wipe_tower_square() | |||
| 	plan_tower();				// propagates depth downwards again (width has changed)
 | ||||
| 	for (auto& lay : m_plan)	// depths set, now the spacing
 | ||||
| 		lay.extra_spacing = lay.depth / lay.toolchanges_depth(); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ public: | |||
| 	WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, | ||||
|                      float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging, | ||||
|                      const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) : | ||||
| 		m_wipe_tower_pos(x, y), | ||||
|     m_wipe_tower_pos(x, y), | ||||
| 		m_wipe_tower_width(width), | ||||
| 		m_wipe_tower_rotation_angle(rotation_angle), | ||||
| 		m_y_shift(0.f), | ||||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -21,45 +21,37 @@ class LayerRegion | |||
|     friend class Layer; | ||||
| 
 | ||||
| public: | ||||
|     Layer* layer() { return this->_layer; } | ||||
|     const Layer* layer() const { return this->_layer; } | ||||
|     PrintRegion* region() { return this->_region; } | ||||
|     const PrintRegion* region() const { return this->_region; } | ||||
|     Layer*              layer()        { return this->_layer; } | ||||
|     const Layer*        layer()  const { return this->_layer; } | ||||
|     PrintRegion*        region()       { return this->_region; } | ||||
|     const PrintRegion*  region() const { return this->_region; } | ||||
| 
 | ||||
|     // collection of surfaces generated by slicing the original geometry
 | ||||
|     // divided by type top/bottom/internal
 | ||||
|     SurfaceCollection slices; | ||||
| 
 | ||||
|     // collection of extrusion paths/loops filling gaps
 | ||||
|     // These fills are generated by the perimeter generator.
 | ||||
|     // They are not printed on their own, but they are copied to this->fills during infill generation.
 | ||||
|     ExtrusionEntityCollection thin_fills; | ||||
|     // Collection of surfaces generated by slicing the original geometry, divided by type top/bottom/internal.
 | ||||
|     SurfaceCollection           slices; | ||||
| 
 | ||||
|     // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
 | ||||
|     // and for re-starting of infills.
 | ||||
|     ExPolygons          fill_expolygons; | ||||
|     ExPolygons                  fill_expolygons; | ||||
|     // collection of surfaces for infill generation
 | ||||
|     SurfaceCollection   fill_surfaces; | ||||
|     SurfaceCollection           fill_surfaces; | ||||
|     // Collection of extrusion paths/loops filling gaps.
 | ||||
|     // These fills are generated by the perimeter generator.
 | ||||
|     // They are not printed on their own, but they are copied to this->fills during infill generation.
 | ||||
|     ExtrusionEntityCollection   thin_fills; | ||||
| 
 | ||||
|     // Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces).
 | ||||
|     // While not necessary, the memory consumption is meager and it speeds up calculation.
 | ||||
|     // The perimeter_surfaces keep the IDs of the slices (top/bottom/)
 | ||||
|     SurfaceCollection perimeter_surfaces; | ||||
| 
 | ||||
|     // collection of expolygons representing the bridged areas (thus not
 | ||||
|     // needing support material)
 | ||||
|     Polygons bridged; | ||||
|     // Collection of expolygons representing the bridged areas (thus not needing support material).
 | ||||
|     //FIXME Not used as of now.
 | ||||
|     Polygons                    bridged; | ||||
| 
 | ||||
|     // collection of polylines representing the unsupported bridge edges
 | ||||
|     PolylineCollection unsupported_bridge_edges; | ||||
|     PolylineCollection          unsupported_bridge_edges; | ||||
| 
 | ||||
|     // ordered collection of extrusion paths/loops to build all perimeters
 | ||||
|     // (this collection contains only ExtrusionEntityCollection objects)
 | ||||
|     ExtrusionEntityCollection perimeters; | ||||
| 
 | ||||
|     // ordered collection of extrusion paths to fill surfaces
 | ||||
|     // (this collection contains only ExtrusionEntityCollection objects)
 | ||||
|     ExtrusionEntityCollection fills; | ||||
|     // Ordered collection of extrusion paths/loops to build all perimeters.
 | ||||
|     // This collection contains only ExtrusionEntityCollection objects.
 | ||||
|     ExtrusionEntityCollection   perimeters; | ||||
|     // Ordered collection of extrusion paths to fill surfaces.
 | ||||
|     // This collection contains only ExtrusionEntityCollection objects.
 | ||||
|     ExtrusionEntityCollection   fills; | ||||
|      | ||||
|     Flow flow(FlowRole role, bool bridge = false, double width = -1) const; | ||||
|     void slices_to_fill_surfaces_clipped(); | ||||
|  |  | |||
|  | @ -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 this->_region->flow( | ||||
|         role, | ||||
|  | @ -51,8 +50,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void | ||||
| LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) | ||||
| void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) | ||||
| { | ||||
|     this->perimeters.clear(); | ||||
|     this->thin_fills.clear(); | ||||
|  | @ -340,8 +338,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) | |||
| #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ | ||||
| } | ||||
| 
 | ||||
| void | ||||
| LayerRegion::prepare_fill_surfaces() | ||||
| void LayerRegion::prepare_fill_surfaces() | ||||
| { | ||||
| #ifdef SLIC3R_DEBUG_SLICE_PROCESSING | ||||
|     export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); | ||||
|  | @ -382,8 +379,7 @@ LayerRegion::prepare_fill_surfaces() | |||
| #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ | ||||
| } | ||||
| 
 | ||||
| double | ||||
| LayerRegion::infill_area_threshold() const | ||||
| double LayerRegion::infill_area_threshold() const | ||||
| { | ||||
|     double ss = this->flow(frSolidInfill).scaled_spacing(); | ||||
|     return ss*ss; | ||||
|  |  | |||
|  | @ -627,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) | ||||
|  | @ -658,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; | ||||
| } | ||||
|  | @ -669,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)); | ||||
|         } | ||||
|  | @ -681,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; | ||||
| } | ||||
|  | @ -692,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
 | ||||
|  | @ -798,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; | ||||
| } | ||||
|  | @ -806,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; | ||||
| } | ||||
|  | @ -822,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); | ||||
|  | @ -874,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); | ||||
|  | @ -888,7 +889,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) | ||||
|             { | ||||
|  | @ -985,6 +986,37 @@ 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 == "0") | ||||
|         return MODEL_PART; | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| 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.
 | ||||
|  |  | |||
|  | @ -167,15 +167,27 @@ 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; }; | ||||
|     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; | ||||
|     void set_material(t_model_material_id material_id, const ModelMaterial &material); | ||||
|     void                material_id(t_model_material_id material_id); | ||||
|     ModelMaterial*      material() const; | ||||
|     void                set_material(t_model_material_id material_id, const ModelMaterial &material); | ||||
|     // 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.
 | ||||
|  | @ -186,24 +198,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; | ||||
|     t_model_material_id _material_id; | ||||
|     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) | ||||
|  |  | |||
|  | @ -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()); | ||||
|             spatindex.query(bgi::intersects(querybb), | ||||
|                             std::back_inserter(result)); | ||||
|             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_; | ||||
|  | @ -318,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) { | ||||
|  | @ -327,6 +334,7 @@ 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}); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|  | @ -360,6 +368,7 @@ public: | |||
|                                   bin_area_, | ||||
|                                   norm_, | ||||
|                                   rtree_, | ||||
|                                   smallsrtree_, | ||||
|                                   remaining_); | ||||
| 
 | ||||
|             double score = std::get<0>(result); | ||||
|  | @ -397,6 +406,7 @@ public: | |||
|                                   bin_area_, | ||||
|                                   norm_, | ||||
|                                   rtree_, | ||||
|                                   smallsrtree_, | ||||
|                                   remaining_); | ||||
| 
 | ||||
|             double score = std::get<0>(result); | ||||
|  | @ -440,6 +450,7 @@ public: | |||
|                                   bin_area_, | ||||
|                                   norm_, | ||||
|                                   rtree_, | ||||
|                                   smallsrtree_, | ||||
|                                   remaining_); | ||||
|             double score = std::get<0>(result); | ||||
| 
 | ||||
|  | @ -468,6 +479,7 @@ public: | |||
|                                   0, | ||||
|                                   norm_, | ||||
|                                   rtree_, | ||||
|                                   smallsrtree_, | ||||
|                                   remaining_); | ||||
|             return std::get<0>(result); | ||||
|         }; | ||||
|  |  | |||
|  | @ -35,8 +35,10 @@ 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; } | ||||
|     bool   is_valid() const { return this->points.size() >= 2; } | ||||
| 
 | ||||
|     int  find_point(const Point &point) const; | ||||
|     bool has_boundary_point(const Point &point) const; | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -366,9 +366,12 @@ void Print::add_model_object(ModelObject* model_object, int idx) | |||
|     // Invalidate all print steps.
 | ||||
|     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 < this->regions.size(); ++ i) | ||||
|  | @ -383,6 +386,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.
 | ||||
|  | @ -857,7 +861,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); | ||||
|     } | ||||
| } | ||||
|  | @ -1197,6 +1201,9 @@ void Print::_make_wipe_tower() | |||
|     } | ||||
|     m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>( | ||||
| 		wipe_tower.tool_change((unsigned int)-1, false)); | ||||
| 
 | ||||
|     m_wipe_tower_used_filament = wipe_tower.get_used_filament(); | ||||
|     m_wipe_tower_number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); | ||||
| } | ||||
| 
 | ||||
| std::string Print::output_filename() | ||||
|  |  | |||
|  | @ -80,7 +80,10 @@ public: | |||
| 
 | ||||
|     Print* print() { return this->_print; } | ||||
|     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; | ||||
| 
 | ||||
| private: | ||||
|     Print* _print; | ||||
|  | @ -211,6 +214,10 @@ public: | |||
| 
 | ||||
|     bool is_printable() const { return !this->_shifted_copies.empty(); } | ||||
| 
 | ||||
|     // 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: | ||||
|     Print* _print; | ||||
|     ModelObject* _model_object; | ||||
|  | @ -222,6 +229,7 @@ private: | |||
|     ~PrintObject() {} | ||||
| 
 | ||||
|     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; | ||||
| }; | ||||
| 
 | ||||
| typedef std::vector<PrintObject*> PrintObjectPtrs; | ||||
|  | @ -246,7 +254,7 @@ public: | |||
| 
 | ||||
|     std::string                     estimated_normal_print_time; | ||||
|     std::string                     estimated_silent_print_time; | ||||
|     double                          total_used_filament, total_extruded_volume, total_cost, total_weight; | ||||
|     double                          total_used_filament, total_extruded_volume, total_cost, total_weight, total_wipe_tower_cost, total_wipe_tower_filament; | ||||
|     std::map<size_t, float>         filament_stats; | ||||
|     PrintState<PrintStep, psCount>  state; | ||||
| 
 | ||||
|  | @ -315,6 +323,8 @@ public: | |||
|     std::unique_ptr<WipeTower::ToolChangeResult>          m_wipe_tower_priming; | ||||
|     std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes; | ||||
|     std::unique_ptr<WipeTower::ToolChangeResult>          m_wipe_tower_final_purge; | ||||
|     std::vector<float>                                    m_wipe_tower_used_filament; | ||||
|     int                                                   m_wipe_tower_number_of_toolchanges = -1; | ||||
| 
 | ||||
|     std::string output_filename(); | ||||
|     std::string output_filepath(const std::string &path); | ||||
|  |  | |||
|  | @ -1717,6 +1717,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 +1763,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()); | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -177,6 +177,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" | ||||
|  | @ -1325,29 +1326,62 @@ 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]; | ||||
|         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); | ||||
|             } | ||||
|             if (mesh.stl.stats.number_of_facets > 0) { | ||||
|                 // transform mesh
 | ||||
|                 // we ignore the per-instance transformations currently and only 
 | ||||
|                 // consider the first one
 | ||||
|                 this->model_object()->instances.front()->transform_mesh(&mesh, true); | ||||
|                 // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
 | ||||
|                 mesh.translate(- unscale<float>(this->_copies_shift(0)), - unscale<float>(this->_copies_shift(1)), - float(this->model_object()->bounding_box().min(2))); | ||||
|                 // perform actual slicing
 | ||||
|                 TriangleMeshSlicer mslicer(&mesh); | ||||
|                 mslicer.slice(z, &layers); | ||||
|             } | ||||
|         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 (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 
 | ||||
|             // consider the first one
 | ||||
|             this->model_object()->instances.front()->transform_mesh(&mesh, true); | ||||
|             // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
 | ||||
|             mesh.translate(- unscale<float>(this->_copies_shift(0)), - unscale<float>(this->_copies_shift(1)), - float(this->model_object()->bounding_box().min(2))); | ||||
|             // perform actual slicing
 | ||||
|             TriangleMeshSlicer mslicer(&mesh); | ||||
|             mslicer.slice(z, &layers); | ||||
|         } | ||||
|     } | ||||
|     return layers; | ||||
|  |  | |||
|  | @ -57,4 +57,9 @@ coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const | |||
|             print_config.nozzle_diameter.get_at(this->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(this->config.bridge_flow_ratio.value); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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 
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -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; | ||||
| 		} | ||||
|  |  | |||
|  | @ -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); } | ||||
|  |  | |||
|  | @ -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,10 +626,23 @@ 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"; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :  | ||||
|     mesh(_mesh) | ||||
| { | ||||
|  | @ -698,13 +714,13 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : | |||
|                 } | ||||
|         } | ||||
|         // Assign an edge index to the 1st face.
 | ||||
| 		this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; | ||||
|         this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; | ||||
|         if (found) { | ||||
|             EdgeToFace &edge_j = edges_map[j]; | ||||
| 			this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; | ||||
| 			// Mark the edge as connected.
 | ||||
| 			edge_j.face = -1; | ||||
| 		} | ||||
|             this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; | ||||
|             // Mark the edge as connected.
 | ||||
|             edge_j.face = -1; | ||||
|         } | ||||
|         ++ num_edges; | ||||
|     } | ||||
| } | ||||
|  | @ -771,13 +787,30 @@ 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)); | ||||
|             Polygons  &polygons   = (*layers)[i]; | ||||
|             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; | ||||
|     } | ||||
|  | @ -793,91 +826,94 @@ 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) { | ||||
|                     int               a_id     = vertices[j % 3]; | ||||
|                     int               b_id     = vertices[(j+1) % 3]; | ||||
|                     if (reverse) | ||||
|                         std::swap(a_id, b_id); | ||||
|                     const stl_vertex &a = this->v_scaled_shared[a_id]; | ||||
|                     const stl_vertex &b = this->v_scaled_shared[b_id]; | ||||
|                     il.a(0)    = a(0); | ||||
|                     il.a(1)    = a(1); | ||||
|                     il.b(0)    = b(0); | ||||
|                     il.b(1)    = b(1); | ||||
|                     il.a_id   = a_id; | ||||
|                     il.b_id   = b_id; | ||||
|                     (*lines)[layer_idx].emplace_back(il); | ||||
|                 } | ||||
|                 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) | ||||
|                             std::swap(a_id, b_id); | ||||
|                         const stl_vertex &a = this->v_scaled_shared[a_id]; | ||||
|                         const stl_vertex &b = this->v_scaled_shared[b_id]; | ||||
|                         il.a(0)    = a(0); | ||||
|                         il.a(1)    = a(1); | ||||
|                         il.b(0)    = b(0); | ||||
|                         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 | ||||
|                 (*lines)[layer_idx].emplace_back(il); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void | ||||
| TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const | ||||
| void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const | ||||
| { | ||||
|     std::vector<Polygons> layers_p; | ||||
|     this->slice(z, &layers_p); | ||||
|      | ||||
| 	BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start"; | ||||
| 	layers->resize(z.size()); | ||||
| 	tbb::parallel_for( | ||||
| 		tbb::blocked_range<size_t>(0, z.size()), | ||||
| 		[&layers_p, layers, this](const tbb::blocked_range<size_t>& range) { | ||||
|     		for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { | ||||
|     BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start"; | ||||
|     layers->resize(z.size()); | ||||
|     tbb::parallel_for( | ||||
|         tbb::blocked_range<size_t>(0, z.size()), | ||||
|         [&layers_p, layers, this](const tbb::blocked_range<size_t>& range) { | ||||
|             for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { | ||||
| #ifdef SLIC3R_TRIANGLEMESH_DEBUG | ||||
|     			printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); | ||||
|                 printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); | ||||
| #endif | ||||
|                 this->make_expolygons(layers_p[layer_id], &(*layers)[layer_id]); | ||||
|     		} | ||||
|     	}); | ||||
| 	BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; | ||||
|             } | ||||
|         }); | ||||
|     BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; | ||||
| } | ||||
| 
 | ||||
| // 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)
 | ||||
|     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
 | ||||
|     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               a_id     = vertices[j % 3]; | ||||
|         int               b_id     = vertices[(j+1) % 3]; | ||||
|         const stl_vertex &a = this->v_scaled_shared[a_id]; | ||||
|  | @ -890,116 +926,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.
 | ||||
|                 line_out->edge_type = feTop; | ||||
|                 swap = true; | ||||
|             } else { | ||||
|                 // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
 | ||||
|                 line_out->edge_type = feBottom; | ||||
|                 // 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 | ||||
|                     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; | ||||
|             IntersectionPoint &point = points[num_points ++]; | ||||
|             point(0)       = a(0); | ||||
|             point(1)       = a(1); | ||||
|             point.point_id  = a_id; | ||||
|             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; | ||||
|             IntersectionPoint &point = points[num_points ++]; | ||||
|             point(0)       = b(0); | ||||
|             point(1)       = b(1); | ||||
|             point.point_id  = b_id; | ||||
|             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)); | ||||
|             point.edge_id   = edge_id; | ||||
|             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() {}; | ||||
|  | @ -1022,7 +1221,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) | ||||
|  | @ -1039,13 +1238,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; | ||||
|  | @ -1066,7 +1266,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; | ||||
|                             } | ||||
|  | @ -1078,7 +1278,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; | ||||
|                             } | ||||
|  | @ -1109,7 +1309,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(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -1176,12 +1376,12 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo | |||
|                             } | ||||
|                     } | ||||
|                 } | ||||
| 				if (next_start == nullptr) { | ||||
| 					// The current loop could not be closed. Unmark the segment.
 | ||||
| 					opl.consumed = false; | ||||
| 					break; | ||||
| 				} | ||||
| 				// Attach this polyline to the end of the initial polyline.
 | ||||
|                 if (next_start == nullptr) { | ||||
|                     // The current loop could not be closed. Unmark the segment.
 | ||||
|                     opl.consumed = false; | ||||
|                     break; | ||||
|                 } | ||||
|                 // Attach this polyline to the end of the initial polyline.
 | ||||
|                 if (next_start->start) { | ||||
|                     auto it = next_start->polyline->points.begin(); | ||||
|                     std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); | ||||
|  | @ -1201,8 +1401,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) { | ||||
|  | @ -1217,9 +1417,9 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo | |||
|                         loops->emplace_back(std::move(opl.points)); | ||||
|                     } | ||||
|                     opl.points.clear(); | ||||
| 					break; | ||||
|                     break; | ||||
|                 } | ||||
| 				// Continue with the current loop.
 | ||||
|                 // Continue with the current loop.
 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -1267,15 +1467,15 @@ void TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &l | |||
|         if (slice_idx == -1) | ||||
|             // Ignore this hole.
 | ||||
|             continue; | ||||
| 		assert(current_contour_area < std::numeric_limits<double>::max() && current_contour_area >= -hole->area()); | ||||
| 		(*slices)[slice_idx].holes.emplace_back(std::move(*hole)); | ||||
|         assert(current_contour_area < std::numeric_limits<double>::max() && current_contour_area >= -hole->area()); | ||||
|         (*slices)[slice_idx].holes.emplace_back(std::move(*hole)); | ||||
|     } | ||||
| 
 | ||||
| #if 0 | ||||
|     // If the input mesh is not valid, the holes may intersect with the external contour.
 | ||||
|     // Rather subtract them from the outer contour.
 | ||||
|     Polygons poly; | ||||
| 	for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { | ||||
|     for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { | ||||
|         if (it_slice->holes.empty()) { | ||||
|             poly.emplace_back(std::move(it_slice->contour)); | ||||
|         } else { | ||||
|  | @ -1285,7 +1485,7 @@ void TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &l | |||
|                 it->reverse(); | ||||
|             polygons_append(poly, diff(contours, it_slice->holes)); | ||||
|         } | ||||
| 	} | ||||
|     } | ||||
|     // If the input mesh is not valid, the input contours may intersect.
 | ||||
|     *slices = union_ex(poly); | ||||
| #endif | ||||
|  | @ -1402,7 +1602,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); | ||||
|  |  | |||
|  | @ -82,7 +82,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.
 | ||||
|  | @ -116,6 +116,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.
 | ||||
|  | @ -124,11 +132,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; | ||||
|  | @ -139,7 +159,12 @@ public: | |||
|     TriangleMeshSlicer(TriangleMesh* _mesh); | ||||
|     void slice(const std::vector<float> &z, std::vector<Polygons>* layers) const; | ||||
|     void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) 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; | ||||
|      | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -24,6 +24,8 @@ | |||
| #include <boost/nowide/integration/filesystem.hpp> | ||||
| #include <boost/nowide/convert.hpp> | ||||
| 
 | ||||
| #include <tbb/task_scheduler_init.h> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; | ||||
|  | @ -82,6 +84,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) | ||||
|  |  | |||
|  | @ -11,13 +11,11 @@ | |||
| #include <ModelArrange.hpp> | ||||
| #include <slic3r/GUI/PresetBundle.hpp> | ||||
| 
 | ||||
| #include <Geometry.hpp> | ||||
| #include <PrintConfig.hpp> | ||||
| #include <Print.hpp> | ||||
| #include <PrintExport.hpp> | ||||
| #include <Geometry.hpp> | ||||
| #include <Model.hpp> | ||||
| #include <Utils.hpp> | ||||
| #include <SLABasePool.hpp> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -45,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; | ||||
|  | @ -73,336 +62,8 @@ void AppControllerBoilerplate::global_progress_indicator( | |||
|     pri_data_->m.unlock(); | ||||
| } | ||||
| 
 | ||||
| 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(); | ||||
| 
 | ||||
| //    });
 | ||||
| } | ||||
| 
 | ||||
| void ProgressIndicator::message_fmt( | ||||
|         const string &fmtstr, ...) { | ||||
|         const std::string &fmtstr, ...) { | ||||
|     std::stringstream ss; | ||||
|     va_list args; | ||||
|     va_start(args, fmtstr); | ||||
|  | @ -433,80 +94,77 @@ const PrintConfig &PrintController::config() const | |||
|     return print_->config; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void AppController::arrange_model() | ||||
| { | ||||
|     using Coord = libnest2d::TCoord<libnest2d::PointImpl>; | ||||
|     auto ftr = std::async( | ||||
|                supports_asynch()? std::launch::async : std::launch::deferred, | ||||
|                [this]() | ||||
|     { | ||||
|         using Coord = libnest2d::TCoord<libnest2d::PointImpl>; | ||||
| 
 | ||||
|     if(arranging_.load()) return; | ||||
|         unsigned count = 0; | ||||
|         for(auto obj : model_->objects) count += obj->instances.size(); | ||||
| 
 | ||||
|     // to prevent UI reentrancies
 | ||||
|     arranging_.store(true); | ||||
|         auto pind = global_progress_indicator(); | ||||
| 
 | ||||
|     unsigned count = 0; | ||||
|     for(auto obj : model_->objects) count += obj->instances.size(); | ||||
|         float pmax = 1.0; | ||||
| 
 | ||||
|     auto pind = global_progress_indicator(); | ||||
|         if(pind) { | ||||
|             pmax = pind->max(); | ||||
| 
 | ||||
|     float pmax = 1.0; | ||||
|             // Set the range of the progress to the object count
 | ||||
|             pind->max(count); | ||||
| 
 | ||||
|     if(pind) { | ||||
|         pmax = pind->max(); | ||||
|         } | ||||
| 
 | ||||
|         // Set the range of the progress to the object count
 | ||||
|         pind->max(count); | ||||
|         auto dist = print_ctl()->config().min_object_distance(); | ||||
| 
 | ||||
|         pind->on_cancel([this](){ | ||||
|             arranging_.store(false); | ||||
|         }); | ||||
|         // Create the arranger config
 | ||||
|         auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR); | ||||
| 
 | ||||
|         auto& bedpoints = print_ctl()->config().bed_shape.values; | ||||
|         Polyline bed; bed.points.reserve(bedpoints.size()); | ||||
|         for(auto& v : bedpoints) | ||||
|             bed.append(Point::new_scale(v(0), v(1))); | ||||
| 
 | ||||
|         if(pind) pind->update(0, L("Arranging objects...")); | ||||
| 
 | ||||
|         try { | ||||
|             arr::BedShapeHint hint; | ||||
|             // TODO: from Sasha from GUI
 | ||||
|             hint.type = arr::BedShapeType::WHO_KNOWS; | ||||
| 
 | ||||
| //FIXME merge error
 | ||||
| /*
 | ||||
|             arr::arrange(*model_, | ||||
|                          min_obj_distance, | ||||
|                          bed, | ||||
|                          hint, | ||||
|                          false, // create many piles not just one pile
 | ||||
|                          [pind, count](unsigned rem) { | ||||
|                 if(pind) | ||||
|                     pind->update(count - rem, L("Arranging objects...")); | ||||
|             }); | ||||
| */ | ||||
|         } 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")); | ||||
|         } | ||||
| 
 | ||||
|         // Restore previous max value
 | ||||
|         if(pind) { | ||||
|             pind->max(pmax); | ||||
|             pind->update(0, L("Arranging done.")); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     while( ftr.wait_for(std::chrono::milliseconds(10)) | ||||
|            != std::future_status::ready) { | ||||
|         process_events(); | ||||
|     } | ||||
| 
 | ||||
|     auto dist = print_ctl()->config().min_object_distance(); | ||||
| 
 | ||||
|     // Create the arranger config
 | ||||
|     auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR); | ||||
| 
 | ||||
|     auto& bedpoints = print_ctl()->config().bed_shape.values; | ||||
|     Polyline bed; bed.points.reserve(bedpoints.size()); | ||||
|     for(auto& v : bedpoints) | ||||
|         bed.append(Point::new_scale(v(0), v(1))); | ||||
| 
 | ||||
|     if(pind) pind->update(0, _(L("Arranging objects..."))); | ||||
| 
 | ||||
|     try { | ||||
|         arr::BedShapeHint hint; | ||||
|         // TODO: from Sasha from GUI
 | ||||
|         hint.type = arr::BedShapeType::WHO_KNOWS; | ||||
| 
 | ||||
|         arr::arrange(*model_, | ||||
|                       min_obj_distance, | ||||
|                       bed, | ||||
|                       hint, | ||||
|                       false, // create many piles not just one pile
 | ||||
|                       [this, pind, count](unsigned rem) { | ||||
|             if(pind) | ||||
|                 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"))); | ||||
|     } | ||||
| 
 | ||||
|     // Restore previous max value
 | ||||
|     if(pind) { | ||||
|         pind->max(pmax); | ||||
|         pind->update(0, arranging_.load() ? L("Arranging done.") : | ||||
|                                             L("Arranging canceled.")); | ||||
| 
 | ||||
|         pind->on_cancel(/*remove cancel function*/); | ||||
|     } | ||||
| 
 | ||||
|     arranging_.store(false); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ class Print; | |||
| class PrintObject; | ||||
| class PrintConfig; | ||||
| class ProgressStatusBar; | ||||
| class DynamicPrintConfig; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief A boilerplate class for creating application logic. It should provide | ||||
|  | @ -46,7 +47,7 @@ public: | |||
|     AppControllerBoilerplate(); | ||||
|     ~AppControllerBoilerplate(); | ||||
| 
 | ||||
|     using Path = string; | ||||
|     using Path = std::string; | ||||
|     using PathList = std::vector<Path>; | ||||
| 
 | ||||
|     /// Common runtime issue types
 | ||||
|  | @ -67,20 +68,20 @@ public: | |||
|      * @return Returns a list of paths choosed by the user. | ||||
|      */ | ||||
|     PathList query_destination_paths( | ||||
|             const string& 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 string& title) const; | ||||
|             const std::string& title) const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * @brief Same as query_destination_paths but returns only one path. | ||||
|      */ | ||||
|     Path query_destination_path( | ||||
|             const string& title, | ||||
|             const std::string& title, | ||||
|             const std::string& extensions, | ||||
|             const std::string& hint = "") const; | ||||
| 
 | ||||
|  | @ -95,11 +96,11 @@ public: | |||
|      * title. | ||||
|      */ | ||||
|     bool report_issue(IssueType issuetype, | ||||
|                       const string& description, | ||||
|                       const string& brief); | ||||
|                       const std::string& description, | ||||
|                       const std::string& brief); | ||||
| 
 | ||||
|     bool report_issue(IssueType issuetype, | ||||
|                       const string& description); | ||||
|                       const std::string& description); | ||||
| 
 | ||||
|     /**
 | ||||
|      * @brief Return the global progress indicator for the current controller. | ||||
|  | @ -150,12 +151,12 @@ protected: | |||
|      */ | ||||
|     ProgresIndicatorPtr create_progress_indicator( | ||||
|             unsigned statenum, | ||||
|             const string& title, | ||||
|             const string& firstmsg) const; | ||||
|             const std::string& title, | ||||
|             const std::string& firstmsg) const; | ||||
| 
 | ||||
|     ProgresIndicatorPtr create_progress_indicator( | ||||
|             unsigned statenum, | ||||
|             const string& 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.
 | ||||
|  | @ -167,43 +168,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
 | ||||
|  | @ -218,15 +182,9 @@ 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(); | ||||
|     //FIXME Vojtech: Merging error
 | ||||
|     void slice() {} | ||||
|     void slice_to_png() {} | ||||
| 
 | ||||
|     const PrintConfig& config() const; | ||||
| }; | ||||
|  | @ -237,7 +195,6 @@ public: | |||
| class AppController: public AppControllerBoilerplate { | ||||
|     Model *model_ = nullptr; | ||||
|     PrintController::Ptr printctl; | ||||
|     std::atomic<bool> arranging_; | ||||
| public: | ||||
| 
 | ||||
|     /**
 | ||||
|  | @ -273,14 +230,15 @@ 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. | ||||
|      * | ||||
|      * @param gauge_id The ID of the gague widget of the status bar. | ||||
|      * @param statusbar_id The ID of the status bar. | ||||
|      */ | ||||
|     void set_global_progress_indicator(ProgressStatusBar *prs); | ||||
|     void set_global_progress_indicator(unsigned gauge_id, | ||||
|                                           unsigned statusbar_id); | ||||
| 
 | ||||
|     void arrange_model(); | ||||
| }; | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ | |||
| #include <future> | ||||
| 
 | ||||
| #include <slic3r/GUI/GUI.hpp> | ||||
| #include <slic3r/GUI/ProgressStatusBar.hpp> | ||||
| 
 | ||||
| #include <wx/app.h> | ||||
| #include <wx/filedlg.h> | ||||
|  | @ -28,16 +27,16 @@ bool AppControllerBoilerplate::supports_asynch() const | |||
| 
 | ||||
| void AppControllerBoilerplate::process_events() | ||||
| { | ||||
|     wxYieldIfNeeded(); | ||||
|     wxSafeYield(); | ||||
| } | ||||
| 
 | ||||
| AppControllerBoilerplate::PathList | ||||
| AppControllerBoilerplate::query_destination_paths( | ||||
|         const string &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 +52,11 @@ AppControllerBoilerplate::query_destination_paths( | |||
| 
 | ||||
| AppControllerBoilerplate::Path | ||||
| AppControllerBoilerplate::query_destination_path( | ||||
|         const string &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 +71,8 @@ AppControllerBoilerplate::query_destination_path( | |||
| } | ||||
| 
 | ||||
| bool AppControllerBoilerplate::report_issue(IssueType issuetype, | ||||
|                                  const string &description, | ||||
|                                  const string &brief) | ||||
|                                  const std::string &description, | ||||
|                                  const std::string &brief) | ||||
| { | ||||
|     auto icon = wxICON_INFORMATION; | ||||
|     auto style = wxOK|wxCENTRE; | ||||
|  | @ -85,15 +84,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 string &description) | ||||
|         const std::string &description) | ||||
| { | ||||
|     return report_issue(issuetype, description, string()); | ||||
|     return report_issue(issuetype, description, std::string()); | ||||
| } | ||||
| 
 | ||||
| wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent); | ||||
|  | @ -137,8 +136,8 @@ public: | |||
|     /// Get the mode of parallel operation.
 | ||||
|     inline bool asynch() const { return is_asynch_; } | ||||
| 
 | ||||
|     inline GuiProgressIndicator(int range, const string& title, | ||||
|                                 const string& firstmsg) : | ||||
|     inline GuiProgressIndicator(int range, const wxString& title, | ||||
|                                 const wxString& firstmsg) : | ||||
|         gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(), | ||||
|                wxPD_APP_MODAL | wxPD_AUTO_HIDE), | ||||
|         message_(firstmsg), | ||||
|  | @ -152,11 +151,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 +165,28 @@ public: | |||
|         } else _state(st); | ||||
|     } | ||||
| 
 | ||||
|     virtual void message(const string & msg) override { | ||||
|         message_ = msg; | ||||
|     virtual void message(const std::string & msg) override { | ||||
|         message_ = _(msg); | ||||
|     } | ||||
| 
 | ||||
|     virtual void messageFmt(const string& 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 string & title) override { | ||||
|         title_ = title; | ||||
|     virtual void title(const std::string & title) override { | ||||
|         title_ = _(title); | ||||
|     } | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| AppControllerBoilerplate::ProgresIndicatorPtr | ||||
| AppControllerBoilerplate::create_progress_indicator( | ||||
|         unsigned statenum, const string& title, const string& firstmsg) const | ||||
|         unsigned statenum, | ||||
|         const std::string& title, | ||||
|         const std::string& firstmsg) const | ||||
| { | ||||
|     auto pri = | ||||
|             std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg); | ||||
|  | @ -203,29 +199,39 @@ AppControllerBoilerplate::create_progress_indicator( | |||
| } | ||||
| 
 | ||||
| AppControllerBoilerplate::ProgresIndicatorPtr | ||||
| AppControllerBoilerplate::create_progress_indicator(unsigned statenum, | ||||
|                                                     const string &title) const | ||||
| AppControllerBoilerplate::create_progress_indicator( | ||||
|         unsigned statenum, const std::string &title) const | ||||
| { | ||||
|     return create_progress_indicator(statenum, title, string()); | ||||
|     return create_progress_indicator(statenum, title, std::string()); | ||||
| } | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| // A wrapper progress indicator class around the statusbar created in perl.
 | ||||
| class Wrapper: public ProgressIndicator, public wxEvtHandler { | ||||
|     ProgressStatusBar *sbar_; | ||||
|     wxGauge *gauge_; | ||||
|     wxStatusBar *stbar_; | ||||
|     using Base = ProgressIndicator; | ||||
|     std::string message_; | ||||
|     wxString message_; | ||||
|     AppControllerBoilerplate& ctl_; | ||||
| 
 | ||||
|     void showProgress(bool show = true) { | ||||
|         sbar_->show_progress(show); | ||||
|         gauge_->Show(show); | ||||
|     } | ||||
| 
 | ||||
|     void _state(unsigned st) { | ||||
|         if( st <= ProgressIndicator::max() ) { | ||||
|             Base::state(st); | ||||
|             sbar_->set_status_text(message_); | ||||
|             sbar_->set_progress(st); | ||||
| 
 | ||||
|             if(!gauge_->IsShown()) showProgress(true); | ||||
| 
 | ||||
|             stbar_->SetStatusText(message_); | ||||
|             if(static_cast<long>(st) == gauge_->GetRange()) { | ||||
|                 gauge_->SetValue(0); | ||||
|                 showProgress(false); | ||||
|             } else { | ||||
|                 gauge_->SetValue(static_cast<int>(st)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -238,12 +244,12 @@ class Wrapper: public ProgressIndicator, public wxEvtHandler { | |||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     inline Wrapper(ProgressStatusBar *sbar, | ||||
|     inline Wrapper(wxGauge *gauge, wxStatusBar *stbar, | ||||
|                    AppControllerBoilerplate& ctl): | ||||
|         sbar_(sbar), ctl_(ctl) | ||||
|         gauge_(gauge), stbar_(stbar), ctl_(ctl) | ||||
|     { | ||||
|         Base::max(static_cast<float>(sbar_->get_range())); | ||||
|         Base::states(static_cast<unsigned>(sbar_->get_range())); | ||||
|         Base::max(static_cast<float>(gauge->GetRange())); | ||||
|         Base::states(static_cast<unsigned>(gauge->GetRange())); | ||||
| 
 | ||||
|         Bind(PROGRESS_STATUS_UPDATE_EVENT, | ||||
|              &Wrapper::_state, | ||||
|  | @ -256,7 +262,7 @@ public: | |||
| 
 | ||||
|     virtual void max(float val) override { | ||||
|         if(val > 1.0) { | ||||
|             sbar_->set_range(static_cast<int>(val)); | ||||
|             gauge_->SetRange(static_cast<int>(val)); | ||||
|             ProgressIndicator::max(val); | ||||
|         } | ||||
|     } | ||||
|  | @ -271,32 +277,31 @@ public: | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     virtual void message(const string & msg) override { | ||||
|         message_ = msg; | ||||
|     virtual void message(const std::string & msg) override { | ||||
|         message_ = _(msg); | ||||
|     } | ||||
| 
 | ||||
|     virtual void message_fmt(const string& 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 string & /*title*/) override {} | ||||
| 
 | ||||
|     virtual void on_cancel(CancelFn fn) override { | ||||
|         sbar_->set_cancel_callback(fn); | ||||
|         Base::on_cancel(fn); | ||||
|     } | ||||
|     virtual void title(const std::string & /*title*/) override {} | ||||
| 
 | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| void AppController::set_global_progress_indicator(ProgressStatusBar *prsb) | ||||
| void AppController::set_global_progress_indicator( | ||||
|         unsigned gid, | ||||
|         unsigned sid) | ||||
| { | ||||
|     if(prsb) { | ||||
|         global_progress_indicator(std::make_shared<Wrapper>(prsb, *this)); | ||||
|     wxGauge* gauge = dynamic_cast<wxGauge*>(wxWindow::FindWindowById(gid)); | ||||
|     wxStatusBar* sb = dynamic_cast<wxStatusBar*>(wxWindow::FindWindowById(sid)); | ||||
| 
 | ||||
|     if(gauge && sb) { | ||||
|         global_progressind_ = std::make_shared<Wrapper>(gauge, sb, *this); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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,15 +684,15 @@ 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 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -72,6 +72,14 @@ public: | |||
| 	bool				has(const std::string &key) const | ||||
| 		{ return this->has("", key); } | ||||
| 
 | ||||
| 	void				erase(const std::string §ion, 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 §ion) | ||||
| 		{ m_storage[section].clear(); } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
| 	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); | ||||
| 	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(); }); | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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> | ||||
|  | @ -1226,12 +1227,63 @@ 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) | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ class wxButton; | |||
| class wxFileDialog; | ||||
| class wxStaticBitmap; | ||||
| class wxFont; | ||||
| class wxTopLevelWindow; | ||||
| 
 | ||||
| namespace Slic3r {  | ||||
| 
 | ||||
|  | @ -223,7 +224,12 @@ 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); | ||||
|  |  | |||
|  | @ -1225,7 +1225,7 @@ void load_part(	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(); | ||||
| 
 | ||||
|  | @ -1283,7 +1283,8 @@ void load_lambda(	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)); | ||||
|  | @ -1320,7 +1321,8 @@ void load_lambda(const std::string& type_name) | |||
|     mesh.repair(); | ||||
| 
 | ||||
|     auto new_volume = (*m_objects)[m_selected_object_id]->add_volume(mesh); | ||||
|     new_volume->modifier = true; | ||||
|     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)); | ||||
|  | @ -1385,9 +1387,9 @@ bool remove_subobject_from_object(const int 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) | ||||
|         if (vol->is_model_part()) | ||||
|             ++solid_cnt; | ||||
|     if (!volume->modifier && solid_cnt == 1) { | ||||
|     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; | ||||
|     } | ||||
|  | @ -1477,7 +1479,7 @@ 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]->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); | ||||
|  |  | |||
|  | @ -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",  | ||||
|  |  | |||
|  | @ -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" }) | ||||
|  |  | |||
|  | @ -3,7 +3,6 @@ | |||
| 
 | ||||
| #include <string> | ||||
| #include <functional> | ||||
| #include "Strings.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -43,13 +42,13 @@ public: | |||
|     } | ||||
| 
 | ||||
|     /// Message shown on the next status update.
 | ||||
|     virtual void message(const string&) = 0; | ||||
|     virtual void message(const std::string&) = 0; | ||||
| 
 | ||||
|     /// Title of the operation.
 | ||||
|     virtual void title(const string&) = 0; | ||||
|     virtual void title(const std::string&) = 0; | ||||
| 
 | ||||
|     /// Formatted message for the next status update. Works just like sprintf.
 | ||||
|     virtual void message_fmt(const string& 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; } | ||||
|  | @ -61,7 +60,7 @@ public: | |||
|     virtual void cancel() { cancelfunc_(); } | ||||
| 
 | ||||
|     /// Convenience function to call message and status update in one function.
 | ||||
|     void update(float st, const string& msg) { | ||||
|     void update(float st, const std::string& msg) { | ||||
|         message(msg); state(st); | ||||
|     } | ||||
| }; | ||||
|  |  | |||
|  | @ -1,10 +0,0 @@ | |||
| #ifndef STRINGS_HPP | ||||
| #define STRINGS_HPP | ||||
| 
 | ||||
| #include "GUI/GUI.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| using string = wxString; | ||||
| } | ||||
| 
 | ||||
| #endif // STRINGS_HPP
 | ||||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ | |||
|     PrintController *print_ctl(); | ||||
|     void set_model(Model *model); | ||||
|     void set_print(Print *print); | ||||
|     void set_global_progress_indicator(ProgressStatusBar *prs); | ||||
|     void set_global_progress_indicator(unsigned gauge_id, unsigned statusbar_id); | ||||
| 
 | ||||
|     void arrange_model(); | ||||
| }; | ||||
|  | @ -186,3 +186,8 @@ void 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); %}; | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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); | ||||
|      | ||||
|  |  | |||
|  | @ -267,6 +267,36 @@ Print::total_cost(...) | |||
|             THIS->total_cost = (double)SvNV(ST(1)); | ||||
|         } | ||||
|         RETVAL = THIS->total_cost; | ||||
|     OUTPUT: | ||||
|         RETVAL | ||||
| 
 | ||||
| double | ||||
| Print::total_wipe_tower_cost(...) | ||||
|     CODE: | ||||
|         if (items > 1) { | ||||
|             THIS->total_wipe_tower_cost = (double)SvNV(ST(1)); | ||||
|         } | ||||
|         RETVAL = THIS->total_wipe_tower_cost; | ||||
|     OUTPUT: | ||||
|         RETVAL | ||||
| 
 | ||||
| double | ||||
| Print::total_wipe_tower_filament(...) | ||||
|     CODE: | ||||
|         if (items > 1) { | ||||
|             THIS->total_wipe_tower_filament = (double)SvNV(ST(1)); | ||||
|         } | ||||
|         RETVAL = THIS->total_wipe_tower_filament; | ||||
|     OUTPUT: | ||||
|         RETVAL | ||||
| 
 | ||||
| int | ||||
| Print::m_wipe_tower_number_of_toolchanges(...) | ||||
|     CODE: | ||||
|         if (items > 1) { | ||||
|             THIS->m_wipe_tower_number_of_toolchanges = (double)SvNV(ST(1)); | ||||
|         } | ||||
|         RETVAL = THIS->m_wipe_tower_number_of_toolchanges; | ||||
|     OUTPUT: | ||||
|         RETVAL         | ||||
| %} | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Matena
						Lukas Matena