New upstream version 1.41.2+dfsg
Chow Loong Jin
5 years ago
87 | 87 | $self->Fit; |
88 | 88 | $self->SetMinSize([760, 490]); |
89 | 89 | $self->SetSize($self->GetMinSize); |
90 | wxTheApp->restore_window_pos($self, "main_frame"); | |
90 | Slic3r::GUI::restore_window_size($self, "main_frame"); | |
91 | 91 | $self->Show; |
92 | 92 | $self->Layout; |
93 | 93 | } |
100 | 100 | return; |
101 | 101 | } |
102 | 102 | # save window size |
103 | wxTheApp->save_window_pos($self, "main_frame"); | |
103 | Slic3r::GUI::save_window_size($self, "main_frame"); | |
104 | 104 | # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback, |
105 | 105 | # but in rare cases it may not have been called yet. |
106 | 106 | wxTheApp->{app_config}->save; |
237 | 237 | my @expolygons = (); |
238 | 238 | foreach my $volume (@{$self->{model_object}->volumes}) { |
239 | 239 | next if !$volume->mesh; |
240 | next if $volume->modifier; | |
240 | next if !$volume->model_part; | |
241 | 241 | my $expp = $volume->mesh->slice([ $z_cut ])->[0]; |
242 | 242 | push @expolygons, @$expp; |
243 | 243 | } |
15 | 15 | use constant ICON_OBJECT => 0; |
16 | 16 | use constant ICON_SOLIDMESH => 1; |
17 | 17 | use constant ICON_MODIFIERMESH => 2; |
18 | use constant ICON_SUPPORT_ENFORCER => 3; | |
19 | use constant ICON_SUPPORT_BLOCKER => 4; | |
18 | 20 | |
19 | 21 | sub new { |
20 | 22 | my ($class, $parent, %params) = @_; |
34 | 36 | y => 0, |
35 | 37 | z => 0, |
36 | 38 | }; |
37 | ||
39 | ||
38 | 40 | # create TreeCtrl |
39 | 41 | my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100], |
40 | 42 | wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT |
45 | 47 | $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT |
46 | 48 | $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH |
47 | 49 | $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH |
50 | $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_enforcer.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_ENFORCER | |
51 | $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_blocker.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_BLOCKER | |
48 | 52 | |
49 | 53 | my $rootId = $tree->AddRoot("Object", ICON_OBJECT); |
50 | 54 | $tree->SetPlData($rootId, { type => 'object' }); |
88 | 92 | $self->{btn_move_down}->SetFont($Slic3r::GUI::small_font); |
89 | 93 | |
90 | 94 | # part settings panel |
91 | $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; $self->_update_canvas; }); | |
95 | $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { | |
96 | my ($key, $value) = @_; | |
97 | wxTheApp->CallAfter(sub { | |
98 | $self->set_part_type($value) if ($key eq "part_type"); | |
99 | $self->{part_settings_changed} = 1; | |
100 | $self->_update_canvas; | |
101 | }); | |
102 | }); | |
92 | 103 | my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL); |
93 | 104 | $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0); |
94 | 105 | |
224 | 235 | my $selectedId = $rootId; |
225 | 236 | foreach my $volume_id (0..$#{$object->volumes}) { |
226 | 237 | my $volume = $object->volumes->[$volume_id]; |
227 | ||
228 | my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH; | |
238 | my $icon = | |
239 | $volume->modifier ? ICON_MODIFIERMESH : | |
240 | $volume->support_enforcer ? ICON_SUPPORT_ENFORCER : | |
241 | $volume->support_blocker ? ICON_SUPPORT_BLOCKER : | |
242 | ICON_SOLIDMESH; | |
229 | 243 | my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon); |
230 | 244 | if ($volume_id == $selected_volume_idx) { |
231 | 245 | $selectedId = $itemId; |
287 | 301 | |
288 | 302 | if (my $itemData = $self->get_selection) { |
289 | 303 | my ($config, @opt_keys); |
304 | my $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_OBJECT; | |
305 | my $support = 0; | |
290 | 306 | if ($itemData->{type} eq 'volume') { |
291 | 307 | # select volume in 3D preview |
292 | 308 | if ($self->{canvas}) { |
300 | 316 | # attach volume config to settings panel |
301 | 317 | my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; |
302 | 318 | |
303 | if ($volume->modifier) { | |
319 | if (! $volume->model_part) { | |
304 | 320 | $self->{optgroup_movers}->enable; |
321 | if ($volume->support_enforcer || $volume->support_blocker) { | |
322 | $support = 1; | |
323 | $type = $volume->support_enforcer ? | |
324 | Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER : | |
325 | Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER; | |
326 | } else { | |
327 | $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER; | |
328 | } | |
305 | 329 | } else { |
330 | $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART; | |
306 | 331 | $self->{optgroup_movers}->disable; |
307 | 332 | } |
308 | 333 | $config = $volume->config; |
309 | 334 | $self->{staticbox}->SetLabel('Part Settings'); |
310 | ||
311 | 335 | # get default values |
312 | @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys}; | |
336 | @opt_keys = $support ? () : @{Slic3r::Config::PrintRegion->new->get_keys}; | |
313 | 337 | } elsif ($itemData->{type} eq 'object') { |
314 | 338 | # select nothing in 3D preview |
315 | 339 | |
322 | 346 | # get default values |
323 | 347 | my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); |
324 | 348 | |
325 | # decide which settings will be shown by default | |
349 | # decide which settings will be shown by default | |
326 | 350 | if ($itemData->{type} eq 'object') { |
327 | 351 | $config->set_ifndef('wipe_into_objects', 0); |
328 | 352 | $config->set_ifndef('wipe_into_infill', 0); |
329 | 353 | } |
330 | 354 | |
331 | 355 | # append default extruder |
332 | push @opt_keys, 'extruder'; | |
333 | $default_config->set('extruder', 0); | |
334 | $config->set_ifndef('extruder', 0); | |
356 | if (! $support) { | |
357 | push @opt_keys, 'extruder'; | |
358 | $default_config->set('extruder', 0); | |
359 | $config->set_ifndef('extruder', 0); | |
360 | } | |
361 | $self->{settings_panel}->set_type($type); | |
335 | 362 | $self->{settings_panel}->set_default_config($default_config); |
336 | 363 | $self->{settings_panel}->set_config($config); |
337 | 364 | $self->{settings_panel}->set_opt_keys(\@opt_keys); |
338 | 365 | |
339 | 366 | # disable minus icon to remove the settings |
340 | if ($itemData->{type} eq 'object') { | |
341 | $self->{settings_panel}->set_fixed_options([qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)]); | |
342 | } else { | |
343 | $self->{settings_panel}->set_fixed_options([qw(extruder)]); | |
344 | } | |
345 | ||
367 | my $fixed_options = | |
368 | ($itemData->{type} eq 'object') ? [qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)] : | |
369 | $support ? [] : [qw(extruder)]; | |
370 | $self->{settings_panel}->set_fixed_options($fixed_options); | |
346 | 371 | $self->{settings_panel}->enable; |
347 | 372 | } |
348 | 373 | |
349 | 374 | Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas}; |
375 | } | |
376 | ||
377 | sub set_part_type | |
378 | { | |
379 | my ($self, $part_type) = @_; | |
380 | if (my $itemData = $self->get_selection) { | |
381 | if ($itemData->{type} eq 'volume') { | |
382 | my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; | |
383 | if ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER || | |
384 | $part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART) { | |
385 | $volume->set_modifier($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER); | |
386 | } elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER) { | |
387 | $volume->set_support_enforcer; | |
388 | } elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER) { | |
389 | $volume->set_support_blocker; | |
390 | } | |
391 | # We want the icon of the selected item to be changed as well. | |
392 | $self->reload_tree($itemData->{volume_id}); | |
393 | } | |
394 | } | |
350 | 395 | } |
351 | 396 | |
352 | 397 | sub on_btn_load { |
361 | 406 | } |
362 | 407 | |
363 | 408 | foreach my $object (@{$model->objects}) { |
409 | my $delta_x = 0.0; | |
410 | my $delta_y = 0.0; | |
411 | my $delta_z = 0.0; | |
412 | if (($self->{model_object}->origin_translation->x != 0.0) || ($self->{model_object}->origin_translation->y != 0.0) || ($self->{model_object}->origin_translation->z != 0.0)) { | |
413 | $object->center_around_origin; | |
414 | $delta_x = $self->{model_object}->origin_translation->x - $object->origin_translation->x; | |
415 | $delta_y = $self->{model_object}->origin_translation->y - $object->origin_translation->y; | |
416 | $delta_z = $self->{model_object}->origin_translation->z - $object->origin_translation->z; | |
417 | } | |
364 | 418 | foreach my $volume (@{$object->volumes}) { |
365 | 419 | my $new_volume = $self->{model_object}->add_volume($volume); |
366 | 420 | $new_volume->set_modifier($is_modifier); |
367 | 421 | $new_volume->set_name(basename($input_file)); |
368 | 422 | |
369 | 423 | # apply the same translation we applied to the object |
370 | $new_volume->mesh->translate(@{$self->{model_object}->origin_translation}); | |
424 | if (($delta_x != 0.0) || ($delta_y != 0.0) || ($delta_z != 0.0)) { | |
425 | $new_volume->mesh->translate($delta_x, $delta_y, $delta_z); | |
426 | $new_volume->convex_hull->translate($delta_x, $delta_y, $delta_z); | |
427 | } | |
371 | 428 | |
372 | 429 | # set a default extruder value, since user can't add it manually |
373 | 430 | $new_volume->config->set_ifndef('extruder', 0); |
32 | 32 | $self->{layers}->Closing; |
33 | 33 | |
34 | 34 | # save window size |
35 | wxTheApp->save_window_pos($self, "object_settings"); | |
35 | Slic3r::GUI::save_window_size($self, "object_settings"); | |
36 | 36 | |
37 | 37 | $self->EndModal(wxID_OK); |
38 | 38 | $self->{parts}->Destroy; |
48 | 48 | |
49 | 49 | $self->Layout; |
50 | 50 | |
51 | wxTheApp->restore_window_pos($self, "object_settings"); | |
51 | Slic3r::GUI::restore_window_size($self, "object_settings"); | |
52 | 52 | |
53 | 53 | return $self; |
54 | 54 | } |
6 | 6 | use utf8; |
7 | 7 | |
8 | 8 | use List::Util qw(first); |
9 | use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG | |
10 | wxTheApp); | |
11 | use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU); | |
9 | use Wx qw(:misc :sizer :button :combobox wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp); | |
10 | use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_LEFT_DOWN EVT_MENU); | |
12 | 11 | use base 'Wx::ScrolledWindow'; |
13 | 12 | |
14 | 13 | use constant ICON_MATERIAL => 0; |
15 | 14 | use constant ICON_SOLIDMESH => 1; |
16 | 15 | use constant ICON_MODIFIERMESH => 2; |
16 | ||
17 | use constant TYPE_OBJECT => -1; | |
18 | use constant TYPE_PART => 0; | |
19 | use constant TYPE_MODIFIER => 1; | |
20 | use constant TYPE_SUPPORT_ENFORCER => 2; | |
21 | use constant TYPE_SUPPORT_BLOCKER => 3; | |
17 | 22 | |
18 | 23 | my %icons = ( |
19 | 24 | 'Advanced' => 'wand.png', |
35 | 40 | $self->{config} = Slic3r::Config->new; |
36 | 41 | # On change callback. |
37 | 42 | $self->{on_change} = $params{on_change}; |
43 | $self->{type} = TYPE_OBJECT; | |
38 | 44 | $self->{fixed_options} = {}; |
39 | 45 | |
40 | 46 | $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); |
41 | 47 | |
42 | 48 | $self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL); |
43 | 49 | $self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0); |
44 | ||
50 | ||
45 | 51 | # option selector |
46 | 52 | { |
47 | 53 | # create the button |
109 | 115 | $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; |
110 | 116 | } |
111 | 117 | |
118 | sub set_type { | |
119 | my ($self, $type) = @_; | |
120 | $self->{type} = $type; | |
121 | if ($type == TYPE_SUPPORT_ENFORCER || $type == TYPE_SUPPORT_BLOCKER) { | |
122 | $self->{btn_add}->Hide; | |
123 | } else { | |
124 | $self->{btn_add}->Show; | |
125 | } | |
126 | } | |
127 | ||
112 | 128 | sub set_fixed_options { |
113 | 129 | my ($self, $opt_keys) = @_; |
114 | 130 | $self->{fixed_options} = { map {$_ => 1} @$opt_keys }; |
120 | 136 | |
121 | 137 | $self->{options_sizer}->Clear(1); |
122 | 138 | return if !defined $self->{config}; |
123 | ||
139 | ||
140 | if ($self->{type} != TYPE_OBJECT) { | |
141 | my $label = Wx::StaticText->new($self, -1, "Type:"), | |
142 | my $selection = [ "Part", "Modifier", "Support Enforcer", "Support Blocker" ]; | |
143 | my $field = Wx::ComboBox->new($self, -1, $selection->[$self->{type}], wxDefaultPosition, Wx::Size->new(160, -1), $selection, wxCB_READONLY); | |
144 | my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); | |
145 | $sizer->Add($label, 1, wxEXPAND | wxALL, 5); | |
146 | $sizer->Add($field, 0, wxALL, 5); | |
147 | EVT_COMBOBOX($self, $field, sub { | |
148 | my $idx = $field->GetSelection; # get index of selected value | |
149 | $self->{on_change}->("part_type", $idx) if $self->{on_change}; | |
150 | }); | |
151 | $self->{options_sizer}->Add($sizer, 0, wxEXPAND | wxBOTTOM, 0); | |
152 | } | |
153 | ||
124 | 154 | my %categories = (); |
125 | foreach my $opt_key (@{$self->{config}->get_keys}) { | |
126 | my $category = $Slic3r::Config::Options->{$opt_key}{category}; | |
127 | $categories{$category} ||= []; | |
128 | push @{$categories{$category}}, $opt_key; | |
155 | if ($self->{type} != TYPE_SUPPORT_ENFORCER && $self->{type} != TYPE_SUPPORT_BLOCKER) { | |
156 | foreach my $opt_key (@{$self->{config}->get_keys}) { | |
157 | my $category = $Slic3r::Config::Options->{$opt_key}{category}; | |
158 | $categories{$category} ||= []; | |
159 | push @{$categories{$category}}, $opt_key; | |
160 | } | |
129 | 161 | } |
130 | 162 | foreach my $category (sort keys %categories) { |
131 | 163 | my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( |
135 | 167 | full_labels => 1, |
136 | 168 | label_font => $Slic3r::GUI::small_font, |
137 | 169 | sidetext_font => $Slic3r::GUI::small_font, |
138 | label_width => 120, | |
170 | label_width => 150, | |
139 | 171 | on_change => sub { $self->{on_change}->() if $self->{on_change} }, |
140 | 172 | extra_column => sub { |
141 | 173 | my ($line) = @_; |
766 | 766 | $model->convert_multipart_object(scalar(@$nozzle_dmrs)) if $dialog->ShowModal() == wxID_YES; |
767 | 767 | } |
768 | 768 | |
769 | # objects imported from 3mf require a call to center_around_origin to have gizmos working properly and this call | |
770 | # need to be done after looks_like_multipart_object detection | |
771 | if ($input_file =~ /.3[mM][fF]$/) | |
772 | { | |
773 | foreach my $model_object (@{$model->objects}) { | |
774 | $model_object->center_around_origin; # also aligns object to Z = 0 | |
775 | } | |
776 | } | |
777 | ||
769 | 778 | if ($one_by_one) { |
770 | 779 | push @obj_idx, $self->load_model_objects(@{$model->objects}); |
771 | 780 | } else { |
1645 | 1654 | $grid_sizer->AddGrowableCol(1, 1); |
1646 | 1655 | $grid_sizer->AddGrowableCol(3, 1); |
1647 | 1656 | $print_info_sizer->Add($grid_sizer, 0, wxEXPAND); |
1657 | my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0; | |
1648 | 1658 | my @info = ( |
1649 | 1659 | L("Used Filament (m)") |
1650 | => sprintf("%.2f" , $self->{print}->total_used_filament / 1000), | |
1660 | => $is_wipe_tower ? | |
1661 | sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_used_filament / 1000, | |
1662 | ($self->{print}->total_used_filament - $self->{print}->total_wipe_tower_filament) / 1000, | |
1663 | L("objects"), | |
1664 | $self->{print}->total_wipe_tower_filament / 1000, | |
1665 | L("wipe tower")) : | |
1666 | sprintf("%.2f" , $self->{print}->total_used_filament / 1000), | |
1667 | ||
1651 | 1668 | L("Used Filament (mm³)") |
1652 | 1669 | => sprintf("%.2f" , $self->{print}->total_extruded_volume), |
1653 | 1670 | L("Used Filament (g)"), |
1654 | 1671 | => sprintf("%.2f" , $self->{print}->total_weight), |
1655 | 1672 | L("Cost"), |
1656 | => sprintf("%.2f" , $self->{print}->total_cost), | |
1673 | => $is_wipe_tower ? | |
1674 | sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_cost, | |
1675 | ($self->{print}->total_cost - $self->{print}->total_wipe_tower_cost), | |
1676 | L("objects"), | |
1677 | $self->{print}->total_wipe_tower_cost, | |
1678 | L("wipe tower")) : | |
1679 | sprintf("%.2f" , $self->{print}->total_cost), | |
1657 | 1680 | L("Estimated printing time (normal mode)") |
1658 | 1681 | => $self->{print}->estimated_normal_print_time, |
1659 | 1682 | L("Estimated printing time (silent mode)") |
1660 | 1683 | => $self->{print}->estimated_silent_print_time |
1661 | 1684 | ); |
1685 | # if there is a wipe tower, insert number of toolchanges info into the array: | |
1686 | splice (@info, 8, 0, L("Number of tool changes") => sprintf("%.d", $self->{print}->m_wipe_tower_number_of_toolchanges)) if ($is_wipe_tower); | |
1687 | ||
1662 | 1688 | while ( my $label = shift @info) { |
1663 | 1689 | my $value = shift @info; |
1664 | 1690 | next if $value eq "N/A"; |
1673 | 1699 | |
1674 | 1700 | $scrolled_window_sizer->Show(2, $show); |
1675 | 1701 | $scrolled_window_panel->Layout; |
1702 | $self->Layout; | |
1676 | 1703 | } |
1677 | 1704 | |
1678 | 1705 | sub do_print { |
355 | 355 | } |
356 | 356 | } |
357 | 357 | |
358 | sub save_window_pos { | |
359 | my ($self, $window, $name) = @_; | |
360 | ||
361 | $self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY); | |
362 | $self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH); | |
363 | $self->{app_config}->set("${name}_maximized", $window->IsMaximized); | |
364 | $self->{app_config}->save; | |
365 | } | |
366 | ||
367 | sub restore_window_pos { | |
368 | my ($self, $window, $name) = @_; | |
369 | if ($self->{app_config}->has("${name}_pos")) { | |
370 | my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ]; | |
371 | $window->SetSize($size); | |
372 | ||
373 | my $display = Wx::Display->new->GetClientArea(); | |
374 | my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ]; | |
375 | if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) { | |
376 | $window->Move($pos); | |
377 | } | |
378 | $window->Maximize(1) if $self->{app_config}->get("${name}_maximized"); | |
379 | } | |
380 | } | |
381 | ||
382 | 358 | 1; |
Binary diff not shown
Binary diff not shown
Binary diff not shown
0 | min_slic3r_version = 1.41.1 | |
1 | 0.3.2 New MK2.5 and MK3 FW versions | |
2 | 0.3.1 New MK2.5 and MK3 FW versions | |
3 | 0.3.0 New MK2.5 and MK3 FW version | |
0 | 4 | min_slic3r_version = 1.41.0-alpha |
5 | 0.2.9 New MK2.5 and MK3 FW versions | |
6 | 0.2.8 New MK2.5 and MK3 FW version | |
7 | min_slic3r_version = 1.41.1 | |
8 | 0.2.7 New MK2.5 and MK3 FW version | |
9 | 0.2.6 Added MMU2 MK2.5 settings | |
10 | min_slic3r_version = 1.41.0-alpha | |
11 | 0.2.5 Prusament is out - added prusament settings | |
12 | 0.2.4 Added soluble support profiles for MMU2 | |
13 | 0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit | |
1 | 14 | 0.2.2 Edited MMU2 Single mode purge line |
2 | 15 | 0.2.1 Added PET and BVOH settings for MMU2 |
3 | 16 | 0.2.0-beta5 Fixed MMU1 ramming parameters |
4 | 4 | name = Prusa Research |
5 | 5 | # Configuration version of this file. Config file will only be installed, if the config_version differs. |
6 | 6 | # This means, the server may force the Slic3r configuration to be downgraded. |
7 | config_version = 0.2.2 | |
7 | config_version = 0.3.2 | |
8 | 8 | # Where to get the updates from? |
9 | 9 | config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ |
10 | 10 | |
33 | 33 | [printer_model:MK2SMM] |
34 | 34 | name = Original Prusa i3 MK2/S MMU 1.0 |
35 | 35 | variants = 0.4; 0.6 |
36 | ||
37 | [printer_model:MK2.5MMU2] | |
38 | name = Original Prusa i3 MK2.5 MMU 2.0 | |
39 | variants = 0.4 | |
36 | 40 | |
37 | 41 | # All presets starting with asterisk, for example *common*, are intermediate and they will |
38 | 42 | # not make it into the user interface. |
180 | 184 | support_material_synchronize_layers = 1 |
181 | 185 | support_material_threshold = 80 |
182 | 186 | support_material_with_sheath = 1 |
183 | wipe_tower = 1 | |
184 | 187 | |
185 | 188 | # XXXXXXXXXXXXXXXXXXXX |
186 | 189 | # XXX--- 0.05mm ---XXX |
328 | 331 | [print:0.15mm 100mms Linear Advance] |
329 | 332 | inherits = *0.15mm* |
330 | 333 | bridge_flow_ratio = 0.95 |
331 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 | |
334 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 | |
332 | 335 | external_perimeter_speed = 50 |
333 | 336 | infill_speed = 100 |
334 | 337 | max_print_speed = 150 |
340 | 343 | |
341 | 344 | [print:0.15mm OPTIMAL] |
342 | 345 | inherits = *0.15mm* |
343 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 | |
346 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 | |
344 | 347 | top_infill_extrusion_width = 0.45 |
345 | 348 | |
346 | 349 | [print:0.15mm OPTIMAL 0.25 nozzle] |
373 | 376 | solid_infill_speed = 200 |
374 | 377 | top_solid_infill_speed = 50 |
375 | 378 | |
379 | [print:0.15mm OPTIMAL MK3 SOLUBLE FULL] | |
380 | inherits = 0.15mm OPTIMAL MK3; *soluble_support* | |
381 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
382 | notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder | |
383 | support_material_extruder = 5 | |
384 | support_material_interface_extruder = 5 | |
385 | perimeter_speed = 40 | |
386 | solid_infill_speed = 40 | |
387 | top_infill_extrusion_width = 0.45 | |
388 | top_solid_infill_speed = 30 | |
389 | ||
390 | [print:0.15mm OPTIMAL MK3 SOLUBLE INTERFACE] | |
391 | inherits = 0.15mm OPTIMAL MK3 SOLUBLE FULL | |
392 | notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder | |
393 | support_material_extruder = 0 | |
394 | support_material_interface_layers = 3 | |
395 | support_material_with_sheath = 0 | |
396 | ||
376 | 397 | [print:0.15mm OPTIMAL SOLUBLE FULL] |
377 | 398 | inherits = *0.15mm*; *soluble_support* |
378 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
399 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
379 | 400 | external_perimeter_speed = 25 |
380 | 401 | notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder |
381 | 402 | perimeter_speed = 40 |
382 | 403 | solid_infill_speed = 40 |
383 | 404 | top_infill_extrusion_width = 0.45 |
384 | 405 | top_solid_infill_speed = 30 |
385 | wipe_tower = 1 | |
386 | 406 | |
387 | 407 | [print:0.15mm OPTIMAL SOLUBLE INTERFACE] |
388 | 408 | inherits = 0.15mm OPTIMAL SOLUBLE FULL |
403 | 423 | perimeter_speed = 45 |
404 | 424 | solid_infill_speed = 200 |
405 | 425 | top_solid_infill_speed = 50 |
426 | ||
406 | 427 | [print:*0.20mm*] |
407 | 428 | inherits = *common* |
408 | 429 | bottom_solid_layers = 4 |
435 | 456 | |
436 | 457 | [print:0.20mm 100mms Linear Advance] |
437 | 458 | inherits = *0.20mm* |
438 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 | |
459 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 | |
439 | 460 | external_perimeter_speed = 50 |
440 | 461 | infill_speed = 100 |
441 | 462 | max_print_speed = 150 |
457 | 478 | solid_infill_speed = 200 |
458 | 479 | top_solid_infill_speed = 50 |
459 | 480 | |
481 | [print:0.20mm FAST MK3 SOLUBLE FULL] | |
482 | inherits = 0.20mm FAST MK3; *soluble_support* | |
483 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
484 | notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder | |
485 | support_material_extruder = 5 | |
486 | support_material_interface_extruder = 5 | |
487 | perimeter_speed = 40 | |
488 | solid_infill_speed = 40 | |
489 | top_infill_extrusion_width = 0.45 | |
490 | top_solid_infill_speed = 30 | |
491 | ||
492 | [print:0.20mm FAST MK3 SOLUBLE INTERFACE] | |
493 | inherits = 0.20mm FAST MK3 SOLUBLE FULL | |
494 | notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder | |
495 | support_material_extruder = 0 | |
496 | support_material_interface_layers = 3 | |
497 | support_material_with_sheath = 0 | |
498 | ||
460 | 499 | [print:0.20mm NORMAL] |
461 | 500 | inherits = *0.20mm* |
462 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 | |
501 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 | |
463 | 502 | |
464 | 503 | [print:0.20mm NORMAL 0.6 nozzle] |
465 | 504 | inherits = *0.20mm*; *0.6nozzle* |
467 | 506 | |
468 | 507 | [print:0.20mm NORMAL SOLUBLE FULL] |
469 | 508 | inherits = *0.20mm*; *soluble_support* |
470 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
509 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
471 | 510 | external_perimeter_speed = 30 |
472 | 511 | notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder |
473 | 512 | perimeter_speed = 40 |
518 | 557 | [print:0.35mm FAST] |
519 | 558 | inherits = *0.35mm* |
520 | 559 | bridge_flow_ratio = 0.95 |
521 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 | |
560 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 | |
522 | 561 | first_layer_extrusion_width = 0.42 |
523 | 562 | perimeter_extrusion_width = 0.43 |
524 | 563 | solid_infill_extrusion_width = 0.7 |
546 | 585 | support_material_with_sheath = 0 |
547 | 586 | support_material_xy_spacing = 150% |
548 | 587 | |
588 | # XXXXXXXXXXXXXXXXXXXXXX | |
589 | # XXX----- MK2.5 ----XXX | |
590 | # XXXXXXXXXXXXXXXXXXXXXX | |
591 | ||
592 | [print:0.15mm 100mms Linear Advance MK2.5] | |
593 | inherits = 0.15mm 100mms Linear Advance | |
594 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 | |
595 | single_extruder_multi_material_priming = 0 | |
596 | ||
597 | [print:0.15mm OPTIMAL MK2.5] | |
598 | inherits = 0.15mm OPTIMAL | |
599 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 | |
600 | single_extruder_multi_material_priming = 0 | |
601 | ||
602 | [print:0.15mm OPTIMAL SOLUBLE FULL MK2.5] | |
603 | inherits = 0.15mm OPTIMAL SOLUBLE FULL | |
604 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
605 | ||
606 | [print:0.15mm OPTIMAL SOLUBLE INTERFACE MK2.5] | |
607 | inherits = 0.15mm OPTIMAL SOLUBLE INTERFACE | |
608 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
609 | ||
610 | [print:0.20mm 100mms Linear Advance MK2.5] | |
611 | inherits = 0.20mm 100mms Linear Advance | |
612 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 | |
613 | single_extruder_multi_material_priming = 0 | |
614 | ||
615 | [print:0.20mm NORMAL MK2.5] | |
616 | inherits = 0.20mm NORMAL | |
617 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 | |
618 | single_extruder_multi_material_priming = 0 | |
619 | ||
620 | [print:0.20mm NORMAL SOLUBLE FULL MK2.5] | |
621 | inherits = 0.20mm NORMAL SOLUBLE FULL | |
622 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
623 | single_extruder_multi_material_priming = 0 | |
624 | ||
625 | [print:0.20mm NORMAL SOLUBLE INTERFACE MK2.5] | |
626 | inherits = 0.20mm NORMAL SOLUBLE INTERFACE | |
627 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 | |
628 | single_extruder_multi_material_priming = 0 | |
629 | ||
630 | [print:0.35mm FAST MK2.5] | |
631 | inherits = 0.35mm FAST | |
632 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 | |
633 | single_extruder_multi_material_priming = 0 | |
634 | ||
549 | 635 | # XXXXXXxxXXXXXXXXXXXXXX |
550 | 636 | # XXX--- filament ---XXX |
551 | 637 | # XXXXXXXXxxXXXXXXXXXXXX |
554 | 640 | cooling = 1 |
555 | 641 | compatible_printers = |
556 | 642 | # For now, all but selected filaments are disabled for the MMU 2.0 |
557 | compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) | |
643 | compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) | |
558 | 644 | end_filament_gcode = "; Filament-specific end gcode" |
559 | 645 | extrusion_multiplier = 1 |
560 | 646 | filament_loading_speed = 28 |
654 | 740 | [filament:ColorFabb Brass Bronze] |
655 | 741 | inherits = *PLA* |
656 | 742 | # For now, all but selected filaments are disabled for the MMU 2.0 |
657 | compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) | |
743 | compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) | |
658 | 744 | extrusion_multiplier = 1.2 |
659 | 745 | filament_cost = 80.65 |
660 | 746 | filament_density = 4 |
686 | 772 | [filament:ColorFabb Woodfil] |
687 | 773 | inherits = *PLA* |
688 | 774 | # For now, all but selected filaments are disabled for the MMU 2.0 |
689 | compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) | |
775 | compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) | |
690 | 776 | extrusion_multiplier = 1.2 |
691 | 777 | filament_cost = 62.9 |
692 | 778 | filament_density = 1.15 |
788 | 874 | [filament:Fillamentum Timberfil] |
789 | 875 | inherits = *PLA* |
790 | 876 | # For now, all but selected filaments are disabled for the MMU 2.0 |
791 | compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) | |
877 | compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) | |
792 | 878 | extrusion_multiplier = 1.2 |
793 | 879 | filament_cost = 68 |
794 | 880 | filament_density = 1.15 |
850 | 936 | |
851 | 937 | [filament:*ABS MMU2*] |
852 | 938 | inherits = Prusa ABS |
853 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material | |
939 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material | |
854 | 940 | filament_cooling_final_speed = 50 |
855 | 941 | filament_cooling_initial_speed = 10 |
856 | 942 | filament_cooling_moves = 5 |
888 | 974 | |
889 | 975 | [filament:*PET MMU2*] |
890 | 976 | inherits = Prusa PET |
891 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material | |
977 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material | |
892 | 978 | temperature = 230 |
893 | 979 | first_layer_temperature = 230 |
894 | 980 | filament_cooling_final_speed = 1 |
914 | 1000 | filament_density = 1.24 |
915 | 1001 | filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" |
916 | 1002 | |
1003 | [filament:Prusament PLA] | |
1004 | inherits = *PLA* | |
1005 | temperature = 215 | |
1006 | filament_cost = 24.99 | |
1007 | filament_density = 1.24 | |
1008 | filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" | |
1009 | ||
917 | 1010 | [filament:*PLA MMU2*] |
918 | 1011 | inherits = Prusa PLA |
919 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material | |
1012 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material | |
920 | 1013 | temperature = 205 |
921 | 1014 | filament_cooling_final_speed = 1 |
922 | 1015 | filament_cooling_initial_speed = 2 |
931 | 1024 | inherits = *PLA MMU2* |
932 | 1025 | |
933 | 1026 | [filament:Prusa PLA MMU2] |
1027 | inherits = *PLA MMU2* | |
1028 | ||
1029 | [filament:Prusament PLA MMU2] | |
934 | 1030 | inherits = *PLA MMU2* |
935 | 1031 | |
936 | 1032 | [filament:SemiFlex or Flexfill 98A] |
997 | 1093 | |
998 | 1094 | [filament:Verbatim BVOH MMU2] |
999 | 1095 | inherits = Verbatim BVOH |
1000 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material | |
1096 | compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material | |
1001 | 1097 | temperature = 195 |
1002 | 1098 | filament_notes = BVOH |
1003 | 1099 | fan_always_on = 1 |
1097 | 1193 | printer_model = MK2S |
1098 | 1194 | printer_variant = 0.4 |
1099 | 1195 | default_print_profile = 0.15mm OPTIMAL |
1100 | default_filament_profile = Prusa PLA | |
1196 | default_filament_profile = Prusament PLA | |
1101 | 1197 | |
1102 | 1198 | [printer:*multimaterial*] |
1103 | 1199 | inherits = *common* |
1192 | 1288 | inherits = Original Prusa i3 MK2 |
1193 | 1289 | printer_model = MK2.5 |
1194 | 1290 | remaining_times = 1 |
1195 | start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 | |
1291 | start_gcode = M115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 | |
1292 | ||
1293 | [printer:Original Prusa i3 MK2.5 MMU2 Single] | |
1294 | inherits = Original Prusa i3 MK2.5; *mm2* | |
1295 | printer_model = MK2.5MMU2 | |
1296 | single_extruder_multi_material = 0 | |
1297 | max_print_height = 200 | |
1298 | remaining_times = 1 | |
1299 | silent_mode = 0 | |
1300 | retract_lift_below = 199 | |
1301 | machine_max_acceleration_e = 10000 | |
1302 | machine_max_acceleration_extruding = 2000 | |
1303 | machine_max_acceleration_retracting = 1500 | |
1304 | machine_max_acceleration_x = 9000 | |
1305 | machine_max_acceleration_y = 9000 | |
1306 | machine_max_acceleration_z = 500 | |
1307 | machine_max_feedrate_e = 120 | |
1308 | machine_max_feedrate_x = 500 | |
1309 | machine_max_feedrate_y = 500 | |
1310 | machine_max_feedrate_z = 12 | |
1311 | machine_max_jerk_e = 2.5 | |
1312 | machine_max_jerk_x = 10 | |
1313 | machine_max_jerk_y = 10 | |
1314 | machine_max_jerk_z = 0.2 | |
1315 | machine_min_extruding_rate = 0 | |
1316 | machine_min_travel_rate = 0 | |
1317 | default_print_profile = 0.15mm OPTIMAL MK2.5 | |
1318 | default_filament_profile = Prusament PLA | |
1319 | printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n | |
1320 | start_gcode = M107\nM115 U3.4.2 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n | |
1321 | end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors | |
1322 | ||
1323 | [printer:Original Prusa i3 MK2.5 MMU2] | |
1324 | inherits = Original Prusa i3 MK2.5; *mm2* | |
1325 | printer_model = MK2.5MMU2 | |
1326 | max_print_height = 200 | |
1327 | remaining_times = 1 | |
1328 | silent_mode = 0 | |
1329 | retract_lift_below = 199 | |
1330 | machine_max_acceleration_e = 10000 | |
1331 | machine_max_acceleration_extruding = 2000 | |
1332 | machine_max_acceleration_retracting = 1500 | |
1333 | machine_max_acceleration_x = 9000 | |
1334 | machine_max_acceleration_y = 9000 | |
1335 | machine_max_acceleration_z = 500 | |
1336 | machine_max_feedrate_e = 120 | |
1337 | machine_max_feedrate_x = 500 | |
1338 | machine_max_feedrate_y = 500 | |
1339 | machine_max_feedrate_z = 12 | |
1340 | machine_max_jerk_e = 2.5 | |
1341 | machine_max_jerk_x = 10 | |
1342 | machine_max_jerk_y = 10 | |
1343 | machine_max_jerk_z = 0.2 | |
1344 | machine_min_extruding_rate = 0 | |
1345 | machine_min_travel_rate = 0 | |
1346 | default_print_profile = 0.15mm OPTIMAL MK2.5 | |
1347 | printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n | |
1348 | single_extruder_multi_material = 1 | |
1349 | # The 5x nozzle diameter defines the number of extruders. Other extruder parameters | |
1350 | # (for example the retract values) are duplicaed from the first value, so they do not need | |
1351 | # to be defined explicitely. | |
1352 | nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 | |
1353 | extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F | |
1354 | start_gcode = M107\nM115 U3.4.2 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n | |
1355 | end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n | |
1196 | 1356 | |
1197 | 1357 | [printer:Original Prusa i3 MK2.5 0.25 nozzle] |
1198 | 1358 | inherits = Original Prusa i3 MK2 0.25 nozzle |
1199 | 1359 | printer_model = MK2.5 |
1200 | 1360 | remaining_times = 1 |
1201 | start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 | |
1361 | start_gcode = M115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 | |
1202 | 1362 | |
1203 | 1363 | [printer:Original Prusa i3 MK2.5 0.6 nozzle] |
1204 | 1364 | inherits = Original Prusa i3 MK2 0.6 nozzle |
1205 | 1365 | printer_model = MK2.5 |
1206 | 1366 | remaining_times = 1 |
1207 | start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 | |
1367 | start_gcode = M115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 | |
1208 | 1368 | |
1209 | 1369 | # XXXXXXXXXXXXXXXXX |
1210 | 1370 | # XXX--- MK3 ---XXX |
1220 | 1380 | machine_max_acceleration_y = 1000,960 |
1221 | 1381 | machine_max_acceleration_z = 1000,1000 |
1222 | 1382 | machine_max_feedrate_e = 120,120 |
1223 | machine_max_feedrate_x = 200,172 | |
1224 | machine_max_feedrate_y = 200,172 | |
1383 | machine_max_feedrate_x = 200,100 | |
1384 | machine_max_feedrate_y = 200,100 | |
1225 | 1385 | machine_max_feedrate_z = 12,12 |
1226 | 1386 | machine_max_jerk_e = 1.5,1.5 |
1227 | 1387 | machine_max_jerk_x = 8,8 |
1234 | 1394 | printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n |
1235 | 1395 | retract_lift_below = 209 |
1236 | 1396 | max_print_height = 210 |
1237 | start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} | |
1397 | start_gcode = M115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} | |
1238 | 1398 | printer_model = MK3 |
1239 | 1399 | default_print_profile = 0.15mm OPTIMAL MK3 |
1240 | 1400 | |
1264 | 1424 | extra_loading_move = -13 |
1265 | 1425 | printer_model = MK3MMU2 |
1266 | 1426 | default_print_profile = 0.15mm OPTIMAL MK3 |
1267 | default_filament_profile = Prusa PLA MMU2 | |
1427 | default_filament_profile = Prusament PLA MMU2 | |
1268 | 1428 | |
1269 | 1429 | [printer:Original Prusa i3 MK3 MMU2 Single] |
1270 | 1430 | inherits = *mm2* |
1271 | start_gcode = M107\nM115 U3.4.0-RC2 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n | |
1431 | single_extruder_multi_material = 0 | |
1432 | start_gcode = M107\nM115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n | |
1272 | 1433 | end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors |
1273 | 1434 | |
1274 | 1435 | [printer:Original Prusa i3 MK3 MMU2] |
1279 | 1440 | machine_max_acceleration_e = 8000,8000 |
1280 | 1441 | nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 |
1281 | 1442 | extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F |
1282 | start_gcode = M107\nM115 U3.4.0-RC2 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n | |
1443 | start_gcode = M107\nM115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n | |
1283 | 1444 | end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n |
1284 | 1445 | |
1285 | 1446 | # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. |
1286 | 1447 | [obsolete_presets] |
1287 | 1448 | print="0.05mm DETAIL 0.25 nozzle";"0.05mm DETAIL MK3";"0.05mm DETAIL";"0.20mm NORMAL MK3";"0.35mm FAST MK3";"print:0.15mm OPTIMAL MK3 MMU2";"print:0.20mm FAST MK3 MMU2" |
1288 | filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" | |
1449 | filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm"⏎ |
266 | 266 | ${LIBDIR}/slic3r/Utils/Time.hpp |
267 | 267 | ${LIBDIR}/slic3r/Utils/HexFile.cpp |
268 | 268 | ${LIBDIR}/slic3r/Utils/HexFile.hpp |
269 | ${LIBDIR}/slic3r/IProgressIndicator.hpp | |
269 | ${LIBDIR}/slic3r/ProgressIndicator.hpp | |
270 | 270 | ${LIBDIR}/slic3r/AppController.hpp |
271 | 271 | ${LIBDIR}/slic3r/AppController.cpp |
272 | 272 | ${LIBDIR}/slic3r/AppControllerWx.cpp |
273 | ${LIBDIR}/slic3r/Strings.hpp | |
274 | 273 | ) |
275 | 274 | |
276 | 275 | add_library(admesh STATIC |
503 | 502 | endif () |
504 | 503 | |
505 | 504 | # SLIC3R_MSVC_PDB |
506 | if (MSVC AND SLIC3R_MSVC_PDB AND ${CMAKE_BUILD_TYPE} STREQUAL "Release") | |
505 | if (MSVC AND SLIC3R_MSVC_PDB AND "${CMAKE_BUILD_TYPE}" STREQUAL "Release") | |
507 | 506 | set_target_properties(XS PROPERTIES |
508 | 507 | COMPILE_FLAGS "/Zi" |
509 | 508 | LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF" |
747 | 746 | set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") |
748 | 747 | |
749 | 748 | add_subdirectory(${LIBDIR}/libnest2d) |
749 | target_compile_definitions(libslic3r PUBLIC -DUSE_TBB) | |
750 | 750 | target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES}) |
751 | 751 | target_include_directories(libslic3r_gui PUBLIC BEFORE ${LIBNEST2D_INCLUDES}) |
752 | 752 |
286 | 286 | { |
287 | 287 | // skip solid/endsolid |
288 | 288 | // (in this order, otherwise it won't work when they are paired in the middle of a file) |
289 | fscanf(stl->fp, "endsolid\n"); | |
289 | fscanf(stl->fp, "endsolid%*[^\n]\n"); | |
290 | 290 | fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") |
291 | 291 | // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs. |
292 | 292 | int res_normal = fscanf(stl->fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); |
30 | 30 | ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp |
31 | 31 | ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp |
32 | 32 | ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp |
33 | ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/rotfinder.hpp | |
33 | 34 | ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp |
34 | 35 | ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp |
35 | 36 | ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp |
88 | 89 | endif() |
89 | 90 | |
90 | 91 | if(LIBNEST2D_BUILD_EXAMPLES) |
92 | ||
91 | 93 | add_executable(example examples/main.cpp |
92 | 94 | # tools/libnfpglue.hpp |
93 | 95 | # tools/libnfpglue.cpp |
96 | tools/nfp_svgnest.hpp | |
97 | tools/nfp_svgnest_glue.hpp | |
94 | 98 | tools/svgtools.hpp |
95 | 99 | tests/printer_parts.cpp |
96 | 100 | tests/printer_parts.h |
97 | ${LIBNEST2D_SRCFILES}) | |
101 | ${LIBNEST2D_SRCFILES} | |
102 | ) | |
103 | set(TBB_STATIC ON) | |
104 | find_package(TBB QUIET) | |
105 | if(TBB_FOUND) | |
106 | message(STATUS "Parallelization with Intel TBB") | |
107 | target_include_directories(example PUBLIC ${TBB_INCLUDE_DIRS}) | |
108 | target_compile_definitions(example PUBLIC ${TBB_DEFINITIONS} -DUSE_TBB) | |
109 | if(MSVC) | |
110 | # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. | |
111 | target_compile_definitions(example PUBLIC -D__TBB_NO_IMPLICIT_LINKAGE) | |
112 | endif() | |
113 | # The Intel TBB library will use the std::exception_ptr feature of C++11. | |
114 | target_compile_definitions(example PUBLIC -DTBB_USE_CAPTURED_EXCEPTION=1) | |
98 | 115 | |
116 | target_link_libraries(example ${TBB_LIBRARIES}) | |
117 | else() | |
118 | find_package(OpenMP QUIET) | |
119 | if(OpenMP_CXX_FOUND) | |
120 | message(STATUS "Parallelization with OpenMP") | |
121 | target_include_directories(example PUBLIC OpenMP::OpenMP_CXX) | |
122 | target_link_libraries(example OpenMP::OpenMP_CXX) | |
123 | endif() | |
124 | endif() | |
99 | 125 | |
100 | 126 | target_link_libraries(example ${LIBNEST2D_LIBRARIES}) |
101 | 127 | target_include_directories(example PUBLIC ${LIBNEST2D_HEADERS}) |
8 | 8 | existing implementation to avoid copying or having unnecessary dependencies. |
9 | 9 | |
10 | 10 | A default backend is provided if the user of the library just wants to use it |
11 | out of the box without additional integration. The default backend is reasonably | |
11 | out of the box without additional integration. This backend is reasonably | |
12 | 12 | fast and robust, being built on top of boost geometry and the |
13 | 13 | [polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of |
14 | this default backend implies the dependency on these packages as well as the | |
15 | compilation of the backend itself (The default backend is not yet header only). | |
14 | this default backend implies the dependency on these packages but its header | |
15 | only as well. | |
16 | 16 | |
17 | 17 | This software is currently under construction and lacks a throughout |
18 | 18 | documentation and some essential algorithms as well. At this stage it works well |
19 | 19 | for rectangles and convex closed polygons without considering holes and |
20 | 20 | concavities. |
21 | 21 | |
22 | Holes and non-convex polygons will be usable in the near future as well. | |
22 | Holes and non-convex polygons will be usable in the near future as well. The | |
23 | no fit polygon based placer module combined with the first fit selection | |
24 | strategy is now used in the [Slic3r](https://github.com/prusa3d/Slic3r) | |
25 | application's arrangement feature. It uses local optimization techniques to find | |
26 | the best placement of each new item based on some features of the arrangement. | |
27 | ||
28 | In the near future I would like to use machine learning to evaluate the | |
29 | placements and (or) the order if items in which they are placed and see what | |
30 | results can be obtained. This is a different approach than that of SVGnest which | |
31 | uses genetic algorithms to find better and better selection orders. Maybe the | |
32 | two approaches can be combined as well. | |
23 | 33 | |
24 | 34 | # References |
25 | 35 | - [SVGNest](https://github.com/Jack000/SVGnest) |
0 | # The MIT License (MIT) | |
1 | # | |
2 | # Copyright (c) 2015 Justus Calvin | |
3 | # | |
4 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | # of this software and associated documentation files (the "Software"), to deal | |
6 | # in the Software without restriction, including without limitation the rights | |
7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | # copies of the Software, and to permit persons to whom the Software is | |
9 | # furnished to do so, subject to the following conditions: | |
10 | # | |
11 | # The above copyright notice and this permission notice shall be included in all | |
12 | # copies or substantial portions of the Software. | |
13 | # | |
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
20 | # SOFTWARE. | |
21 | ||
22 | # | |
23 | # FindTBB | |
24 | # ------- | |
25 | # | |
26 | # Find TBB include directories and libraries. | |
27 | # | |
28 | # Usage: | |
29 | # | |
30 | # find_package(TBB [major[.minor]] [EXACT] | |
31 | # [QUIET] [REQUIRED] | |
32 | # [[COMPONENTS] [components...]] | |
33 | # [OPTIONAL_COMPONENTS components...]) | |
34 | # | |
35 | # where the allowed components are tbbmalloc and tbb_preview. Users may modify | |
36 | # the behavior of this module with the following variables: | |
37 | # | |
38 | # * TBB_ROOT_DIR - The base directory the of TBB installation. | |
39 | # * TBB_INCLUDE_DIR - The directory that contains the TBB headers files. | |
40 | # * TBB_LIBRARY - The directory that contains the TBB library files. | |
41 | # * TBB_<library>_LIBRARY - The path of the TBB the corresponding TBB library. | |
42 | # These libraries, if specified, override the | |
43 | # corresponding library search results, where <library> | |
44 | # may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug, | |
45 | # tbb_preview, or tbb_preview_debug. | |
46 | # * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will | |
47 | # be used instead of the release version. | |
48 | # * TBB_STATIC - Static linking of libraries with a _static suffix. | |
49 | # For example, on Windows a tbb_static.lib will be searched for | |
50 | # instead of tbb.lib. | |
51 | # | |
52 | # Users may modify the behavior of this module with the following environment | |
53 | # variables: | |
54 | # | |
55 | # * TBB_INSTALL_DIR | |
56 | # * TBBROOT | |
57 | # * LIBRARY_PATH | |
58 | # | |
59 | # This module will set the following variables: | |
60 | # | |
61 | # * TBB_FOUND - Set to false, or undefined, if we haven’t found, or | |
62 | # don’t want to use TBB. | |
63 | # * TBB_<component>_FOUND - If False, optional <component> part of TBB sytem is | |
64 | # not available. | |
65 | # * TBB_VERSION - The full version string | |
66 | # * TBB_VERSION_MAJOR - The major version | |
67 | # * TBB_VERSION_MINOR - The minor version | |
68 | # * TBB_INTERFACE_VERSION - The interface version number defined in | |
69 | # tbb/tbb_stddef.h. | |
70 | # * TBB_<library>_LIBRARY_RELEASE - The path of the TBB release version of | |
71 | # <library>, where <library> may be tbb, tbb_debug, | |
72 | # tbbmalloc, tbbmalloc_debug, tbb_preview, or | |
73 | # tbb_preview_debug. | |
74 | # * TBB_<library>_LIBRARY_DEGUG - The path of the TBB release version of | |
75 | # <library>, where <library> may be tbb, tbb_debug, | |
76 | # tbbmalloc, tbbmalloc_debug, tbb_preview, or | |
77 | # tbb_preview_debug. | |
78 | # | |
79 | # The following varibles should be used to build and link with TBB: | |
80 | # | |
81 | # * TBB_INCLUDE_DIRS - The include directory for TBB. | |
82 | # * TBB_LIBRARIES - The libraries to link against to use TBB. | |
83 | # * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB. | |
84 | # * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB. | |
85 | # * TBB_DEFINITIONS - Definitions to use when compiling code that uses | |
86 | # TBB. | |
87 | # * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that | |
88 | # uses TBB. | |
89 | # * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that | |
90 | # uses TBB. | |
91 | # | |
92 | # This module will also create the "tbb" target that may be used when building | |
93 | # executables and libraries. | |
94 | ||
95 | include(FindPackageHandleStandardArgs) | |
96 | ||
97 | if(NOT TBB_FOUND) | |
98 | ||
99 | ################################## | |
100 | # Check the build type | |
101 | ################################## | |
102 | ||
103 | if(NOT DEFINED TBB_USE_DEBUG_BUILD) | |
104 | if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)") | |
105 | set(TBB_BUILD_TYPE DEBUG) | |
106 | else() | |
107 | set(TBB_BUILD_TYPE RELEASE) | |
108 | endif() | |
109 | elseif(TBB_USE_DEBUG_BUILD) | |
110 | set(TBB_BUILD_TYPE DEBUG) | |
111 | else() | |
112 | set(TBB_BUILD_TYPE RELEASE) | |
113 | endif() | |
114 | ||
115 | ################################## | |
116 | # Set the TBB search directories | |
117 | ################################## | |
118 | ||
119 | # Define search paths based on user input and environment variables | |
120 | set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT}) | |
121 | ||
122 | # Define the search directories based on the current platform | |
123 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") | |
124 | set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB" | |
125 | "C:/Program Files (x86)/Intel/TBB") | |
126 | ||
127 | # Set the target architecture | |
128 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) | |
129 | set(TBB_ARCHITECTURE "intel64") | |
130 | else() | |
131 | set(TBB_ARCHITECTURE "ia32") | |
132 | endif() | |
133 | ||
134 | # Set the TBB search library path search suffix based on the version of VC | |
135 | if(WINDOWS_STORE) | |
136 | set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui") | |
137 | elseif(MSVC14) | |
138 | set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14") | |
139 | elseif(MSVC12) | |
140 | set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12") | |
141 | elseif(MSVC11) | |
142 | set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11") | |
143 | elseif(MSVC10) | |
144 | set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10") | |
145 | endif() | |
146 | ||
147 | # Add the library path search suffix for the VC independent version of TBB | |
148 | list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt") | |
149 | ||
150 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") | |
151 | # OS X | |
152 | set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") | |
153 | ||
154 | # TODO: Check to see which C++ library is being used by the compiler. | |
155 | if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) | |
156 | # The default C++ library on OS X 10.9 and later is libc++ | |
157 | set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib") | |
158 | else() | |
159 | set(TBB_LIB_PATH_SUFFIX "lib") | |
160 | endif() | |
161 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") | |
162 | # Linux | |
163 | set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") | |
164 | ||
165 | # TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or | |
166 | # <arch>/gcc4.1. For now, assume that the compiler is more recent than | |
167 | # gcc 4.4.x or later. | |
168 | if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") | |
169 | set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4") | |
170 | elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") | |
171 | set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4") | |
172 | endif() | |
173 | endif() | |
174 | ||
175 | ################################## | |
176 | # Find the TBB include dir | |
177 | ################################## | |
178 | ||
179 | find_path(TBB_INCLUDE_DIRS tbb/tbb.h | |
180 | HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR} | |
181 | PATHS ${TBB_DEFAULT_SEARCH_DIR} | |
182 | PATH_SUFFIXES include) | |
183 | ||
184 | ################################## | |
185 | # Set version strings | |
186 | ################################## | |
187 | ||
188 | if(TBB_INCLUDE_DIRS) | |
189 | file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) | |
190 | string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" | |
191 | TBB_VERSION_MAJOR "${_tbb_version_file}") | |
192 | string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" | |
193 | TBB_VERSION_MINOR "${_tbb_version_file}") | |
194 | string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" | |
195 | TBB_INTERFACE_VERSION "${_tbb_version_file}") | |
196 | set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}") | |
197 | endif() | |
198 | ||
199 | ################################## | |
200 | # Find TBB components | |
201 | ################################## | |
202 | ||
203 | if(TBB_VERSION VERSION_LESS 4.3) | |
204 | set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb) | |
205 | else() | |
206 | set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb) | |
207 | endif() | |
208 | ||
209 | if(TBB_STATIC) | |
210 | set(TBB_STATIC_SUFFIX "_static") | |
211 | endif() | |
212 | ||
213 | # Find each component | |
214 | foreach(_comp ${TBB_SEARCH_COMPOMPONENTS}) | |
215 | if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};") | |
216 | ||
217 | # Search for the libraries | |
218 | find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX} | |
219 | HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} | |
220 | PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH | |
221 | PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) | |
222 | ||
223 | find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug | |
224 | HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} | |
225 | PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH | |
226 | PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) | |
227 | ||
228 | if(TBB_${_comp}_LIBRARY_DEBUG) | |
229 | list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}") | |
230 | endif() | |
231 | if(TBB_${_comp}_LIBRARY_RELEASE) | |
232 | list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}") | |
233 | endif() | |
234 | if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY) | |
235 | set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}") | |
236 | endif() | |
237 | ||
238 | if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}") | |
239 | set(TBB_${_comp}_FOUND TRUE) | |
240 | else() | |
241 | set(TBB_${_comp}_FOUND FALSE) | |
242 | endif() | |
243 | ||
244 | # Mark internal variables as advanced | |
245 | mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE) | |
246 | mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG) | |
247 | mark_as_advanced(TBB_${_comp}_LIBRARY) | |
248 | ||
249 | endif() | |
250 | endforeach() | |
251 | ||
252 | unset(TBB_STATIC_SUFFIX) | |
253 | ||
254 | ################################## | |
255 | # Set compile flags and libraries | |
256 | ################################## | |
257 | ||
258 | set(TBB_DEFINITIONS_RELEASE "") | |
259 | set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1") | |
260 | ||
261 | if(TBB_LIBRARIES_${TBB_BUILD_TYPE}) | |
262 | set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}") | |
263 | set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}") | |
264 | elseif(TBB_LIBRARIES_RELEASE) | |
265 | set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}") | |
266 | set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}") | |
267 | elseif(TBB_LIBRARIES_DEBUG) | |
268 | set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}") | |
269 | set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}") | |
270 | endif() | |
271 | ||
272 | find_package_handle_standard_args(TBB | |
273 | REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES | |
274 | HANDLE_COMPONENTS | |
275 | VERSION_VAR TBB_VERSION) | |
276 | ||
277 | ################################## | |
278 | # Create targets | |
279 | ################################## | |
280 | ||
281 | if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) | |
282 | add_library(tbb SHARED IMPORTED) | |
283 | set_target_properties(tbb PROPERTIES | |
284 | INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS} | |
285 | IMPORTED_LOCATION ${TBB_LIBRARIES}) | |
286 | if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG) | |
287 | set_target_properties(tbb PROPERTIES | |
288 | INTERFACE_COMPILE_DEFINITIONS "$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:TBB_USE_DEBUG=1>" | |
289 | IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG} | |
290 | IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG} | |
291 | IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE} | |
292 | IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE} | |
293 | ) | |
294 | elseif(TBB_LIBRARIES_RELEASE) | |
295 | set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE}) | |
296 | else() | |
297 | set_target_properties(tbb PROPERTIES | |
298 | INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}" | |
299 | IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG} | |
300 | ) | |
301 | endif() | |
302 | endif() | |
303 | ||
304 | mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES) | |
305 | ||
306 | unset(TBB_ARCHITECTURE) | |
307 | unset(TBB_BUILD_TYPE) | |
308 | unset(TBB_LIB_PATH_SUFFIX) | |
309 | unset(TBB_DEFAULT_SEARCH_DIR) | |
310 | ||
311 | if(TBB_DEBUG) | |
312 | message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}") | |
313 | message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}") | |
314 | message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}") | |
315 | message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}") | |
316 | message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}") | |
317 | message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}") | |
318 | message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}") | |
319 | endif() | |
320 | ||
321 | endif() |
0 | 0 | #include <iostream> |
1 | 1 | #include <string> |
2 | 2 | #include <fstream> |
3 | ||
4 | 3 | //#define DEBUG_EXPORT_NFP |
5 | 4 | |
6 | 5 | #include <libnest2d.h> |
8 | 7 | #include "tests/printer_parts.h" |
9 | 8 | #include "tools/benchmark.h" |
10 | 9 | #include "tools/svgtools.hpp" |
10 | #include "libnest2d/rotfinder.hpp" | |
11 | ||
11 | 12 | //#include "tools/libnfpglue.hpp" |
13 | //#include "tools/nfp_svgnest_glue.hpp" | |
14 | ||
12 | 15 | |
13 | 16 | using namespace libnest2d; |
14 | 17 | using ItemGroup = std::vector<std::reference_wrapper<Item>>; |
49 | 52 | using namespace libnest2d; |
50 | 53 | |
51 | 54 | const int SCALE = 1000000; |
52 | // const int SCALE = 1; | |
53 | std::vector<Rectangle> rects = { | |
54 | {80*SCALE, 80*SCALE}, | |
55 | {60*SCALE, 90*SCALE}, | |
56 | {70*SCALE, 30*SCALE}, | |
57 | {80*SCALE, 60*SCALE}, | |
58 | {60*SCALE, 60*SCALE}, | |
59 | {60*SCALE, 40*SCALE}, | |
60 | {40*SCALE, 40*SCALE}, | |
61 | {10*SCALE, 10*SCALE}, | |
62 | {10*SCALE, 10*SCALE}, | |
63 | {10*SCALE, 10*SCALE}, | |
64 | {10*SCALE, 10*SCALE}, | |
65 | {10*SCALE, 10*SCALE}, | |
66 | {5*SCALE, 5*SCALE}, | |
67 | {5*SCALE, 5*SCALE}, | |
68 | {5*SCALE, 5*SCALE}, | |
69 | {5*SCALE, 5*SCALE}, | |
70 | {5*SCALE, 5*SCALE}, | |
71 | {5*SCALE, 5*SCALE}, | |
72 | {5*SCALE, 5*SCALE}, | |
73 | {20*SCALE, 20*SCALE} | |
74 | }; | |
75 | ||
76 | // std::vector<Rectangle> rects = { | |
77 | // {20*SCALE, 10*SCALE}, | |
78 | // {20*SCALE, 10*SCALE}, | |
79 | // {20*SCALE, 20*SCALE}, | |
80 | // }; | |
81 | ||
82 | // std::vector<Item> input { | |
83 | // {{0, 0}, {0, 20*SCALE}, {10*SCALE, 0}, {0, 0}} | |
84 | // }; | |
85 | ||
86 | std::vector<Item> crasher = | |
87 | { | |
88 | { | |
89 | {-5000000, 8954050}, | |
90 | {5000000, 8954050}, | |
91 | {5000000, -45949}, | |
92 | {4972609, -568549}, | |
93 | {3500000, -8954050}, | |
94 | {-3500000, -8954050}, | |
95 | {-4972609, -568549}, | |
96 | {-5000000, -45949}, | |
97 | {-5000000, 8954050}, | |
98 | }, | |
99 | { | |
100 | {-5000000, 8954050}, | |
101 | {5000000, 8954050}, | |
102 | {5000000, -45949}, | |
103 | {4972609, -568549}, | |
104 | {3500000, -8954050}, | |
105 | {-3500000, -8954050}, | |
106 | {-4972609, -568549}, | |
107 | {-5000000, -45949}, | |
108 | {-5000000, 8954050}, | |
109 | }, | |
110 | { | |
111 | {-5000000, 8954050}, | |
112 | {5000000, 8954050}, | |
113 | {5000000, -45949}, | |
114 | {4972609, -568549}, | |
115 | {3500000, -8954050}, | |
116 | {-3500000, -8954050}, | |
117 | {-4972609, -568549}, | |
118 | {-5000000, -45949}, | |
119 | {-5000000, 8954050}, | |
120 | }, | |
121 | { | |
122 | {-5000000, 8954050}, | |
123 | {5000000, 8954050}, | |
124 | {5000000, -45949}, | |
125 | {4972609, -568549}, | |
126 | {3500000, -8954050}, | |
127 | {-3500000, -8954050}, | |
128 | {-4972609, -568549}, | |
129 | {-5000000, -45949}, | |
130 | {-5000000, 8954050}, | |
131 | }, | |
132 | { | |
133 | {-5000000, 8954050}, | |
134 | {5000000, 8954050}, | |
135 | {5000000, -45949}, | |
136 | {4972609, -568549}, | |
137 | {3500000, -8954050}, | |
138 | {-3500000, -8954050}, | |
139 | {-4972609, -568549}, | |
140 | {-5000000, -45949}, | |
141 | {-5000000, 8954050}, | |
142 | }, | |
143 | { | |
144 | {-5000000, 8954050}, | |
145 | {5000000, 8954050}, | |
146 | {5000000, -45949}, | |
147 | {4972609, -568549}, | |
148 | {3500000, -8954050}, | |
149 | {-3500000, -8954050}, | |
150 | {-4972609, -568549}, | |
151 | {-5000000, -45949}, | |
152 | {-5000000, 8954050}, | |
153 | }, | |
154 | { | |
155 | {-9945219, -3065619}, | |
156 | {-9781479, -2031780}, | |
157 | {-9510560, -1020730}, | |
158 | {-9135450, -43529}, | |
159 | {-2099999, 14110899}, | |
160 | {2099999, 14110899}, | |
161 | {9135450, -43529}, | |
162 | {9510560, -1020730}, | |
163 | {9781479, -2031780}, | |
164 | {9945219, -3065619}, | |
165 | {10000000, -4110899}, | |
166 | {9945219, -5156179}, | |
167 | {9781479, -6190020}, | |
168 | {9510560, -7201069}, | |
169 | {9135450, -8178270}, | |
170 | {8660249, -9110899}, | |
171 | {8090169, -9988750}, | |
172 | {7431449, -10802200}, | |
173 | {6691309, -11542300}, | |
174 | {5877850, -12201100}, | |
175 | {5000000, -12771100}, | |
176 | {4067369, -13246399}, | |
177 | {3090169, -13621500}, | |
178 | {2079119, -13892399}, | |
179 | {1045279, -14056099}, | |
180 | {0, -14110899}, | |
181 | {-1045279, -14056099}, | |
182 | {-2079119, -13892399}, | |
183 | {-3090169, -13621500}, | |
184 | {-4067369, -13246399}, | |
185 | {-5000000, -12771100}, | |
186 | {-5877850, -12201100}, | |
187 | {-6691309, -11542300}, | |
188 | {-7431449, -10802200}, | |
189 | {-8090169, -9988750}, | |
190 | {-8660249, -9110899}, | |
191 | {-9135450, -8178270}, | |
192 | {-9510560, -7201069}, | |
193 | {-9781479, -6190020}, | |
194 | {-9945219, -5156179}, | |
195 | {-10000000, -4110899}, | |
196 | {-9945219, -3065619}, | |
197 | }, | |
198 | { | |
199 | {-9945219, -3065619}, | |
200 | {-9781479, -2031780}, | |
201 | {-9510560, -1020730}, | |
202 | {-9135450, -43529}, | |
203 | {-2099999, 14110899}, | |
204 | {2099999, 14110899}, | |
205 | {9135450, -43529}, | |
206 | {9510560, -1020730}, | |
207 | {9781479, -2031780}, | |
208 | {9945219, -3065619}, | |
209 | {10000000, -4110899}, | |
210 | {9945219, -5156179}, | |
211 | {9781479, -6190020}, | |
212 | {9510560, -7201069}, | |
213 | {9135450, -8178270}, | |
214 | {8660249, -9110899}, | |
215 | {8090169, -9988750}, | |
216 | {7431449, -10802200}, | |
217 | {6691309, -11542300}, | |
218 | {5877850, -12201100}, | |
219 | {5000000, -12771100}, | |
220 | {4067369, -13246399}, | |
221 | {3090169, -13621500}, | |
222 | {2079119, -13892399}, | |
223 | {1045279, -14056099}, | |
224 | {0, -14110899}, | |
225 | {-1045279, -14056099}, | |
226 | {-2079119, -13892399}, | |
227 | {-3090169, -13621500}, | |
228 | {-4067369, -13246399}, | |
229 | {-5000000, -12771100}, | |
230 | {-5877850, -12201100}, | |
231 | {-6691309, -11542300}, | |
232 | {-7431449, -10802200}, | |
233 | {-8090169, -9988750}, | |
234 | {-8660249, -9110899}, | |
235 | {-9135450, -8178270}, | |
236 | {-9510560, -7201069}, | |
237 | {-9781479, -6190020}, | |
238 | {-9945219, -5156179}, | |
239 | {-10000000, -4110899}, | |
240 | {-9945219, -3065619}, | |
241 | }, | |
242 | { | |
243 | {-9945219, -3065619}, | |
244 | {-9781479, -2031780}, | |
245 | {-9510560, -1020730}, | |
246 | {-9135450, -43529}, | |
247 | {-2099999, 14110899}, | |
248 | {2099999, 14110899}, | |
249 | {9135450, -43529}, | |
250 | {9510560, -1020730}, | |
251 | {9781479, -2031780}, | |
252 | {9945219, -3065619}, | |
253 | {10000000, -4110899}, | |
254 | {9945219, -5156179}, | |
255 | {9781479, -6190020}, | |
256 | {9510560, -7201069}, | |
257 | {9135450, -8178270}, | |
258 | {8660249, -9110899}, | |
259 | {8090169, -9988750}, | |
260 | {7431449, -10802200}, | |
261 | {6691309, -11542300}, | |
262 | {5877850, -12201100}, | |
263 | {5000000, -12771100}, | |
264 | {4067369, -13246399}, | |
265 | {3090169, -13621500}, | |
266 | {2079119, -13892399}, | |
267 | {1045279, -14056099}, | |
268 | {0, -14110899}, | |
269 | {-1045279, -14056099}, | |
270 | {-2079119, -13892399}, | |
271 | {-3090169, -13621500}, | |
272 | {-4067369, -13246399}, | |
273 | {-5000000, -12771100}, | |
274 | {-5877850, -12201100}, | |
275 | {-6691309, -11542300}, | |
276 | {-7431449, -10802200}, | |
277 | {-8090169, -9988750}, | |
278 | {-8660249, -9110899}, | |
279 | {-9135450, -8178270}, | |
280 | {-9510560, -7201069}, | |
281 | {-9781479, -6190020}, | |
282 | {-9945219, -5156179}, | |
283 | {-10000000, -4110899}, | |
284 | {-9945219, -3065619}, | |
285 | }, | |
286 | { | |
287 | {-9945219, -3065619}, | |
288 | {-9781479, -2031780}, | |
289 | {-9510560, -1020730}, | |
290 | {-9135450, -43529}, | |
291 | {-2099999, 14110899}, | |
292 | {2099999, 14110899}, | |
293 | {9135450, -43529}, | |
294 | {9510560, -1020730}, | |
295 | {9781479, -2031780}, | |
296 | {9945219, -3065619}, | |
297 | {10000000, -4110899}, | |
298 | {9945219, -5156179}, | |
299 | {9781479, -6190020}, | |
300 | {9510560, -7201069}, | |
301 | {9135450, -8178270}, | |
302 | {8660249, -9110899}, | |
303 | {8090169, -9988750}, | |
304 | {7431449, -10802200}, | |
305 | {6691309, -11542300}, | |
306 | {5877850, -12201100}, | |
307 | {5000000, -12771100}, | |
308 | {4067369, -13246399}, | |
309 | {3090169, -13621500}, | |
310 | {2079119, -13892399}, | |
311 | {1045279, -14056099}, | |
312 | {0, -14110899}, | |
313 | {-1045279, -14056099}, | |
314 | {-2079119, -13892399}, | |
315 | {-3090169, -13621500}, | |
316 | {-4067369, -13246399}, | |
317 | {-5000000, -12771100}, | |
318 | {-5877850, -12201100}, | |
319 | {-6691309, -11542300}, | |
320 | {-7431449, -10802200}, | |
321 | {-8090169, -9988750}, | |
322 | {-8660249, -9110899}, | |
323 | {-9135450, -8178270}, | |
324 | {-9510560, -7201069}, | |
325 | {-9781479, -6190020}, | |
326 | {-9945219, -5156179}, | |
327 | {-10000000, -4110899}, | |
328 | {-9945219, -3065619}, | |
329 | }, | |
330 | { | |
331 | {-9945219, -3065619}, | |
332 | {-9781479, -2031780}, | |
333 | {-9510560, -1020730}, | |
334 | {-9135450, -43529}, | |
335 | {-2099999, 14110899}, | |
336 | {2099999, 14110899}, | |
337 | {9135450, -43529}, | |
338 | {9510560, -1020730}, | |
339 | {9781479, -2031780}, | |
340 | {9945219, -3065619}, | |
341 | {10000000, -4110899}, | |
342 | {9945219, -5156179}, | |
343 | {9781479, -6190020}, | |
344 | {9510560, -7201069}, | |
345 | {9135450, -8178270}, | |
346 | {8660249, -9110899}, | |
347 | {8090169, -9988750}, | |
348 | {7431449, -10802200}, | |
349 | {6691309, -11542300}, | |
350 | {5877850, -12201100}, | |
351 | {5000000, -12771100}, | |
352 | {4067369, -13246399}, | |
353 | {3090169, -13621500}, | |
354 | {2079119, -13892399}, | |
355 | {1045279, -14056099}, | |
356 | {0, -14110899}, | |
357 | {-1045279, -14056099}, | |
358 | {-2079119, -13892399}, | |
359 | {-3090169, -13621500}, | |
360 | {-4067369, -13246399}, | |
361 | {-5000000, -12771100}, | |
362 | {-5877850, -12201100}, | |
363 | {-6691309, -11542300}, | |
364 | {-7431449, -10802200}, | |
365 | {-8090169, -9988750}, | |
366 | {-8660249, -9110899}, | |
367 | {-9135450, -8178270}, | |
368 | {-9510560, -7201069}, | |
369 | {-9781479, -6190020}, | |
370 | {-9945219, -5156179}, | |
371 | {-10000000, -4110899}, | |
372 | {-9945219, -3065619}, | |
373 | }, | |
374 | { | |
375 | {-9945219, -3065619}, | |
376 | {-9781479, -2031780}, | |
377 | {-9510560, -1020730}, | |
378 | {-9135450, -43529}, | |
379 | {-2099999, 14110899}, | |
380 | {2099999, 14110899}, | |
381 | {9135450, -43529}, | |
382 | {9510560, -1020730}, | |
383 | {9781479, -2031780}, | |
384 | {9945219, -3065619}, | |
385 | {10000000, -4110899}, | |
386 | {9945219, -5156179}, | |
387 | {9781479, -6190020}, | |
388 | {9510560, -7201069}, | |
389 | {9135450, -8178270}, | |
390 | {8660249, -9110899}, | |
391 | {8090169, -9988750}, | |
392 | {7431449, -10802200}, | |
393 | {6691309, -11542300}, | |
394 | {5877850, -12201100}, | |
395 | {5000000, -12771100}, | |
396 | {4067369, -13246399}, | |
397 | {3090169, -13621500}, | |
398 | {2079119, -13892399}, | |
399 | {1045279, -14056099}, | |
400 | {0, -14110899}, | |
401 | {-1045279, -14056099}, | |
402 | {-2079119, -13892399}, | |
403 | {-3090169, -13621500}, | |
404 | {-4067369, -13246399}, | |
405 | {-5000000, -12771100}, | |
406 | {-5877850, -12201100}, | |
407 | {-6691309, -11542300}, | |
408 | {-7431449, -10802200}, | |
409 | {-8090169, -9988750}, | |
410 | {-8660249, -9110899}, | |
411 | {-9135450, -8178270}, | |
412 | {-9510560, -7201069}, | |
413 | {-9781479, -6190020}, | |
414 | {-9945219, -5156179}, | |
415 | {-10000000, -4110899}, | |
416 | {-9945219, -3065619}, | |
417 | }, | |
418 | { | |
419 | {-9945219, -3065619}, | |
420 | {-9781479, -2031780}, | |
421 | {-9510560, -1020730}, | |
422 | {-9135450, -43529}, | |
423 | {-2099999, 14110899}, | |
424 | {2099999, 14110899}, | |
425 | {9135450, -43529}, | |
426 | {9510560, -1020730}, | |
427 | {9781479, -2031780}, | |
428 | {9945219, -3065619}, | |
429 | {10000000, -4110899}, | |
430 | {9945219, -5156179}, | |
431 | {9781479, -6190020}, | |
432 | {9510560, -7201069}, | |
433 | {9135450, -8178270}, | |
434 | {8660249, -9110899}, | |
435 | {8090169, -9988750}, | |
436 | {7431449, -10802200}, | |
437 | {6691309, -11542300}, | |
438 | {5877850, -12201100}, | |
439 | {5000000, -12771100}, | |
440 | {4067369, -13246399}, | |
441 | {3090169, -13621500}, | |
442 | {2079119, -13892399}, | |
443 | {1045279, -14056099}, | |
444 | {0, -14110899}, | |
445 | {-1045279, -14056099}, | |
446 | {-2079119, -13892399}, | |
447 | {-3090169, -13621500}, | |
448 | {-4067369, -13246399}, | |
449 | {-5000000, -12771100}, | |
450 | {-5877850, -12201100}, | |
451 | {-6691309, -11542300}, | |
452 | {-7431449, -10802200}, | |
453 | {-8090169, -9988750}, | |
454 | {-8660249, -9110899}, | |
455 | {-9135450, -8178270}, | |
456 | {-9510560, -7201069}, | |
457 | {-9781479, -6190020}, | |
458 | {-9945219, -5156179}, | |
459 | {-10000000, -4110899}, | |
460 | {-9945219, -3065619}, | |
461 | }, | |
462 | { | |
463 | {-9945219, -3065619}, | |
464 | {-9781479, -2031780}, | |
465 | {-9510560, -1020730}, | |
466 | {-9135450, -43529}, | |
467 | {-2099999, 14110899}, | |
468 | {2099999, 14110899}, | |
469 | {9135450, -43529}, | |
470 | {9510560, -1020730}, | |
471 | {9781479, -2031780}, | |
472 | {9945219, -3065619}, | |
473 | {10000000, -4110899}, | |
474 | {9945219, -5156179}, | |
475 | {9781479, -6190020}, | |
476 | {9510560, -7201069}, | |
477 | {9135450, -8178270}, | |
478 | {8660249, -9110899}, | |
479 | {8090169, -9988750}, | |
480 | {7431449, -10802200}, | |
481 | {6691309, -11542300}, | |
482 | {5877850, -12201100}, | |
483 | {5000000, -12771100}, | |
484 | {4067369, -13246399}, | |
485 | {3090169, -13621500}, | |
486 | {2079119, -13892399}, | |
487 | {1045279, -14056099}, | |
488 | {0, -14110899}, | |
489 | {-1045279, -14056099}, | |
490 | {-2079119, -13892399}, | |
491 | {-3090169, -13621500}, | |
492 | {-4067369, -13246399}, | |
493 | {-5000000, -12771100}, | |
494 | {-5877850, -12201100}, | |
495 | {-6691309, -11542300}, | |
496 | {-7431449, -10802200}, | |
497 | {-8090169, -9988750}, | |
498 | {-8660249, -9110899}, | |
499 | {-9135450, -8178270}, | |
500 | {-9510560, -7201069}, | |
501 | {-9781479, -6190020}, | |
502 | {-9945219, -5156179}, | |
503 | {-10000000, -4110899}, | |
504 | {-9945219, -3065619}, | |
505 | }, | |
506 | { | |
507 | {-18000000, -1000000}, | |
508 | {-15000000, 22000000}, | |
509 | {-11000000, 26000000}, | |
510 | {11000000, 26000000}, | |
511 | {15000000, 22000000}, | |
512 | {18000000, -1000000}, | |
513 | {18000000, -26000000}, | |
514 | {-18000000, -26000000}, | |
515 | {-18000000, -1000000}, | |
516 | }, | |
517 | }; | |
518 | ||
519 | std::vector<Item> proba = { | |
520 | { | |
521 | Rectangle(100, 2) | |
522 | }, | |
523 | { | |
524 | Rectangle(100, 2) | |
525 | }, | |
526 | { | |
527 | Rectangle(100, 2) | |
528 | }, | |
529 | { | |
530 | Rectangle(10, 10) | |
531 | }, | |
532 | }; | |
533 | ||
534 | proba[0].rotate(Pi/3); | |
535 | proba[1].rotate(Pi-Pi/3); | |
536 | ||
537 | // std::vector<Item> input(25, Rectangle(70*SCALE, 10*SCALE)); | |
55 | ||
56 | std::vector<Item> rects(202, { | |
57 | {-9945219, -3065619}, | |
58 | {-9781479, -2031780}, | |
59 | {-9510560, -1020730}, | |
60 | {-9135450, -43529}, | |
61 | {-2099999, 14110899}, | |
62 | {2099999, 14110899}, | |
63 | {9135450, -43529}, | |
64 | {9510560, -1020730}, | |
65 | {9781479, -2031780}, | |
66 | {9945219, -3065619}, | |
67 | {10000000, -4110899}, | |
68 | {9945219, -5156179}, | |
69 | {9781479, -6190019}, | |
70 | {9510560, -7201069}, | |
71 | {9135450, -8178270}, | |
72 | {8660249, -9110899}, | |
73 | {8090169, -9988750}, | |
74 | {7431449, -10802209}, | |
75 | {6691309, -11542349}, | |
76 | {5877850, -12201069}, | |
77 | {5000000, -12771149}, | |
78 | {4067369, -13246350}, | |
79 | {3090169, -13621459}, | |
80 | {2079119, -13892379}, | |
81 | {1045279, -14056119}, | |
82 | {0, -14110899}, | |
83 | {-1045279, -14056119}, | |
84 | {-2079119, -13892379}, | |
85 | {-3090169, -13621459}, | |
86 | {-4067369, -13246350}, | |
87 | {-5000000, -12771149}, | |
88 | {-5877850, -12201069}, | |
89 | {-6691309, -11542349}, | |
90 | {-7431449, -10802209}, | |
91 | {-8090169, -9988750}, | |
92 | {-8660249, -9110899}, | |
93 | {-9135450, -8178270}, | |
94 | {-9510560, -7201069}, | |
95 | {-9781479, -6190019}, | |
96 | {-9945219, -5156179}, | |
97 | {-10000000, -4110899}, | |
98 | {-9945219, -3065619}, | |
99 | }); | |
100 | ||
538 | 101 | std::vector<Item> input; |
539 | 102 | input.insert(input.end(), prusaParts().begin(), prusaParts().end()); |
540 | 103 | // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); |
541 | 104 | // input.insert(input.end(), stegoParts().begin(), stegoParts().end()); |
542 | 105 | // input.insert(input.end(), rects.begin(), rects.end()); |
543 | // input.insert(input.end(), proba.begin(), proba.end()); | |
544 | // input.insert(input.end(), crasher.begin(), crasher.end()); | |
545 | 106 | |
546 | 107 | Box bin(250*SCALE, 210*SCALE); |
547 | 108 | // PolygonImpl bin = { |
559 | 120 | // {} |
560 | 121 | // }; |
561 | 122 | |
562 | auto min_obj_distance = static_cast<Coord>(0*SCALE); | |
563 | ||
564 | using Placer = strategies::_NofitPolyPlacer<PolygonImpl, Box>; | |
565 | using Packer = Arranger<Placer, FirstFitSelection>; | |
123 | // Circle bin({0, 0}, 125*SCALE); | |
124 | ||
125 | auto min_obj_distance = static_cast<Coord>(6*SCALE); | |
126 | ||
127 | using Placer = placers::_NofitPolyPlacer<PolygonImpl, decltype(bin)>; | |
128 | using Packer = Nester<Placer, FirstFitSelection>; | |
566 | 129 | |
567 | 130 | Packer arrange(bin, min_obj_distance); |
568 | 131 | |
570 | 133 | pconf.alignment = Placer::Config::Alignment::CENTER; |
571 | 134 | pconf.starting_point = Placer::Config::Alignment::CENTER; |
572 | 135 | pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; |
573 | pconf.accuracy = 0.5f; | |
574 | ||
575 | // auto bincenter = ShapeLike::boundingBox(bin).center(); | |
576 | // pconf.object_function = [&bin, bincenter]( | |
577 | // Placer::Pile pile, const Item& item, | |
578 | // double /*area*/, double norm, double penality) { | |
579 | ||
580 | // using pl = PointLike; | |
581 | ||
582 | // static const double BIG_ITEM_TRESHOLD = 0.2; | |
583 | // static const double GRAVITY_RATIO = 0.5; | |
584 | // static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; | |
585 | ||
586 | // // We will treat big items (compared to the print bed) differently | |
587 | // NfpPlacer::Pile bigs; | |
588 | // bigs.reserve(pile.size()); | |
589 | // for(auto& p : pile) { | |
590 | // auto pbb = ShapeLike::boundingBox(p); | |
591 | // auto na = std::sqrt(pbb.width()*pbb.height())/norm; | |
592 | // if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); | |
593 | // } | |
594 | ||
595 | // // Candidate item bounding box | |
596 | // auto ibb = item.boundingBox(); | |
597 | ||
598 | // // Calculate the full bounding box of the pile with the candidate item | |
599 | // pile.emplace_back(item.transformedShape()); | |
600 | // auto fullbb = ShapeLike::boundingBox(pile); | |
601 | // pile.pop_back(); | |
602 | ||
603 | // // The bounding box of the big items (they will accumulate in the center | |
604 | // // of the pile | |
605 | // auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); | |
606 | ||
607 | // // The size indicator of the candidate item. This is not the area, | |
608 | // // but almost... | |
609 | // auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; | |
610 | ||
611 | // // Will hold the resulting score | |
612 | // double score = 0; | |
613 | ||
614 | // if(itemnormarea > BIG_ITEM_TRESHOLD) { | |
615 | // // This branch is for the bigger items.. | |
616 | // // Here we will use the closest point of the item bounding box to | |
617 | // // the already arranged pile. So not the bb center nor the a choosen | |
618 | // // corner but whichever is the closest to the center. This will | |
619 | // // prevent unwanted strange arrangements. | |
620 | ||
621 | // auto minc = ibb.minCorner(); // bottom left corner | |
622 | // auto maxc = ibb.maxCorner(); // top right corner | |
623 | ||
624 | // // top left and bottom right corners | |
625 | // auto top_left = PointImpl{getX(minc), getY(maxc)}; | |
626 | // auto bottom_right = PointImpl{getX(maxc), getY(minc)}; | |
627 | ||
628 | // auto cc = fullbb.center(); // The gravity center | |
629 | ||
630 | // // Now the distnce of the gravity center will be calculated to the | |
631 | // // five anchor points and the smallest will be chosen. | |
632 | // std::array<double, 5> dists; | |
633 | // dists[0] = pl::distance(minc, cc); | |
634 | // dists[1] = pl::distance(maxc, cc); | |
635 | // dists[2] = pl::distance(ibb.center(), cc); | |
636 | // dists[3] = pl::distance(top_left, cc); | |
637 | // dists[4] = pl::distance(bottom_right, cc); | |
638 | ||
639 | // auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; | |
640 | ||
641 | // // Density is the pack density: how big is the arranged pile | |
642 | // auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | |
643 | ||
644 | // // The score is a weighted sum of the distance from pile center | |
645 | // // and the pile size | |
646 | // score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; | |
647 | ||
648 | // } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { | |
649 | // // If there are no big items, only small, we should consider the | |
650 | // // density here as well to not get silly results | |
651 | // auto bindist = pl::distance(ibb.center(), bincenter) / norm; | |
652 | // auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | |
653 | // score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; | |
654 | // } else { | |
655 | // // Here there are the small items that should be placed around the | |
656 | // // already processed bigger items. | |
657 | // // No need to play around with the anchor points, the center will be | |
658 | // // just fine for small items | |
659 | // score = pl::distance(ibb.center(), bigbb.center()) / norm; | |
660 | // } | |
661 | ||
662 | // // If it does not fit into the print bed we will beat it | |
663 | // // with a large penality. If we would not do this, there would be only | |
664 | // // one big pile that doesn't care whether it fits onto the print bed. | |
665 | // if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; | |
666 | ||
667 | // return score; | |
668 | // }; | |
136 | pconf.accuracy = 0.65f; | |
137 | pconf.parallel = true; | |
669 | 138 | |
670 | 139 | Packer::SelectionConfig sconf; |
671 | 140 | // sconf.allow_parallel = false; |
672 | 141 | // sconf.force_parallel = false; |
673 | 142 | // sconf.try_triplets = true; |
674 | 143 | // sconf.try_reverse_order = true; |
675 | // sconf.waste_increment = 0.005; | |
144 | // sconf.waste_increment = 0.01; | |
676 | 145 | |
677 | 146 | arrange.configure(pconf, sconf); |
678 | 147 | |
679 | 148 | arrange.progressIndicator([&](unsigned r){ |
680 | // svg::SVGWriter::Config conf; | |
681 | // conf.mm_in_coord_units = SCALE; | |
682 | // svg::SVGWriter svgw(conf); | |
683 | // svgw.setSize(bin); | |
684 | // svgw.writePackGroup(arrange.lastResult()); | |
685 | // svgw.save("debout"); | |
686 | 149 | std::cout << "Remaining items: " << r << std::endl; |
687 | })/*.useMinimumBoundigBoxRotation()*/; | |
150 | }); | |
151 | ||
152 | // findMinimumBoundingBoxRotations(input.begin(), input.end()); | |
688 | 153 | |
689 | 154 | Benchmark bench; |
690 | 155 | |
692 | 157 | Packer::ResultType result; |
693 | 158 | |
694 | 159 | try { |
695 | result = arrange.arrange(input.begin(), input.end()); | |
160 | result = arrange.execute(input.begin(), input.end()); | |
696 | 161 | } catch(GeometryException& ge) { |
697 | 162 | std::cerr << "Geometry error: " << ge.what() << std::endl; |
698 | 163 | return ; |
706 | 171 | std::vector<double> eff; |
707 | 172 | eff.reserve(result.size()); |
708 | 173 | |
709 | auto bin_area = ShapeLike::area<PolygonImpl>(bin); | |
174 | auto bin_area = sl::area(bin); | |
710 | 175 | for(auto& r : result) { |
711 | 176 | double a = 0; |
712 | 177 | std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); |
727 | 192 | for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); } |
728 | 193 | std::cout << ") Total: " << total << std::endl; |
729 | 194 | |
730 | for(auto& it : input) { | |
731 | auto ret = ShapeLike::isValid(it.transformedShape()); | |
732 | std::cout << ret.second << std::endl; | |
733 | } | |
195 | // for(auto& it : input) { | |
196 | // auto ret = sl::isValid(it.transformedShape()); | |
197 | // std::cout << ret.second << std::endl; | |
198 | // } | |
734 | 199 | |
735 | 200 | if(total != input.size()) std::cout << "ERROR " << "could not pack " |
736 | 201 | << input.size() - total << " elements!" |
743 | 208 | SVGWriter svgw(conf); |
744 | 209 | svgw.setSize(Box(250*SCALE, 210*SCALE)); |
745 | 210 | svgw.writePackGroup(result); |
746 | // std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);}); | |
747 | 211 | svgw.save("out"); |
748 | 212 | } |
749 | 213 | |
750 | 214 | int main(void /*int argc, char **argv*/) { |
751 | 215 | arrangeRectangles(); |
752 | // findDegenerateCase(); | |
753 | ||
754 | 216 | return EXIT_SUCCESS; |
755 | 217 | } |
35 | 35 | using libnest2d::setY; |
36 | 36 | using Box = libnest2d::_Box<PointImpl>; |
37 | 37 | using Segment = libnest2d::_Segment<PointImpl>; |
38 | using Shapes = libnest2d::Nfp::Shapes<PolygonImpl>; | |
38 | using Shapes = libnest2d::nfp::Shapes<PolygonImpl>; | |
39 | 39 | |
40 | 40 | } |
41 | 41 | |
240 | 240 | |
241 | 241 | template<> struct exterior_ring<bp2d::PolygonImpl> { |
242 | 242 | static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) { |
243 | return libnest2d::ShapeLike::getContour(p); | |
243 | return libnest2d::shapelike::getContour(p); | |
244 | 244 | } |
245 | 245 | |
246 | 246 | static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) { |
247 | return libnest2d::ShapeLike::getContour(p); | |
247 | return libnest2d::shapelike::getContour(p); | |
248 | 248 | } |
249 | 249 | }; |
250 | 250 | |
270 | 270 | static inline libnest2d::THolesContainer<bp2d::PolygonImpl>& get( |
271 | 271 | bp2d::PolygonImpl& p) |
272 | 272 | { |
273 | return libnest2d::ShapeLike::holes(p); | |
273 | return libnest2d::shapelike::holes(p); | |
274 | 274 | } |
275 | 275 | |
276 | 276 | static inline const libnest2d::THolesContainer<bp2d::PolygonImpl>& get( |
277 | 277 | bp2d::PolygonImpl const& p) |
278 | 278 | { |
279 | return libnest2d::ShapeLike::holes(p); | |
279 | return libnest2d::shapelike::holes(p); | |
280 | 280 | } |
281 | 281 | }; |
282 | 282 | |
310 | 310 | |
311 | 311 | namespace libnest2d { // Now the algorithms that boost can provide... |
312 | 312 | |
313 | template<> | |
314 | inline double PointLike::distance(const PointImpl& p1, | |
315 | const PointImpl& p2 ) | |
313 | namespace pointlike { | |
314 | template<> | |
315 | inline double distance(const PointImpl& p1, const PointImpl& p2 ) | |
316 | 316 | { |
317 | 317 | return boost::geometry::distance(p1, p2); |
318 | 318 | } |
319 | 319 | |
320 | 320 | template<> |
321 | inline double PointLike::distance(const PointImpl& p, | |
322 | const bp2d::Segment& seg ) | |
321 | inline double distance(const PointImpl& p, const bp2d::Segment& seg ) | |
323 | 322 | { |
324 | 323 | return boost::geometry::distance(p, seg); |
325 | 324 | } |
326 | ||
325 | } | |
326 | ||
327 | namespace shapelike { | |
327 | 328 | // Tell libnest2d how to make string out of a ClipperPolygon object |
328 | 329 | template<> |
329 | inline bool ShapeLike::intersects(const PathImpl& sh1, | |
330 | const PathImpl& sh2) | |
330 | inline bool intersects(const PathImpl& sh1, const PathImpl& sh2) | |
331 | 331 | { |
332 | 332 | return boost::geometry::intersects(sh1, sh2); |
333 | 333 | } |
334 | 334 | |
335 | 335 | // Tell libnest2d how to make string out of a ClipperPolygon object |
336 | 336 | template<> |
337 | inline bool ShapeLike::intersects(const PolygonImpl& sh1, | |
338 | const PolygonImpl& sh2) | |
337 | inline bool intersects(const PolygonImpl& sh1, const PolygonImpl& sh2) | |
339 | 338 | { |
340 | 339 | return boost::geometry::intersects(sh1, sh2); |
341 | 340 | } |
342 | 341 | |
343 | 342 | // Tell libnest2d how to make string out of a ClipperPolygon object |
344 | 343 | template<> |
345 | inline bool ShapeLike::intersects(const bp2d::Segment& s1, | |
346 | const bp2d::Segment& s2) | |
344 | inline bool intersects(const bp2d::Segment& s1, const bp2d::Segment& s2) | |
347 | 345 | { |
348 | 346 | return boost::geometry::intersects(s1, s2); |
349 | 347 | } |
350 | 348 | |
351 | 349 | #ifndef DISABLE_BOOST_AREA |
352 | 350 | template<> |
353 | inline double ShapeLike::area(const PolygonImpl& shape) | |
351 | inline double area(const PolygonImpl& shape, const PolygonTag&) | |
354 | 352 | { |
355 | 353 | return boost::geometry::area(shape); |
356 | 354 | } |
357 | 355 | #endif |
358 | 356 | |
359 | 357 | template<> |
360 | inline bool ShapeLike::isInside<PolygonImpl>(const PointImpl& point, | |
361 | const PolygonImpl& shape) | |
358 | inline bool isInside(const PointImpl& point, const PolygonImpl& shape) | |
362 | 359 | { |
363 | 360 | return boost::geometry::within(point, shape); |
364 | 361 | } |
365 | 362 | |
366 | 363 | template<> |
367 | inline bool ShapeLike::isInside(const PolygonImpl& sh1, | |
368 | const PolygonImpl& sh2) | |
364 | inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2) | |
369 | 365 | { |
370 | 366 | return boost::geometry::within(sh1, sh2); |
371 | 367 | } |
372 | 368 | |
373 | 369 | template<> |
374 | inline bool ShapeLike::touches( const PolygonImpl& sh1, | |
375 | const PolygonImpl& sh2) | |
370 | inline bool touches(const PolygonImpl& sh1, const PolygonImpl& sh2) | |
376 | 371 | { |
377 | 372 | return boost::geometry::touches(sh1, sh2); |
378 | 373 | } |
379 | 374 | |
380 | 375 | template<> |
381 | inline bool ShapeLike::touches( const PointImpl& point, | |
382 | const PolygonImpl& shape) | |
376 | inline bool touches( const PointImpl& point, const PolygonImpl& shape) | |
383 | 377 | { |
384 | 378 | return boost::geometry::touches(point, shape); |
385 | 379 | } |
386 | 380 | |
387 | 381 | #ifndef DISABLE_BOOST_BOUNDING_BOX |
388 | 382 | template<> |
389 | inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) | |
383 | inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) | |
390 | 384 | { |
391 | 385 | bp2d::Box b; |
392 | 386 | boost::geometry::envelope(sh, b); |
394 | 388 | } |
395 | 389 | |
396 | 390 | template<> |
397 | inline bp2d::Box ShapeLike::boundingBox<PolygonImpl>(const bp2d::Shapes& shapes) | |
391 | inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes, | |
392 | const MultiPolygonTag&) | |
398 | 393 | { |
399 | 394 | bp2d::Box b; |
400 | 395 | boost::geometry::envelope(shapes, b); |
404 | 399 | |
405 | 400 | #ifndef DISABLE_BOOST_CONVEX_HULL |
406 | 401 | template<> |
407 | inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh) | |
402 | inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&) | |
408 | 403 | { |
409 | 404 | PolygonImpl ret; |
410 | 405 | boost::geometry::convex_hull(sh, ret); |
412 | 407 | } |
413 | 408 | |
414 | 409 | template<> |
415 | inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes) | |
410 | inline PolygonImpl convexHull(const TMultiShape<PolygonImpl>& shapes, | |
411 | const MultiPolygonTag&) | |
416 | 412 | { |
417 | 413 | PolygonImpl ret; |
418 | 414 | boost::geometry::convex_hull(shapes, ret); |
420 | 416 | } |
421 | 417 | #endif |
422 | 418 | |
423 | #ifndef DISABLE_BOOST_ROTATE | |
424 | template<> | |
425 | inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) | |
426 | { | |
427 | namespace trans = boost::geometry::strategy::transform; | |
428 | ||
429 | PolygonImpl cpy = sh; | |
430 | trans::rotate_transformer<boost::geometry::radian, Radians, 2, 2> | |
431 | rotate(rads); | |
432 | ||
433 | boost::geometry::transform(cpy, sh, rotate); | |
434 | } | |
435 | #endif | |
436 | ||
437 | #ifndef DISABLE_BOOST_TRANSLATE | |
438 | template<> | |
439 | inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) | |
440 | { | |
441 | namespace trans = boost::geometry::strategy::transform; | |
442 | ||
443 | PolygonImpl cpy = sh; | |
444 | trans::translate_transformer<bp2d::Coord, 2, 2> translate( | |
445 | bp2d::getX(offs), bp2d::getY(offs)); | |
446 | ||
447 | boost::geometry::transform(cpy, sh, translate); | |
448 | } | |
449 | #endif | |
450 | ||
451 | 419 | #ifndef DISABLE_BOOST_OFFSET |
452 | 420 | template<> |
453 | inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance) | |
421 | inline void offset(PolygonImpl& sh, bp2d::Coord distance) | |
454 | 422 | { |
455 | 423 | PolygonImpl cpy = sh; |
456 | 424 | boost::geometry::buffer(cpy, sh, distance); |
457 | 425 | } |
458 | 426 | #endif |
459 | 427 | |
460 | #ifndef DISABLE_BOOST_NFP_MERGE | |
461 | template<> | |
462 | inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, | |
463 | const PolygonImpl& sh) | |
464 | { | |
465 | bp2d::Shapes retv; | |
466 | boost::geometry::union_(shapes, sh, retv); | |
467 | return retv; | |
468 | } | |
469 | #endif | |
470 | ||
471 | 428 | #ifndef DISABLE_BOOST_SERIALIZE |
472 | template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>( | |
429 | template<> inline std::string serialize<libnest2d::Formats::SVG>( | |
473 | 430 | const PolygonImpl& sh, double scale) |
474 | 431 | { |
475 | 432 | std::stringstream ss; |
481 | 438 | |
482 | 439 | Polygonf::ring_type ring; |
483 | 440 | Polygonf::inner_container_type holes; |
484 | ring.reserve(ShapeLike::contourVertexCount(sh)); | |
485 | ||
486 | for(auto it = ShapeLike::cbegin(sh); it != ShapeLike::cend(sh); it++) { | |
441 | ring.reserve(shapelike::contourVertexCount(sh)); | |
442 | ||
443 | for(auto it = shapelike::cbegin(sh); it != shapelike::cend(sh); it++) { | |
487 | 444 | auto& v = *it; |
488 | 445 | ring.emplace_back(getX(v)*scale, getY(v)*scale); |
489 | 446 | }; |
490 | 447 | |
491 | auto H = ShapeLike::holes(sh); | |
448 | auto H = shapelike::holes(sh); | |
492 | 449 | for(PathImpl& h : H ) { |
493 | 450 | Polygonf::ring_type hf; |
494 | 451 | for(auto it = h.begin(); it != h.end(); it++) { |
511 | 468 | |
512 | 469 | #ifndef DISABLE_BOOST_UNSERIALIZE |
513 | 470 | template<> |
514 | inline void ShapeLike::unserialize<libnest2d::Formats::SVG>( | |
471 | inline void unserialize<libnest2d::Formats::SVG>( | |
515 | 472 | PolygonImpl& sh, |
516 | 473 | const std::string& str) |
517 | 474 | { |
518 | 475 | } |
519 | 476 | #endif |
520 | 477 | |
521 | template<> inline std::pair<bool, std::string> | |
522 | ShapeLike::isValid(const PolygonImpl& sh) | |
478 | template<> inline std::pair<bool, std::string> isValid(const PolygonImpl& sh) | |
523 | 479 | { |
524 | 480 | std::string message; |
525 | 481 | bool ret = boost::geometry::is_valid(sh, message); |
526 | 482 | |
527 | 483 | return {ret, message}; |
528 | 484 | } |
485 | } | |
486 | ||
487 | namespace nfp { | |
488 | ||
489 | #ifndef DISABLE_BOOST_NFP_MERGE | |
490 | ||
491 | // Warning: I could not get boost union_ to work. Geometries will overlap. | |
492 | template<> | |
493 | inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes, | |
494 | const PolygonImpl& sh) | |
495 | { | |
496 | bp2d::Shapes retv; | |
497 | boost::geometry::union_(shapes, sh, retv); | |
498 | return retv; | |
499 | } | |
500 | ||
501 | template<> | |
502 | inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes) | |
503 | { | |
504 | bp2d::Shapes retv; | |
505 | boost::geometry::union_(shapes, shapes.back(), retv); | |
506 | return retv; | |
507 | } | |
508 | #endif | |
509 | ||
510 | } | |
511 | ||
529 | 512 | |
530 | 513 | } |
531 | 514 |
98 | 98 | using Type = PointImpl; |
99 | 99 | }; |
100 | 100 | |
101 | // Type of vertex iterator used by Clipper | |
102 | template<> struct VertexIteratorType<PolygonImpl> { | |
103 | using Type = ClipperLib::Path::iterator; | |
104 | }; | |
105 | ||
106 | // Type of vertex iterator used by Clipper | |
107 | template<> struct VertexConstIteratorType<PolygonImpl> { | |
108 | using Type = ClipperLib::Path::const_iterator; | |
101 | template<> struct PointType<PointImpl> { | |
102 | using Type = PointImpl; | |
109 | 103 | }; |
110 | 104 | |
111 | 105 | template<> struct CountourType<PolygonImpl> { |
112 | 106 | using Type = PathImpl; |
113 | 107 | }; |
114 | 108 | |
115 | // Tell binpack2d how to extract the X coord from a ClipperPoint object | |
116 | template<> inline TCoord<PointImpl> PointLike::x(const PointImpl& p) | |
109 | template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; }; | |
110 | ||
111 | template<> struct ShapeTag<TMultiShape<PolygonImpl>> { | |
112 | using Type = MultiPolygonTag; | |
113 | }; | |
114 | ||
115 | template<> struct PointType<TMultiShape<PolygonImpl>> { | |
116 | using Type = PointImpl; | |
117 | }; | |
118 | ||
119 | template<> struct HolesContainer<PolygonImpl> { | |
120 | using Type = ClipperLib::Paths; | |
121 | }; | |
122 | ||
123 | namespace pointlike { | |
124 | ||
125 | // Tell libnest2d how to extract the X coord from a ClipperPoint object | |
126 | template<> inline TCoord<PointImpl> x(const PointImpl& p) | |
117 | 127 | { |
118 | 128 | return p.X; |
119 | 129 | } |
120 | 130 | |
121 | // Tell binpack2d how to extract the Y coord from a ClipperPoint object | |
122 | template<> inline TCoord<PointImpl> PointLike::y(const PointImpl& p) | |
131 | // Tell libnest2d how to extract the Y coord from a ClipperPoint object | |
132 | template<> inline TCoord<PointImpl> y(const PointImpl& p) | |
123 | 133 | { |
124 | 134 | return p.Y; |
125 | 135 | } |
126 | 136 | |
127 | // Tell binpack2d how to extract the X coord from a ClipperPoint object | |
128 | template<> inline TCoord<PointImpl>& PointLike::x(PointImpl& p) | |
137 | // Tell libnest2d how to extract the X coord from a ClipperPoint object | |
138 | template<> inline TCoord<PointImpl>& x(PointImpl& p) | |
129 | 139 | { |
130 | 140 | return p.X; |
131 | 141 | } |
132 | 142 | |
133 | // Tell binpack2d how to extract the Y coord from a ClipperPoint object | |
134 | template<> | |
135 | inline TCoord<PointImpl>& PointLike::y(PointImpl& p) | |
143 | // Tell libnest2d how to extract the Y coord from a ClipperPoint object | |
144 | template<> inline TCoord<PointImpl>& y(PointImpl& p) | |
136 | 145 | { |
137 | 146 | return p.Y; |
138 | 147 | } |
139 | 148 | |
140 | template<> | |
141 | inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity) | |
142 | { | |
143 | return sh.Contour.reserve(vertex_capacity); | |
144 | 149 | } |
145 | 150 | |
146 | 151 | #define DISABLE_BOOST_AREA |
174 | 179 | |
175 | 180 | return ClipperLib::Area(sh.Contour) + a; |
176 | 181 | } |
177 | } | |
178 | ||
179 | // Tell binpack2d how to make string out of a ClipperPolygon object | |
180 | template<> | |
181 | inline double ShapeLike::area(const PolygonImpl& sh) { | |
182 | ||
183 | } | |
184 | ||
185 | namespace shapelike { | |
186 | ||
187 | template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity) | |
188 | { | |
189 | return sh.Contour.reserve(vertex_capacity); | |
190 | } | |
191 | ||
192 | // Tell libnest2d how to make string out of a ClipperPolygon object | |
193 | template<> inline double area(const PolygonImpl& sh, const PolygonTag&) | |
194 | { | |
182 | 195 | return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh); |
183 | 196 | } |
184 | 197 | |
185 | template<> | |
186 | inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) { | |
198 | template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance) | |
199 | { | |
187 | 200 | #define DISABLE_BOOST_OFFSET |
188 | 201 | |
189 | 202 | using ClipperLib::ClipperOffset; |
233 | 246 | } |
234 | 247 | |
235 | 248 | // Tell libnest2d how to make string out of a ClipperPolygon object |
236 | template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) { | |
249 | template<> inline std::string toString(const PolygonImpl& sh) | |
250 | { | |
237 | 251 | std::stringstream ss; |
238 | 252 | |
239 | 253 | ss << "Contour {\n"; |
256 | 270 | } |
257 | 271 | |
258 | 272 | template<> |
259 | inline TVertexIterator<PolygonImpl> ShapeLike::begin(PolygonImpl& sh) | |
260 | { | |
261 | return sh.Contour.begin(); | |
262 | } | |
263 | ||
264 | template<> | |
265 | inline TVertexIterator<PolygonImpl> ShapeLike::end(PolygonImpl& sh) | |
266 | { | |
267 | return sh.Contour.end(); | |
268 | } | |
269 | ||
270 | template<> | |
271 | inline TVertexConstIterator<PolygonImpl> ShapeLike::cbegin( | |
272 | const PolygonImpl& sh) | |
273 | { | |
274 | return sh.Contour.cbegin(); | |
275 | } | |
276 | ||
277 | template<> | |
278 | inline TVertexConstIterator<PolygonImpl> ShapeLike::cend( | |
279 | const PolygonImpl& sh) | |
280 | { | |
281 | return sh.Contour.cend(); | |
282 | } | |
283 | ||
284 | template<> struct HolesContainer<PolygonImpl> { | |
285 | using Type = ClipperLib::Paths; | |
286 | }; | |
287 | ||
288 | template<> inline PolygonImpl ShapeLike::create(const PathImpl& path, | |
289 | const HoleStore& holes) { | |
273 | inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) | |
274 | { | |
290 | 275 | PolygonImpl p; |
291 | 276 | p.Contour = path; |
292 | 277 | |
307 | 292 | return p; |
308 | 293 | } |
309 | 294 | |
310 | template<> inline PolygonImpl ShapeLike::create( PathImpl&& path, | |
311 | HoleStore&& holes) { | |
295 | template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { | |
312 | 296 | PolygonImpl p; |
313 | 297 | p.Contour.swap(path); |
314 | 298 | |
330 | 314 | return p; |
331 | 315 | } |
332 | 316 | |
333 | template<> inline const THolesContainer<PolygonImpl>& | |
334 | ShapeLike::holes(const PolygonImpl& sh) | |
317 | template<> | |
318 | inline const THolesContainer<PolygonImpl>& holes(const PolygonImpl& sh) | |
335 | 319 | { |
336 | 320 | return sh.Holes; |
337 | 321 | } |
338 | 322 | |
339 | template<> inline THolesContainer<PolygonImpl>& | |
340 | ShapeLike::holes(PolygonImpl& sh) | |
323 | template<> inline THolesContainer<PolygonImpl>& holes(PolygonImpl& sh) | |
341 | 324 | { |
342 | 325 | return sh.Holes; |
343 | 326 | } |
344 | 327 | |
345 | template<> inline TContour<PolygonImpl>& | |
346 | ShapeLike::getHole(PolygonImpl& sh, unsigned long idx) | |
328 | template<> | |
329 | inline TContour<PolygonImpl>& getHole(PolygonImpl& sh, unsigned long idx) | |
347 | 330 | { |
348 | 331 | return sh.Holes[idx]; |
349 | 332 | } |
350 | 333 | |
351 | template<> inline const TContour<PolygonImpl>& | |
352 | ShapeLike::getHole(const PolygonImpl& sh, unsigned long idx) | |
334 | template<> | |
335 | inline const TContour<PolygonImpl>& getHole(const PolygonImpl& sh, | |
336 | unsigned long idx) | |
353 | 337 | { |
354 | 338 | return sh.Holes[idx]; |
355 | 339 | } |
356 | 340 | |
357 | template<> inline size_t ShapeLike::holeCount(const PolygonImpl& sh) | |
341 | template<> inline size_t holeCount(const PolygonImpl& sh) | |
358 | 342 | { |
359 | 343 | return sh.Holes.size(); |
360 | 344 | } |
361 | 345 | |
362 | template<> inline PathImpl& ShapeLike::getContour(PolygonImpl& sh) | |
346 | template<> inline PathImpl& getContour(PolygonImpl& sh) | |
363 | 347 | { |
364 | 348 | return sh.Contour; |
365 | 349 | } |
366 | 350 | |
367 | 351 | template<> |
368 | inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh) | |
352 | inline const PathImpl& getContour(const PolygonImpl& sh) | |
369 | 353 | { |
370 | 354 | return sh.Contour; |
371 | 355 | } |
372 | 356 | |
373 | 357 | #define DISABLE_BOOST_TRANSLATE |
374 | 358 | template<> |
375 | inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) | |
359 | inline void translate(PolygonImpl& sh, const PointImpl& offs) | |
376 | 360 | { |
377 | 361 | for(auto& p : sh.Contour) { p += offs; } |
378 | 362 | for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; } |
380 | 364 | |
381 | 365 | #define DISABLE_BOOST_ROTATE |
382 | 366 | template<> |
383 | inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) | |
367 | inline void rotate(PolygonImpl& sh, const Radians& rads) | |
384 | 368 | { |
385 | 369 | using Coord = TCoord<PointImpl>; |
386 | 370 | |
401 | 385 | } |
402 | 386 | } |
403 | 387 | |
388 | } // namespace shapelike | |
389 | ||
404 | 390 | #define DISABLE_BOOST_NFP_MERGE |
405 | inline Nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) { | |
406 | Nfp::Shapes<PolygonImpl> retv; | |
391 | inline std::vector<PolygonImpl> _merge(ClipperLib::Clipper& clipper) { | |
392 | shapelike::Shapes<PolygonImpl> retv; | |
407 | 393 | |
408 | 394 | ClipperLib::PolyTree result; |
409 | 395 | clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); |
437 | 423 | return retv; |
438 | 424 | } |
439 | 425 | |
440 | template<> inline Nfp::Shapes<PolygonImpl> | |
441 | Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes) | |
426 | namespace nfp { | |
427 | ||
428 | template<> inline std::vector<PolygonImpl> | |
429 | merge(const std::vector<PolygonImpl>& shapes) | |
442 | 430 | { |
443 | 431 | ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); |
444 | 432 | |
460 | 448 | |
461 | 449 | } |
462 | 450 | |
451 | } | |
452 | ||
463 | 453 | //#define DISABLE_BOOST_SERIALIZE |
464 | 454 | //#define DISABLE_BOOST_UNSERIALIZE |
465 | 455 |
21 | 21 | using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type; |
22 | 22 | |
23 | 23 | /// Getting the type of point structure used by a shape. |
24 | template<class Shape> struct PointType { /*using Type = void;*/ }; | |
24 | template<class Sh> struct PointType { using Type = typename Sh::PointType; }; | |
25 | 25 | |
26 | 26 | /// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`. |
27 | 27 | template<class Shape> |
28 | 28 | using TPoint = typename PointType<remove_cvref_t<Shape>>::Type; |
29 | ||
30 | /// Getting the VertexIterator type of a shape class. | |
31 | template<class Shape> struct VertexIteratorType { /*using Type = void;*/ }; | |
32 | ||
33 | /// Getting the const vertex iterator for a shape class. | |
34 | template<class Shape> struct VertexConstIteratorType {/* using Type = void;*/ }; | |
35 | ||
36 | /** | |
37 | * TVertexIterator<Shape> as shorthand for | |
38 | * `typename VertexIteratorType<Shape>::Type` | |
39 | */ | |
40 | template<class Shape> | |
41 | using TVertexIterator = | |
42 | typename VertexIteratorType<remove_cvref_t<Shape>>::Type; | |
43 | ||
44 | /** | |
45 | * \brief TVertexConstIterator<Shape> as shorthand for | |
46 | * `typename VertexConstIteratorType<Shape>::Type` | |
47 | */ | |
48 | template<class ShapeClass> | |
49 | using TVertexConstIterator = | |
50 | typename VertexConstIteratorType<remove_cvref_t<ShapeClass>>::Type; | |
51 | 29 | |
52 | 30 | /** |
53 | 31 | * \brief A point pair base class for other point pairs (segment, box, ...). |
59 | 37 | RawPoint p2; |
60 | 38 | }; |
61 | 39 | |
40 | struct PolygonTag {}; | |
41 | struct MultiPolygonTag {}; | |
42 | struct BoxTag {}; | |
43 | struct CircleTag {}; | |
44 | ||
45 | template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; }; | |
46 | template<class S> using Tag = typename ShapeTag<S>::Type; | |
47 | ||
48 | template<class S> struct MultiShape { using Type = std::vector<S>; }; | |
49 | template<class S> using TMultiShape = typename MultiShape<S>::Type; | |
50 | ||
62 | 51 | /** |
63 | 52 | * \brief An abstraction of a box; |
64 | 53 | */ |
68 | 57 | using PointPair<RawPoint>::p2; |
69 | 58 | public: |
70 | 59 | |
60 | using Tag = BoxTag; | |
61 | using PointType = RawPoint; | |
62 | ||
71 | 63 | inline _Box() = default; |
72 | 64 | inline _Box(const RawPoint& p, const RawPoint& pp): |
73 | 65 | PointPair<RawPoint>({p, pp}) {} |
97 | 89 | double radius_ = 0; |
98 | 90 | public: |
99 | 91 | |
92 | using Tag = CircleTag; | |
93 | using PointType = RawPoint; | |
94 | ||
100 | 95 | _Circle() = default; |
101 | 96 | |
102 | 97 | _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} |
108 | 103 | inline void radius(double r) { radius_ = r; } |
109 | 104 | |
110 | 105 | inline double area() const BP2D_NOEXCEPT { |
111 | return 2.0*Pi*radius_; | |
106 | return 2.0*Pi*radius_*radius_; | |
112 | 107 | } |
113 | 108 | }; |
114 | 109 | |
122 | 117 | mutable Radians angletox_ = std::nan(""); |
123 | 118 | public: |
124 | 119 | |
120 | using PointType = RawPoint; | |
121 | ||
125 | 122 | inline _Segment() = default; |
126 | 123 | |
127 | 124 | inline _Segment(const RawPoint& p, const RawPoint& pp): |
155 | 152 | inline double length(); |
156 | 153 | }; |
157 | 154 | |
158 | // This struct serves as a namespace. The only difference is that is can be | |
155 | // This struct serves almost as a namespace. The only difference is that is can | |
159 | 156 | // used in friend declarations. |
160 | struct PointLike { | |
157 | namespace pointlike { | |
161 | 158 | |
162 | 159 | template<class RawPoint> |
163 | static TCoord<RawPoint> x(const RawPoint& p) | |
160 | inline TCoord<RawPoint> x(const RawPoint& p) | |
164 | 161 | { |
165 | 162 | return p.x(); |
166 | 163 | } |
167 | 164 | |
168 | 165 | template<class RawPoint> |
169 | static TCoord<RawPoint> y(const RawPoint& p) | |
166 | inline TCoord<RawPoint> y(const RawPoint& p) | |
170 | 167 | { |
171 | 168 | return p.y(); |
172 | 169 | } |
173 | 170 | |
174 | 171 | template<class RawPoint> |
175 | static TCoord<RawPoint>& x(RawPoint& p) | |
172 | inline TCoord<RawPoint>& x(RawPoint& p) | |
176 | 173 | { |
177 | 174 | return p.x(); |
178 | 175 | } |
179 | 176 | |
180 | 177 | template<class RawPoint> |
181 | static TCoord<RawPoint>& y(RawPoint& p) | |
178 | inline TCoord<RawPoint>& y(RawPoint& p) | |
182 | 179 | { |
183 | 180 | return p.y(); |
184 | 181 | } |
185 | 182 | |
186 | 183 | template<class RawPoint> |
187 | static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) | |
184 | inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) | |
188 | 185 | { |
189 | 186 | static_assert(always_false<RawPoint>::value, |
190 | 187 | "PointLike::distance(point, point) unimplemented!"); |
192 | 189 | } |
193 | 190 | |
194 | 191 | template<class RawPoint> |
195 | static double distance(const RawPoint& /*p1*/, | |
192 | inline double distance(const RawPoint& /*p1*/, | |
196 | 193 | const _Segment<RawPoint>& /*s*/) |
197 | 194 | { |
198 | 195 | static_assert(always_false<RawPoint>::value, |
201 | 198 | } |
202 | 199 | |
203 | 200 | template<class RawPoint> |
204 | static std::pair<TCoord<RawPoint>, bool> horizontalDistance( | |
201 | inline std::pair<TCoord<RawPoint>, bool> horizontalDistance( | |
205 | 202 | const RawPoint& p, const _Segment<RawPoint>& s) |
206 | 203 | { |
207 | 204 | using Unit = TCoord<RawPoint>; |
208 | auto x = PointLike::x(p), y = PointLike::y(p); | |
209 | auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first()); | |
210 | auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second()); | |
205 | auto x = pointlike::x(p), y = pointlike::y(p); | |
206 | auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); | |
207 | auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); | |
211 | 208 | |
212 | 209 | TCoord<RawPoint> ret; |
213 | 210 | |
227 | 224 | } |
228 | 225 | |
229 | 226 | template<class RawPoint> |
230 | static std::pair<TCoord<RawPoint>, bool> verticalDistance( | |
227 | inline std::pair<TCoord<RawPoint>, bool> verticalDistance( | |
231 | 228 | const RawPoint& p, const _Segment<RawPoint>& s) |
232 | 229 | { |
233 | 230 | using Unit = TCoord<RawPoint>; |
234 | auto x = PointLike::x(p), y = PointLike::y(p); | |
235 | auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first()); | |
236 | auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second()); | |
231 | auto x = pointlike::x(p), y = pointlike::y(p); | |
232 | auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); | |
233 | auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); | |
237 | 234 | |
238 | 235 | TCoord<RawPoint> ret; |
239 | 236 | |
251 | 248 | |
252 | 249 | return {ret, true}; |
253 | 250 | } |
254 | }; | |
251 | } | |
255 | 252 | |
256 | 253 | template<class RawPoint> |
257 | 254 | TCoord<RawPoint> _Box<RawPoint>::width() const BP2D_NOEXCEPT |
258 | 255 | { |
259 | return PointLike::x(maxCorner()) - PointLike::x(minCorner()); | |
256 | return pointlike::x(maxCorner()) - pointlike::x(minCorner()); | |
260 | 257 | } |
261 | 258 | |
262 | 259 | template<class RawPoint> |
263 | 260 | TCoord<RawPoint> _Box<RawPoint>::height() const BP2D_NOEXCEPT |
264 | 261 | { |
265 | return PointLike::y(maxCorner()) - PointLike::y(minCorner()); | |
266 | } | |
267 | ||
268 | template<class RawPoint> | |
269 | TCoord<RawPoint> getX(const RawPoint& p) { return PointLike::x<RawPoint>(p); } | |
270 | ||
271 | template<class RawPoint> | |
272 | TCoord<RawPoint> getY(const RawPoint& p) { return PointLike::y<RawPoint>(p); } | |
262 | return pointlike::y(maxCorner()) - pointlike::y(minCorner()); | |
263 | } | |
264 | ||
265 | template<class RawPoint> | |
266 | TCoord<RawPoint> getX(const RawPoint& p) { return pointlike::x<RawPoint>(p); } | |
267 | ||
268 | template<class RawPoint> | |
269 | TCoord<RawPoint> getY(const RawPoint& p) { return pointlike::y<RawPoint>(p); } | |
273 | 270 | |
274 | 271 | template<class RawPoint> |
275 | 272 | void setX(RawPoint& p, const TCoord<RawPoint>& val) |
276 | 273 | { |
277 | PointLike::x<RawPoint>(p) = val; | |
274 | pointlike::x<RawPoint>(p) = val; | |
278 | 275 | } |
279 | 276 | |
280 | 277 | template<class RawPoint> |
281 | 278 | void setY(RawPoint& p, const TCoord<RawPoint>& val) |
282 | 279 | { |
283 | PointLike::y<RawPoint>(p) = val; | |
280 | pointlike::y<RawPoint>(p) = val; | |
284 | 281 | } |
285 | 282 | |
286 | 283 | template<class RawPoint> |
302 | 299 | template<class RawPoint> |
303 | 300 | inline double _Segment<RawPoint>::length() |
304 | 301 | { |
305 | return PointLike::distance(first(), second()); | |
302 | return pointlike::distance(first(), second()); | |
306 | 303 | } |
307 | 304 | |
308 | 305 | template<class RawPoint> |
312 | 309 | |
313 | 310 | using Coord = TCoord<RawPoint>; |
314 | 311 | |
315 | RawPoint ret = { | |
316 | static_cast<Coord>( std::round((getX(minc) + getX(maxc))/2.0) ), | |
317 | static_cast<Coord>( std::round((getY(minc) + getY(maxc))/2.0) ) | |
312 | RawPoint ret = { // No rounding here, we dont know if these are int coords | |
313 | static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ), | |
314 | static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 ) | |
318 | 315 | }; |
319 | 316 | |
320 | 317 | return ret; |
355 | 352 | |
356 | 353 | // This struct serves as a namespace. The only difference is that it can be |
357 | 354 | // used in friend declarations and can be aliased at class scope. |
358 | struct ShapeLike { | |
359 | ||
360 | template<class RawShape> | |
361 | using Shapes = std::vector<RawShape>; | |
362 | ||
363 | template<class RawShape> | |
364 | static RawShape create(const TContour<RawShape>& contour, | |
355 | namespace shapelike { | |
356 | ||
357 | template<class RawShape> | |
358 | using Shapes = TMultiShape<RawShape>; | |
359 | ||
360 | template<class RawShape> | |
361 | inline RawShape create(const TContour<RawShape>& contour, | |
365 | 362 | const THolesContainer<RawShape>& holes) |
366 | 363 | { |
367 | 364 | return RawShape(contour, holes); |
368 | 365 | } |
369 | 366 | |
370 | 367 | template<class RawShape> |
371 | static RawShape create(TContour<RawShape>&& contour, | |
368 | inline RawShape create(TContour<RawShape>&& contour, | |
372 | 369 | THolesContainer<RawShape>&& holes) |
373 | 370 | { |
374 | 371 | return RawShape(contour, holes); |
375 | 372 | } |
376 | 373 | |
377 | 374 | template<class RawShape> |
378 | static RawShape create(const TContour<RawShape>& contour) | |
375 | inline RawShape create(const TContour<RawShape>& contour) | |
379 | 376 | { |
380 | 377 | return create<RawShape>(contour, {}); |
381 | 378 | } |
382 | 379 | |
383 | 380 | template<class RawShape> |
384 | static RawShape create(TContour<RawShape>&& contour) | |
381 | inline RawShape create(TContour<RawShape>&& contour) | |
385 | 382 | { |
386 | 383 | return create<RawShape>(contour, {}); |
387 | 384 | } |
388 | 385 | |
389 | 386 | template<class RawShape> |
390 | static THolesContainer<RawShape>& holes(RawShape& /*sh*/) | |
387 | inline THolesContainer<RawShape>& holes(RawShape& /*sh*/) | |
391 | 388 | { |
392 | 389 | static THolesContainer<RawShape> empty; |
393 | 390 | return empty; |
394 | 391 | } |
395 | 392 | |
396 | 393 | template<class RawShape> |
397 | static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/) | |
394 | inline const THolesContainer<RawShape>& holes(const RawShape& /*sh*/) | |
398 | 395 | { |
399 | 396 | static THolesContainer<RawShape> empty; |
400 | 397 | return empty; |
401 | 398 | } |
402 | 399 | |
403 | 400 | template<class RawShape> |
404 | static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx) | |
401 | inline TContour<RawShape>& getHole(RawShape& sh, unsigned long idx) | |
405 | 402 | { |
406 | 403 | return holes(sh)[idx]; |
407 | 404 | } |
408 | 405 | |
409 | 406 | template<class RawShape> |
410 | static const TContour<RawShape>& getHole(const RawShape& sh, | |
407 | inline const TContour<RawShape>& getHole(const RawShape& sh, | |
411 | 408 | unsigned long idx) |
412 | 409 | { |
413 | 410 | return holes(sh)[idx]; |
414 | 411 | } |
415 | 412 | |
416 | 413 | template<class RawShape> |
417 | static size_t holeCount(const RawShape& sh) | |
414 | inline size_t holeCount(const RawShape& sh) | |
418 | 415 | { |
419 | 416 | return holes(sh).size(); |
420 | 417 | } |
421 | 418 | |
422 | 419 | template<class RawShape> |
423 | static TContour<RawShape>& getContour(RawShape& sh) | |
420 | inline TContour<RawShape>& getContour(RawShape& sh) | |
424 | 421 | { |
425 | 422 | return sh; |
426 | 423 | } |
427 | 424 | |
428 | 425 | template<class RawShape> |
429 | static const TContour<RawShape>& getContour(const RawShape& sh) | |
426 | inline const TContour<RawShape>& getContour(const RawShape& sh) | |
430 | 427 | { |
431 | 428 | return sh; |
432 | 429 | } |
433 | 430 | |
434 | 431 | // Optional, does nothing by default |
435 | 432 | template<class RawShape> |
436 | static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} | |
433 | inline void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} | |
437 | 434 | |
438 | 435 | template<class RawShape, class...Args> |
439 | static void addVertex(RawShape& sh, Args...args) | |
436 | inline void addVertex(RawShape& sh, Args...args) | |
440 | 437 | { |
441 | 438 | return getContour(sh).emplace_back(std::forward<Args>(args)...); |
442 | 439 | } |
443 | 440 | |
444 | 441 | template<class RawShape> |
445 | static TVertexIterator<RawShape> begin(RawShape& sh) | |
446 | { | |
447 | return sh.begin(); | |
448 | } | |
449 | ||
450 | template<class RawShape> | |
451 | static TVertexIterator<RawShape> end(RawShape& sh) | |
452 | { | |
453 | return sh.end(); | |
454 | } | |
455 | ||
456 | template<class RawShape> | |
457 | static TVertexConstIterator<RawShape> cbegin(const RawShape& sh) | |
458 | { | |
459 | return sh.cbegin(); | |
460 | } | |
461 | ||
462 | template<class RawShape> | |
463 | static TVertexConstIterator<RawShape> cend(const RawShape& sh) | |
464 | { | |
465 | return sh.cend(); | |
466 | } | |
467 | ||
468 | template<class RawShape> | |
469 | static std::string toString(const RawShape& /*sh*/) | |
442 | inline typename TContour<RawShape>::iterator begin(RawShape& sh) | |
443 | { | |
444 | return getContour(sh).begin(); | |
445 | } | |
446 | ||
447 | template<class RawShape> | |
448 | inline typename TContour<RawShape>::iterator end(RawShape& sh) | |
449 | { | |
450 | return getContour(sh).end(); | |
451 | } | |
452 | ||
453 | template<class RawShape> | |
454 | inline typename TContour<RawShape>::const_iterator | |
455 | cbegin(const RawShape& sh) | |
456 | { | |
457 | return getContour(sh).cbegin(); | |
458 | } | |
459 | ||
460 | template<class RawShape> | |
461 | inline typename TContour<RawShape>::const_iterator cend(const RawShape& sh) | |
462 | { | |
463 | return getContour(sh).cend(); | |
464 | } | |
465 | ||
466 | template<class RawShape> | |
467 | inline std::string toString(const RawShape& /*sh*/) | |
470 | 468 | { |
471 | 469 | return ""; |
472 | 470 | } |
473 | 471 | |
474 | 472 | template<Formats, class RawShape> |
475 | static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) | |
473 | inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) | |
476 | 474 | { |
477 | 475 | static_assert(always_false<RawShape>::value, |
478 | 476 | "ShapeLike::serialize() unimplemented!"); |
480 | 478 | } |
481 | 479 | |
482 | 480 | template<Formats, class RawShape> |
483 | static void unserialize(RawShape& /*sh*/, const std::string& /*str*/) | |
481 | inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) | |
484 | 482 | { |
485 | 483 | static_assert(always_false<RawShape>::value, |
486 | 484 | "ShapeLike::unserialize() unimplemented!"); |
487 | 485 | } |
488 | 486 | |
489 | 487 | template<class RawShape> |
490 | static double area(const RawShape& /*sh*/) | |
488 | inline double area(const RawShape& /*sh*/, const PolygonTag&) | |
491 | 489 | { |
492 | 490 | static_assert(always_false<RawShape>::value, |
493 | 491 | "ShapeLike::area() unimplemented!"); |
495 | 493 | } |
496 | 494 | |
497 | 495 | template<class RawShape> |
498 | static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) | |
496 | inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) | |
499 | 497 | { |
500 | 498 | static_assert(always_false<RawShape>::value, |
501 | 499 | "ShapeLike::intersects() unimplemented!"); |
503 | 501 | } |
504 | 502 | |
505 | 503 | template<class RawShape> |
506 | static bool isInside(const TPoint<RawShape>& /*point*/, | |
504 | inline bool isInside(const TPoint<RawShape>& /*point*/, | |
507 | 505 | const RawShape& /*shape*/) |
508 | 506 | { |
509 | 507 | static_assert(always_false<RawShape>::value, |
512 | 510 | } |
513 | 511 | |
514 | 512 | template<class RawShape> |
515 | static bool isInside(const RawShape& /*shape*/, | |
513 | inline bool isInside(const RawShape& /*shape*/, | |
516 | 514 | const RawShape& /*shape*/) |
517 | 515 | { |
518 | 516 | static_assert(always_false<RawShape>::value, |
521 | 519 | } |
522 | 520 | |
523 | 521 | template<class RawShape> |
524 | static bool touches( const RawShape& /*shape*/, | |
522 | inline bool touches( const RawShape& /*shape*/, | |
525 | 523 | const RawShape& /*shape*/) |
526 | 524 | { |
527 | 525 | static_assert(always_false<RawShape>::value, |
530 | 528 | } |
531 | 529 | |
532 | 530 | template<class RawShape> |
533 | static bool touches( const TPoint<RawShape>& /*point*/, | |
531 | inline bool touches( const TPoint<RawShape>& /*point*/, | |
534 | 532 | const RawShape& /*shape*/) |
535 | 533 | { |
536 | 534 | static_assert(always_false<RawShape>::value, |
539 | 537 | } |
540 | 538 | |
541 | 539 | template<class RawShape> |
542 | static _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/) | |
540 | inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/, | |
541 | const PolygonTag&) | |
543 | 542 | { |
544 | 543 | static_assert(always_false<RawShape>::value, |
545 | 544 | "ShapeLike::boundingBox(shape) unimplemented!"); |
546 | 545 | } |
547 | 546 | |
548 | template<class RawShape> | |
549 | static _Box<TPoint<RawShape>> boundingBox(const Shapes<RawShape>& /*sh*/) | |
550 | { | |
551 | static_assert(always_false<RawShape>::value, | |
547 | template<class RawShapes> | |
548 | inline _Box<TPoint<typename RawShapes::value_type>> | |
549 | boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) | |
550 | { | |
551 | static_assert(always_false<RawShapes>::value, | |
552 | 552 | "ShapeLike::boundingBox(shapes) unimplemented!"); |
553 | 553 | } |
554 | 554 | |
555 | 555 | template<class RawShape> |
556 | static RawShape convexHull(const RawShape& /*sh*/) | |
556 | inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) | |
557 | 557 | { |
558 | 558 | static_assert(always_false<RawShape>::value, |
559 | 559 | "ShapeLike::convexHull(shape) unimplemented!"); |
560 | 560 | return RawShape(); |
561 | 561 | } |
562 | 562 | |
563 | template<class RawShape> | |
564 | static RawShape convexHull(const Shapes<RawShape>& /*sh*/) | |
565 | { | |
566 | static_assert(always_false<RawShape>::value, | |
563 | template<class RawShapes> | |
564 | inline typename RawShapes::value_type | |
565 | convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) | |
566 | { | |
567 | static_assert(always_false<RawShapes>::value, | |
567 | 568 | "ShapeLike::convexHull(shapes) unimplemented!"); |
568 | return RawShape(); | |
569 | } | |
570 | ||
571 | template<class RawShape> | |
572 | static void rotate(RawShape& /*sh*/, const Radians& /*rads*/) | |
569 | return typename RawShapes::value_type(); | |
570 | } | |
571 | ||
572 | template<class RawShape> | |
573 | inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) | |
573 | 574 | { |
574 | 575 | static_assert(always_false<RawShape>::value, |
575 | 576 | "ShapeLike::rotate() unimplemented!"); |
576 | 577 | } |
577 | 578 | |
578 | 579 | template<class RawShape, class RawPoint> |
579 | static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) | |
580 | inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) | |
580 | 581 | { |
581 | 582 | static_assert(always_false<RawShape>::value, |
582 | 583 | "ShapeLike::translate() unimplemented!"); |
583 | 584 | } |
584 | 585 | |
585 | 586 | template<class RawShape> |
586 | static void offset(RawShape& /*sh*/, TCoord<TPoint<RawShape>> /*distance*/) | |
587 | { | |
588 | static_assert(always_false<RawShape>::value, | |
589 | "ShapeLike::offset() unimplemented!"); | |
590 | } | |
591 | ||
592 | template<class RawShape> | |
593 | static std::pair<bool, std::string> isValid(const RawShape& /*sh*/) | |
587 | inline void offset(RawShape& /*sh*/, TCoord<TPoint<RawShape>> /*distance*/) | |
588 | { | |
589 | dout() << "The current geometry backend does not support offsetting!\n"; | |
590 | } | |
591 | ||
592 | template<class RawShape> | |
593 | inline std::pair<bool, std::string> isValid(const RawShape& /*sh*/) | |
594 | 594 | { |
595 | 595 | return {false, "ShapeLike::isValid() unimplemented!"}; |
596 | 596 | } |
597 | 597 | |
598 | 598 | template<class RawShape> |
599 | static inline bool isConvex(const TContour<RawShape>& sh) | |
599 | inline bool isConvex(const TContour<RawShape>& sh) | |
600 | 600 | { |
601 | 601 | using Vertex = TPoint<RawShape>; |
602 | 602 | auto first = sh.begin(); |
632 | 632 | // No need to implement these |
633 | 633 | // ************************************************************************* |
634 | 634 | |
635 | template<class RawShape> | |
636 | static inline _Box<TPoint<RawShape>> boundingBox( | |
637 | const _Box<TPoint<RawShape>>& box) | |
635 | template<class Box> | |
636 | inline Box boundingBox(const Box& box, const BoxTag& ) | |
638 | 637 | { |
639 | 638 | return box; |
640 | 639 | } |
641 | 640 | |
642 | template<class RawShape> | |
643 | static inline _Box<TPoint<RawShape>> boundingBox( | |
644 | const _Circle<TPoint<RawShape>>& circ) | |
645 | { | |
646 | using Coord = TCoord<TPoint<RawShape>>; | |
647 | TPoint<RawShape> pmin = { | |
641 | template<class Circle> | |
642 | inline _Box<typename Circle::PointType> boundingBox( | |
643 | const Circle& circ, const CircleTag&) | |
644 | { | |
645 | using Point = typename Circle::PointType; | |
646 | using Coord = TCoord<Point>; | |
647 | Point pmin = { | |
648 | 648 | static_cast<Coord>(getX(circ.center()) - circ.radius()), |
649 | 649 | static_cast<Coord>(getY(circ.center()) - circ.radius()) }; |
650 | 650 | |
651 | TPoint<RawShape> pmax = { | |
651 | Point pmax = { | |
652 | 652 | static_cast<Coord>(getX(circ.center()) + circ.radius()), |
653 | 653 | static_cast<Coord>(getY(circ.center()) + circ.radius()) }; |
654 | 654 | |
655 | 655 | return {pmin, pmax}; |
656 | 656 | } |
657 | 657 | |
658 | template<class RawShape> | |
659 | static inline double area(const _Box<TPoint<RawShape>>& box) | |
660 | { | |
661 | return static_cast<double>(box.width() * box.height()); | |
662 | } | |
663 | ||
664 | template<class RawShape> | |
665 | static inline double area(const _Circle<TPoint<RawShape>>& circ) | |
658 | template<class S> // Dispatch function | |
659 | inline _Box<TPoint<S>> boundingBox(const S& sh) | |
660 | { | |
661 | return boundingBox(sh, Tag<S>() ); | |
662 | } | |
663 | ||
664 | template<class Box> | |
665 | inline double area(const Box& box, const BoxTag& ) | |
666 | { | |
667 | return box.area(); | |
668 | } | |
669 | ||
670 | template<class Circle> | |
671 | inline double area(const Circle& circ, const CircleTag& ) | |
666 | 672 | { |
667 | 673 | return circ.area(); |
668 | 674 | } |
669 | 675 | |
670 | template<class RawShape> | |
671 | static inline double area(const Shapes<RawShape>& shapes) | |
676 | template<class RawShape> // Dispatching function | |
677 | inline double area(const RawShape& sh) | |
678 | { | |
679 | return area(sh, Tag<RawShape>()); | |
680 | } | |
681 | ||
682 | template<class RawShape> | |
683 | inline double area(const Shapes<RawShape>& shapes) | |
672 | 684 | { |
673 | 685 | return std::accumulate(shapes.begin(), shapes.end(), 0.0, |
674 | 686 | [](double a, const RawShape& b) { |
677 | 689 | } |
678 | 690 | |
679 | 691 | template<class RawShape> |
680 | static bool isInside(const TPoint<RawShape>& point, | |
692 | inline auto convexHull(const RawShape& sh) | |
693 | -> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce | |
694 | { | |
695 | return convexHull(sh, Tag<RawShape>()); | |
696 | } | |
697 | ||
698 | template<class RawShape> | |
699 | inline bool isInside(const TPoint<RawShape>& point, | |
681 | 700 | const _Circle<TPoint<RawShape>>& circ) |
682 | 701 | { |
683 | return PointLike::distance(point, circ.center()) < circ.radius(); | |
684 | } | |
685 | ||
686 | template<class RawShape> | |
687 | static bool isInside(const TPoint<RawShape>& point, | |
702 | return pointlike::distance(point, circ.center()) < circ.radius(); | |
703 | } | |
704 | ||
705 | template<class RawShape> | |
706 | inline bool isInside(const TPoint<RawShape>& point, | |
688 | 707 | const _Box<TPoint<RawShape>>& box) |
689 | 708 | { |
690 | 709 | auto px = getX(point); |
698 | 717 | } |
699 | 718 | |
700 | 719 | template<class RawShape> |
701 | static bool isInside(const RawShape& sh, | |
720 | inline bool isInside(const RawShape& sh, | |
702 | 721 | const _Circle<TPoint<RawShape>>& circ) |
703 | 722 | { |
704 | 723 | return std::all_of(cbegin(sh), cend(sh), |
708 | 727 | } |
709 | 728 | |
710 | 729 | template<class RawShape> |
711 | static bool isInside(const _Box<TPoint<RawShape>>& box, | |
730 | inline bool isInside(const _Box<TPoint<RawShape>>& box, | |
712 | 731 | const _Circle<TPoint<RawShape>>& circ) |
713 | 732 | { |
714 | 733 | return isInside<RawShape>(box.minCorner(), circ) && |
716 | 735 | } |
717 | 736 | |
718 | 737 | template<class RawShape> |
719 | static bool isInside(const _Box<TPoint<RawShape>>& ibb, | |
738 | inline bool isInside(const _Box<TPoint<RawShape>>& ibb, | |
720 | 739 | const _Box<TPoint<RawShape>>& box) |
721 | 740 | { |
722 | 741 | auto iminX = getX(ibb.minCorner()); |
733 | 752 | } |
734 | 753 | |
735 | 754 | template<class RawShape> // Potential O(1) implementation may exist |
736 | static inline TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx) | |
755 | inline TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx) | |
737 | 756 | { |
738 | 757 | return *(begin(sh) + idx); |
739 | 758 | } |
740 | 759 | |
741 | 760 | template<class RawShape> // Potential O(1) implementation may exist |
742 | static inline const TPoint<RawShape>& vertex(const RawShape& sh, | |
761 | inline const TPoint<RawShape>& vertex(const RawShape& sh, | |
743 | 762 | unsigned long idx) |
744 | 763 | { |
745 | 764 | return *(cbegin(sh) + idx); |
746 | 765 | } |
747 | 766 | |
748 | 767 | template<class RawShape> |
749 | static inline size_t contourVertexCount(const RawShape& sh) | |
768 | inline size_t contourVertexCount(const RawShape& sh) | |
750 | 769 | { |
751 | 770 | return cend(sh) - cbegin(sh); |
752 | 771 | } |
753 | 772 | |
754 | 773 | template<class RawShape, class Fn> |
755 | static inline void foreachContourVertex(RawShape& sh, Fn fn) { | |
774 | inline void foreachContourVertex(RawShape& sh, Fn fn) { | |
756 | 775 | for(auto it = begin(sh); it != end(sh); ++it) fn(*it); |
757 | 776 | } |
758 | 777 | |
759 | 778 | template<class RawShape, class Fn> |
760 | static inline void foreachHoleVertex(RawShape& sh, Fn fn) { | |
779 | inline void foreachHoleVertex(RawShape& sh, Fn fn) { | |
761 | 780 | for(int i = 0; i < holeCount(sh); ++i) { |
762 | 781 | auto& h = getHole(sh, i); |
763 | 782 | for(auto it = begin(h); it != end(h); ++it) fn(*it); |
765 | 784 | } |
766 | 785 | |
767 | 786 | template<class RawShape, class Fn> |
768 | static inline void foreachContourVertex(const RawShape& sh, Fn fn) { | |
787 | inline void foreachContourVertex(const RawShape& sh, Fn fn) { | |
769 | 788 | for(auto it = cbegin(sh); it != cend(sh); ++it) fn(*it); |
770 | 789 | } |
771 | 790 | |
772 | 791 | template<class RawShape, class Fn> |
773 | static inline void foreachHoleVertex(const RawShape& sh, Fn fn) { | |
792 | inline void foreachHoleVertex(const RawShape& sh, Fn fn) { | |
774 | 793 | for(int i = 0; i < holeCount(sh); ++i) { |
775 | 794 | auto& h = getHole(sh, i); |
776 | 795 | for(auto it = cbegin(h); it != cend(h); ++it) fn(*it); |
778 | 797 | } |
779 | 798 | |
780 | 799 | template<class RawShape, class Fn> |
781 | static inline void foreachVertex(RawShape& sh, Fn fn) { | |
800 | inline void foreachVertex(RawShape& sh, Fn fn) { | |
782 | 801 | foreachContourVertex(sh, fn); |
783 | 802 | foreachHoleVertex(sh, fn); |
784 | 803 | } |
785 | 804 | |
786 | 805 | template<class RawShape, class Fn> |
787 | static inline void foreachVertex(const RawShape& sh, Fn fn) { | |
806 | inline void foreachVertex(const RawShape& sh, Fn fn) { | |
788 | 807 | foreachContourVertex(sh, fn); |
789 | 808 | foreachHoleVertex(sh, fn); |
790 | 809 | } |
791 | ||
792 | }; | |
810 | } | |
811 | ||
812 | #define DECLARE_MAIN_TYPES(T) \ | |
813 | using Polygon = T; \ | |
814 | using Point = TPoint<T>; \ | |
815 | using Coord = TCoord<Point>; \ | |
816 | using Contour = TContour<T>; \ | |
817 | using Box = _Box<Point>; \ | |
818 | using Circle = _Circle<Point>; \ | |
819 | using Segment = _Segment<Point>; \ | |
820 | using Polygons = TMultiShape<T> | |
793 | 821 | |
794 | 822 | } |
795 | 823 |
7 | 7 | #include <iterator> |
8 | 8 | |
9 | 9 | namespace libnest2d { |
10 | ||
11 | namespace __nfp { | |
12 | // Do not specialize this... | |
13 | template<class RawShape> | |
14 | inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2) | |
15 | { | |
16 | using Coord = TCoord<TPoint<RawShape>>; | |
17 | Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); | |
18 | auto diff = y1 - y2; | |
19 | if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon()) | |
20 | return x1 < x2; | |
21 | ||
22 | return diff < 0; | |
23 | } | |
24 | } | |
25 | ||
26 | /// A collection of static methods for handling the no fit polygon creation. | |
27 | namespace nfp { | |
28 | ||
29 | //namespace sl = shapelike; | |
30 | //namespace pl = pointlike; | |
10 | 31 | |
11 | 32 | /// The complexity level of a polygon that an NFP implementation can handle. |
12 | 33 | enum class NfpLevel: unsigned { |
17 | 38 | BOTH_CONCAVE_WITH_HOLES |
18 | 39 | }; |
19 | 40 | |
20 | /// A collection of static methods for handling the no fit polygon creation. | |
21 | struct Nfp { | |
41 | template<class RawShape> | |
42 | using NfpResult = std::pair<RawShape, TPoint<RawShape>>; | |
43 | ||
44 | template<class RawShape> struct MaxNfpLevel { | |
45 | static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; | |
46 | }; | |
47 | ||
22 | 48 | |
23 | 49 | // Shorthand for a pile of polygons |
24 | 50 | template<class RawShape> |
25 | using Shapes = typename ShapeLike::Shapes<RawShape>; | |
51 | using Shapes = TMultiShape<RawShape>; | |
26 | 52 | |
27 | 53 | /** |
28 | 54 | * Merge a bunch of polygons with the specified additional polygon. |
35 | 61 | * mostly it will be a set containing only one big polygon but if the input |
36 | 62 | * polygons are disjuct than the resulting set will contain more polygons. |
37 | 63 | */ |
38 | template<class RawShape> | |
39 | static Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/) | |
40 | { | |
41 | static_assert(always_false<RawShape>::value, | |
64 | template<class RawShapes> | |
65 | inline RawShapes merge(const RawShapes& /*shc*/) | |
66 | { | |
67 | static_assert(always_false<RawShapes>::value, | |
42 | 68 | "Nfp::merge(shapes, shape) unimplemented!"); |
43 | 69 | } |
44 | 70 | |
54 | 80 | * polygons are disjuct than the resulting set will contain more polygons. |
55 | 81 | */ |
56 | 82 | template<class RawShape> |
57 | static Shapes<RawShape> merge(const Shapes<RawShape>& shc, | |
58 | const RawShape& sh) | |
59 | { | |
60 | auto m = merge(shc); | |
83 | inline TMultiShape<RawShape> merge(const TMultiShape<RawShape>& shc, | |
84 | const RawShape& sh) | |
85 | { | |
86 | auto m = nfp::merge(shc); | |
61 | 87 | m.push_back(sh); |
62 | return merge(m); | |
63 | } | |
64 | ||
65 | /** | |
66 | * A method to get a vertex from a polygon that always maintains a relative | |
67 | * position to the coordinate system: It is always the rightmost top vertex. | |
68 | * | |
69 | * This way it does not matter in what order the vertices are stored, the | |
70 | * reference will be always the same for the same polygon. | |
71 | */ | |
72 | template<class RawShape> | |
73 | inline static TPoint<RawShape> referenceVertex(const RawShape& sh) | |
74 | { | |
75 | return rightmostUpVertex(sh); | |
88 | return nfp::merge(m); | |
76 | 89 | } |
77 | 90 | |
78 | 91 | /** |
81 | 94 | * the result will be the leftmost (with the highest X coordinate). |
82 | 95 | */ |
83 | 96 | template<class RawShape> |
84 | static TPoint<RawShape> leftmostDownVertex(const RawShape& sh) | |
97 | inline TPoint<RawShape> leftmostDownVertex(const RawShape& sh) | |
85 | 98 | { |
86 | 99 | |
87 | 100 | // find min x and min y vertex |
88 | auto it = std::min_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), | |
89 | _vsort<RawShape>); | |
90 | ||
91 | return *it; | |
101 | auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh), | |
102 | __nfp::_vsort<RawShape>); | |
103 | ||
104 | return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;; | |
92 | 105 | } |
93 | 106 | |
94 | 107 | /** |
97 | 110 | * the result will be the rightmost (with the lowest X coordinate). |
98 | 111 | */ |
99 | 112 | template<class RawShape> |
100 | static TPoint<RawShape> rightmostUpVertex(const RawShape& sh) | |
113 | TPoint<RawShape> rightmostUpVertex(const RawShape& sh) | |
101 | 114 | { |
102 | 115 | |
103 | 116 | // find max x and max y vertex |
104 | auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), | |
105 | _vsort<RawShape>); | |
106 | ||
107 | return *it; | |
108 | } | |
109 | ||
110 | template<class RawShape> | |
111 | using NfpResult = std::pair<RawShape, TPoint<RawShape>>; | |
112 | ||
113 | /// Helper function to get the NFP | |
114 | template<NfpLevel nfptype, class RawShape> | |
115 | static NfpResult<RawShape> noFitPolygon(const RawShape& sh, | |
116 | const RawShape& other) | |
117 | { | |
118 | NfpImpl<RawShape, nfptype> nfp; | |
119 | return nfp(sh, other); | |
117 | auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh), | |
118 | __nfp::_vsort<RawShape>); | |
119 | ||
120 | return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it; | |
121 | } | |
122 | ||
123 | /** | |
124 | * A method to get a vertex from a polygon that always maintains a relative | |
125 | * position to the coordinate system: It is always the rightmost top vertex. | |
126 | * | |
127 | * This way it does not matter in what order the vertices are stored, the | |
128 | * reference will be always the same for the same polygon. | |
129 | */ | |
130 | template<class RawShape> | |
131 | inline TPoint<RawShape> referenceVertex(const RawShape& sh) | |
132 | { | |
133 | return rightmostUpVertex(sh); | |
120 | 134 | } |
121 | 135 | |
122 | 136 | /** |
138 | 152 | * |
139 | 153 | */ |
140 | 154 | template<class RawShape> |
141 | static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh, | |
155 | inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh, | |
142 | 156 | const RawShape& other) |
143 | 157 | { |
144 | 158 | using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>; |
145 | using sl = ShapeLike; | |
159 | namespace sl = shapelike; | |
146 | 160 | |
147 | 161 | RawShape rsh; // Final nfp placeholder |
148 | 162 | Vertex top_nfp; |
186 | 200 | sl::addVertex(rsh, edgelist.front().second()); |
187 | 201 | |
188 | 202 | // Sorting function for the nfp reference vertex search |
189 | auto& cmp = _vsort<RawShape>; | |
203 | auto& cmp = __nfp::_vsort<RawShape>; | |
190 | 204 | |
191 | 205 | // the reference (rightmost top) vertex so far |
192 | 206 | top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp ); |
213 | 227 | } |
214 | 228 | |
215 | 229 | template<class RawShape> |
216 | static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary, | |
230 | NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary, | |
217 | 231 | const RawShape& cother) |
218 | 232 | { |
219 | 233 | |
232 | 246 | using Vertex = TPoint<RawShape>; |
233 | 247 | using Coord = TCoord<Vertex>; |
234 | 248 | using Edge = _Segment<Vertex>; |
235 | using sl = ShapeLike; | |
249 | namespace sl = shapelike; | |
236 | 250 | using std::signbit; |
237 | 251 | using std::sort; |
238 | 252 | using std::vector; |
527 | 541 | } |
528 | 542 | }; |
529 | 543 | |
530 | template<class RawShape> struct MaxNfpLevel { | |
531 | static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; | |
532 | }; | |
533 | ||
534 | private: | |
535 | ||
536 | // Do not specialize this... | |
537 | template<class RawShape> | |
538 | static inline bool _vsort(const TPoint<RawShape>& v1, | |
539 | const TPoint<RawShape>& v2) | |
540 | { | |
541 | using Coord = TCoord<TPoint<RawShape>>; | |
542 | Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); | |
543 | auto diff = y1 - y2; | |
544 | if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon()) | |
545 | return x1 < x2; | |
546 | ||
547 | return diff < 0; | |
548 | } | |
549 | ||
550 | }; | |
544 | /// Helper function to get the NFP | |
545 | template<NfpLevel nfptype, class RawShape> | |
546 | inline NfpResult<RawShape> noFitPolygon(const RawShape& sh, | |
547 | const RawShape& other) | |
548 | { | |
549 | NfpImpl<RawShape, nfptype> nfps; | |
550 | return nfps(sh, other); | |
551 | } | |
552 | ||
553 | } | |
551 | 554 | |
552 | 555 | } |
553 | 556 |
8 | 8 | #include <functional> |
9 | 9 | |
10 | 10 | #include "geometry_traits.hpp" |
11 | #include "optimizer.hpp" | |
12 | 11 | |
13 | 12 | namespace libnest2d { |
13 | ||
14 | namespace sl = shapelike; | |
15 | namespace pl = pointlike; | |
14 | 16 | |
15 | 17 | /** |
16 | 18 | * \brief An item to be placed on a bin. |
27 | 29 | using Coord = TCoord<TPoint<RawShape>>; |
28 | 30 | using Vertex = TPoint<RawShape>; |
29 | 31 | using Box = _Box<Vertex>; |
30 | using sl = ShapeLike; | |
32 | ||
33 | using VertexConstIterator = typename TContour<RawShape>::const_iterator; | |
31 | 34 | |
32 | 35 | // The original shape that gets encapsulated. |
33 | 36 | RawShape sh_; |
37 | 40 | Radians rotation_; |
38 | 41 | Coord offset_distance_; |
39 | 42 | |
40 | // Info about whether the tranformations will have to take place | |
43 | // Info about whether the transformations will have to take place | |
41 | 44 | // This is needed because if floating point is used, it is hard to say |
42 | 45 | // that a zero angle is not a rotation because of testing for equality. |
43 | 46 | bool has_rotation_ = false, has_translation_ = false, has_offset_ = false; |
57 | 60 | }; |
58 | 61 | |
59 | 62 | mutable Convexity convexity_ = Convexity::UNCHECKED; |
60 | mutable TVertexConstIterator<RawShape> rmt_; // rightmost top vertex | |
61 | mutable TVertexConstIterator<RawShape> lmb_; // leftmost bottom vertex | |
63 | mutable VertexConstIterator rmt_; // rightmost top vertex | |
64 | mutable VertexConstIterator lmb_; // leftmost bottom vertex | |
62 | 65 | mutable bool rmt_valid_ = false, lmb_valid_ = false; |
63 | 66 | mutable struct BBCache { |
64 | Box bb; bool valid; Vertex tr; | |
65 | BBCache(): valid(false), tr(0, 0) {} | |
67 | Box bb; bool valid; | |
68 | BBCache(): valid(false) {} | |
66 | 69 | } bb_cache_; |
67 | 70 | |
68 | 71 | public: |
79 | 82 | * supports. Giving out a non const iterator would make it impossible to |
80 | 83 | * perform correct cache invalidation. |
81 | 84 | */ |
82 | using Iterator = TVertexConstIterator<RawShape>; | |
85 | using Iterator = VertexConstIterator; | |
83 | 86 | |
84 | 87 | /** |
85 | 88 | * @brief Get the orientation of the polygon. |
108 | 111 | explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {} |
109 | 112 | |
110 | 113 | /** |
111 | * @brief Create an item from an initilizer list. | |
114 | * @brief Create an item from an initializer list. | |
112 | 115 | * @param il The initializer list of vertices. |
113 | 116 | */ |
114 | 117 | inline _Item(const std::initializer_list< Vertex >& il): |
158 | 161 | } |
159 | 162 | |
160 | 163 | /** |
161 | * @brief Get a copy of an outer vertex whithin the carried shape. | |
164 | * @brief Get a copy of an outer vertex within the carried shape. | |
162 | 165 | * |
163 | 166 | * Note that the vertex considered here is taken from the original shape |
164 | 167 | * that this item is constructed from. This means that no transformation is |
243 | 246 | * @param p |
244 | 247 | * @return |
245 | 248 | */ |
246 | inline bool isPointInside(const Vertex& p) const | |
249 | inline bool isInside(const Vertex& p) const | |
247 | 250 | { |
248 | 251 | return sl::isInside(p, transformedShape()); |
249 | 252 | } |
306 | 309 | { |
307 | 310 | if(translation_ != tr) { |
308 | 311 | translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; |
309 | bb_cache_.valid = false; | |
312 | //bb_cache_.valid = false; | |
310 | 313 | } |
311 | 314 | } |
312 | 315 | |
341 | 344 | |
342 | 345 | inline Box boundingBox() const { |
343 | 346 | if(!bb_cache_.valid) { |
344 | bb_cache_.bb = sl::boundingBox(transformedShape()); | |
345 | bb_cache_.tr = {0, 0}; | |
347 | if(!has_rotation_) | |
348 | bb_cache_.bb = sl::boundingBox(offsettedShape()); | |
349 | else { | |
350 | // TODO make sure this works | |
351 | auto rotsh = offsettedShape(); | |
352 | sl::rotate(rotsh, rotation_); | |
353 | bb_cache_.bb = sl::boundingBox(rotsh); | |
354 | } | |
346 | 355 | bb_cache_.valid = true; |
347 | 356 | } |
348 | 357 | |
349 | auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr; | |
350 | return {bb.minCorner() + tr, bb.maxCorner() + tr}; | |
358 | auto &bb = bb_cache_.bb; auto &tr = translation_; | |
359 | return {bb.minCorner() + tr, bb.maxCorner() + tr }; | |
351 | 360 | } |
352 | 361 | |
353 | 362 | inline Vertex referenceVertex() const { |
437 | 446 | inline _Rectangle(Unit width, Unit height, |
438 | 447 | // disable this ctor if o != CLOCKWISE |
439 | 448 | enable_if_t< o == TO::CLOCKWISE, int> = 0 ): |
440 | _Item<RawShape>( ShapeLike::create<RawShape>( { | |
449 | _Item<RawShape>( sl::create<RawShape>( { | |
441 | 450 | {0, 0}, |
442 | 451 | {0, height}, |
443 | 452 | {width, height}, |
451 | 460 | inline _Rectangle(Unit width, Unit height, |
452 | 461 | // disable this ctor if o != COUNTER_CLOCKWISE |
453 | 462 | enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ): |
454 | _Item<RawShape>( ShapeLike::create<RawShape>( { | |
463 | _Item<RawShape>( sl::create<RawShape>( { | |
455 | 464 | {0, 0}, |
456 | 465 | {width, 0}, |
457 | 466 | {width, height}, |
472 | 481 | |
473 | 482 | template<class RawShape> |
474 | 483 | inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const { |
475 | return ShapeLike::isInside<RawShape>(boundingBox(), box); | |
484 | return sl::isInside<RawShape>(boundingBox(), box); | |
476 | 485 | } |
477 | 486 | |
478 | 487 | template<class RawShape> inline bool |
479 | 488 | _Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const { |
480 | return ShapeLike::isInside<RawShape>(transformedShape(), circ); | |
489 | return sl::isInside<RawShape>(transformedShape(), circ); | |
490 | } | |
491 | ||
492 | ||
493 | template<class I> using _ItemRef = std::reference_wrapper<I>; | |
494 | template<class I> using _ItemGroup = std::vector<_ItemRef<I>>; | |
495 | ||
496 | template<class Iterator> | |
497 | struct ConstItemRange { | |
498 | Iterator from; | |
499 | Iterator to; | |
500 | bool valid = false; | |
501 | ||
502 | ConstItemRange() = default; | |
503 | ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {} | |
504 | }; | |
505 | ||
506 | template<class Container> | |
507 | inline ConstItemRange<typename Container::const_iterator> | |
508 | rem(typename Container::const_iterator it, const Container& cont) { | |
509 | return {std::next(it), cont.end()}; | |
481 | 510 | } |
482 | 511 | |
483 | 512 | /** |
484 | 513 | * \brief A wrapper interface (trait) class for any placement strategy provider. |
485 | 514 | * |
486 | * If a client want's to use its own placement algorithm, all it has to do is to | |
515 | * If a client wants to use its own placement algorithm, all it has to do is to | |
487 | 516 | * specialize this class template and define all the ten methods it has. It can |
488 | 517 | * use the strategies::PlacerBoilerplace class for creating a new placement |
489 | 518 | * strategy where only the constructor and the trypack method has to be provided |
514 | 543 | */ |
515 | 544 | using PackResult = typename PlacementStrategy::PackResult; |
516 | 545 | |
517 | using ItemRef = std::reference_wrapper<Item>; | |
518 | using ItemGroup = std::vector<ItemRef>; | |
546 | using ItemRef = _ItemRef<Item>; | |
547 | using ItemGroup = _ItemGroup<Item>; | |
548 | using DefaultIterator = typename ItemGroup::const_iterator; | |
519 | 549 | |
520 | 550 | /** |
521 | 551 | * @brief Constructor taking the bin and an optional configuration. |
535 | 565 | * Note that it depends on the particular placer implementation how it |
536 | 566 | * reacts to config changes in the middle of a calculation. |
537 | 567 | * |
538 | * @param config The configuration object defined by the placement startegy. | |
568 | * @param config The configuration object defined by the placement strategy. | |
539 | 569 | */ |
540 | 570 | inline void configure(const Config& config) { impl_.configure(config); } |
541 | 571 | |
542 | 572 | /** |
543 | * @brief A method that tries to pack an item and returns an object | |
544 | * describing the pack result. | |
545 | * | |
546 | * The result can be casted to bool and used as an argument to the accept | |
547 | * method to accept a succesfully packed item. This way the next packing | |
548 | * will consider the accepted item as well. The PackResult should carry the | |
549 | * transformation info so that if the tried item is later modified or tried | |
550 | * multiple times, the result object should set it to the originally | |
551 | * determied position. An implementation can be found in the | |
552 | * strategies::PlacerBoilerplate::PackResult class. | |
553 | * | |
554 | * @param item Ithe item to be packed. | |
555 | * @return The PackResult object that can be implicitly casted to bool. | |
556 | */ | |
557 | inline PackResult trypack(Item& item) { return impl_.trypack(item); } | |
558 | ||
559 | /** | |
560 | * @brief A method to accept a previously tried item. | |
573 | * Try to pack an item with a result object that contains the packing | |
574 | * information for later accepting it. | |
575 | * | |
576 | * \param item_store A container of items that are intended to be packed | |
577 | * later. Can be used by the placer to switch tactics. When it's knows that | |
578 | * many items will come a greedy strategy may not be the best. | |
579 | * \param from The iterator to the item from which the packing should start, | |
580 | * including the pointed item | |
581 | * \param count How many items should be packed. If the value is 1, than | |
582 | * just the item pointed to by "from" argument should be packed. | |
583 | */ | |
584 | template<class Iter = DefaultIterator> | |
585 | inline PackResult trypack( | |
586 | Item& item, | |
587 | const ConstItemRange<Iter>& remaining = ConstItemRange<Iter>()) | |
588 | { | |
589 | return impl_.trypack(item, remaining); | |
590 | } | |
591 | ||
592 | /** | |
593 | * @brief A method to accept a previously tried item (or items). | |
561 | 594 | * |
562 | 595 | * If the pack result is a failure the method should ignore it. |
563 | 596 | * @param r The result of a previous trypack call. |
565 | 598 | inline void accept(PackResult& r) { impl_.accept(r); } |
566 | 599 | |
567 | 600 | /** |
568 | * @brief pack Try to pack an item and immediately accept it on success. | |
601 | * @brief pack Try to pack and immediately accept it on success. | |
569 | 602 | * |
570 | 603 | * A default implementation would be to call |
571 | * { auto&& r = trypack(item); accept(r); return r; } but we should let the | |
604 | * { auto&& r = trypack(...); accept(r); return r; } but we should let the | |
572 | 605 | * implementor of the placement strategy to harvest any optimizations from |
573 | * the absence of an intermadiate step. The above version can still be used | |
606 | * the absence of an intermediate step. The above version can still be used | |
574 | 607 | * in the implementation. |
575 | 608 | * |
576 | 609 | * @param item The item to pack. |
577 | 610 | * @return Returns true if the item was packed or false if it could not be |
578 | 611 | * packed. |
579 | 612 | */ |
580 | inline bool pack(Item& item) { return impl_.pack(item); } | |
613 | template<class Range = ConstItemRange<DefaultIterator>> | |
614 | inline bool pack( | |
615 | Item& item, | |
616 | const Range& remaining = Range()) | |
617 | { | |
618 | return impl_.pack(item, remaining); | |
619 | } | |
581 | 620 | |
582 | 621 | /// Unpack the last element (remove it from the list of packed items). |
583 | 622 | inline void unpackLast() { impl_.unpackLast(); } |
595 | 634 | inline void clearItems() { impl_.clearItems(); } |
596 | 635 | |
597 | 636 | inline double filledArea() const { return impl_.filledArea(); } |
598 | ||
599 | #ifndef NDEBUG | |
600 | inline auto getDebugItems() -> decltype(impl_.debug_items_)& | |
601 | { | |
602 | return impl_.debug_items_; | |
603 | } | |
604 | #endif | |
605 | 637 | |
606 | 638 | }; |
607 | 639 | |
627 | 659 | * Note that it depends on the particular placer implementation how it |
628 | 660 | * reacts to config changes in the middle of a calculation. |
629 | 661 | * |
630 | * @param config The configuration object defined by the selection startegy. | |
662 | * @param config The configuration object defined by the selection strategy. | |
631 | 663 | */ |
632 | 664 | inline void configure(const Config& config) { |
633 | 665 | impl_.configure(config); |
634 | 666 | } |
635 | 667 | |
636 | 668 | /** |
637 | * @brief A function callback which should be called whenewer an item or | |
638 | * a group of items where succesfully packed. | |
669 | * @brief A function callback which should be called whenever an item or | |
670 | * a group of items where successfully packed. | |
639 | 671 | * @param fn A function callback object taking one unsigned integer as the |
640 | 672 | * number of the remaining items to pack. |
641 | 673 | */ |
648 | 680 | * placer compatible with the PlacementStrategyLike interface. |
649 | 681 | * |
650 | 682 | * \param first, last The first and last iterator if the input sequence. It |
651 | * can be only an iterator of a type converitible to Item. | |
683 | * can be only an iterator of a type convertible to Item. | |
652 | 684 | * \param bin. The shape of the bin. It has to be supported by the placement |
653 | 685 | * strategy. |
654 | 686 | * \param An optional config object for the placer. |
680 | 712 | /** |
681 | 713 | * @brief Get the items for a particular bin. |
682 | 714 | * @param binIndex The index of the requested bin. |
683 | * @return Returns a list of allitems packed into the requested bin. | |
715 | * @return Returns a list of all items packed into the requested bin. | |
684 | 716 | */ |
685 | 717 | inline ItemGroup itemsForBin(size_t binIndex) { |
686 | 718 | return impl_.itemsForBin(binIndex); |
722 | 754 | >; |
723 | 755 | |
724 | 756 | /** |
725 | * The Arranger is the frontend class for the binpack2d library. It takes the | |
757 | * The Arranger is the front-end class for the libnest2d library. It takes the | |
726 | 758 | * input items and outputs the items with the proper transformations to be |
727 | 759 | * inside the provided bin. |
728 | 760 | */ |
729 | 761 | template<class PlacementStrategy, class SelectionStrategy > |
730 | class Arranger { | |
762 | class Nester { | |
731 | 763 | using TSel = SelectionStrategyLike<SelectionStrategy>; |
732 | 764 | TSel selector_; |
733 | bool use_min_bb_rotation_ = false; | |
734 | 765 | public: |
735 | 766 | using Item = typename PlacementStrategy::Item; |
736 | 767 | using ItemRef = std::reference_wrapper<Item>; |
768 | 799 | template<class TBinType = BinType, |
769 | 800 | class PConf = PlacementConfig, |
770 | 801 | class SConf = SelectionConfig> |
771 | Arranger( TBinType&& bin, | |
802 | Nester( TBinType&& bin, | |
772 | 803 | Unit min_obj_distance = 0, |
773 | 804 | PConf&& pconfig = PConf(), |
774 | 805 | SConf&& sconfig = SConf()): |
801 | 832 | * the selection algorithm. |
802 | 833 | */ |
803 | 834 | template<class TIterator> |
804 | inline PackGroup arrange(TIterator from, TIterator to) | |
805 | { | |
806 | return _arrange(from, to); | |
835 | inline PackGroup execute(TIterator from, TIterator to) | |
836 | { | |
837 | return _execute(from, to); | |
807 | 838 | } |
808 | 839 | |
809 | 840 | /** |
814 | 845 | * input sequence size. |
815 | 846 | */ |
816 | 847 | template<class TIterator> |
817 | inline IndexedPackGroup arrangeIndexed(TIterator from, TIterator to) | |
818 | { | |
819 | return _arrangeIndexed(from, to); | |
848 | inline IndexedPackGroup executeIndexed(TIterator from, TIterator to) | |
849 | { | |
850 | return _executeIndexed(from, to); | |
820 | 851 | } |
821 | 852 | |
822 | 853 | /// Shorthand to normal arrange method. |
823 | 854 | template<class TIterator> |
824 | 855 | inline PackGroup operator() (TIterator from, TIterator to) |
825 | 856 | { |
826 | return _arrange(from, to); | |
827 | } | |
828 | ||
829 | /// Set a progress indicatior function object for the selector. | |
830 | inline Arranger& progressIndicator(ProgressFunction func) | |
857 | return _execute(from, to); | |
858 | } | |
859 | ||
860 | /// Set a progress indicator function object for the selector. | |
861 | inline Nester& progressIndicator(ProgressFunction func) | |
831 | 862 | { |
832 | 863 | selector_.progressIndicator(func); return *this; |
833 | 864 | } |
841 | 872 | return ret; |
842 | 873 | } |
843 | 874 | |
844 | inline Arranger& useMinimumBoundigBoxRotation(bool s = true) { | |
845 | use_min_bb_rotation_ = s; return *this; | |
846 | } | |
847 | ||
848 | 875 | private: |
849 | 876 | |
850 | 877 | template<class TIterator, |
851 | 878 | class IT = remove_cvref_t<typename TIterator::value_type>, |
852 | 879 | |
853 | // This funtion will be used only if the iterators are pointing to | |
854 | // a type compatible with the binpack2d::_Item template. | |
880 | // This function will be used only if the iterators are pointing to | |
881 | // a type compatible with the libnets2d::_Item template. | |
855 | 882 | // This way we can use references to input elements as they will |
856 | 883 | // have to exist for the lifetime of this call. |
857 | 884 | class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT> |
858 | 885 | > |
859 | inline PackGroup _arrange(TIterator from, TIterator to, bool = false) | |
860 | { | |
861 | __arrange(from, to); | |
886 | inline PackGroup _execute(TIterator from, TIterator to, bool = false) | |
887 | { | |
888 | __execute(from, to); | |
862 | 889 | return lastResult(); |
863 | 890 | } |
864 | 891 | |
866 | 893 | class IT = remove_cvref_t<typename TIterator::value_type>, |
867 | 894 | class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT> |
868 | 895 | > |
869 | inline PackGroup _arrange(TIterator from, TIterator to, int = false) | |
896 | inline PackGroup _execute(TIterator from, TIterator to, int = false) | |
870 | 897 | { |
871 | 898 | item_cache_ = {from, to}; |
872 | 899 | |
873 | __arrange(item_cache_.begin(), item_cache_.end()); | |
900 | __execute(item_cache_.begin(), item_cache_.end()); | |
874 | 901 | return lastResult(); |
875 | 902 | } |
876 | 903 | |
877 | 904 | template<class TIterator, |
878 | 905 | class IT = remove_cvref_t<typename TIterator::value_type>, |
879 | 906 | |
880 | // This funtion will be used only if the iterators are pointing to | |
881 | // a type compatible with the binpack2d::_Item template. | |
907 | // This function will be used only if the iterators are pointing to | |
908 | // a type compatible with the libnest2d::_Item template. | |
882 | 909 | // This way we can use references to input elements as they will |
883 | 910 | // have to exist for the lifetime of this call. |
884 | 911 | class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT> |
885 | 912 | > |
886 | inline IndexedPackGroup _arrangeIndexed(TIterator from, | |
913 | inline IndexedPackGroup _executeIndexed(TIterator from, | |
887 | 914 | TIterator to, |
888 | 915 | bool = false) |
889 | 916 | { |
890 | __arrange(from, to); | |
917 | __execute(from, to); | |
891 | 918 | return createIndexedPackGroup(from, to, selector_); |
892 | 919 | } |
893 | 920 | |
895 | 922 | class IT = remove_cvref_t<typename TIterator::value_type>, |
896 | 923 | class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT> |
897 | 924 | > |
898 | inline IndexedPackGroup _arrangeIndexed(TIterator from, | |
925 | inline IndexedPackGroup _executeIndexed(TIterator from, | |
899 | 926 | TIterator to, |
900 | 927 | int = false) |
901 | 928 | { |
902 | 929 | item_cache_ = {from, to}; |
903 | __arrange(item_cache_.begin(), item_cache_.end()); | |
930 | __execute(item_cache_.begin(), item_cache_.end()); | |
904 | 931 | return createIndexedPackGroup(from, to, selector_); |
905 | 932 | } |
906 | 933 | |
932 | 959 | return pg; |
933 | 960 | } |
934 | 961 | |
935 | Radians findBestRotation(Item& item) { | |
936 | opt::StopCriteria stopcr; | |
937 | stopcr.absolute_score_difference = 0.01; | |
938 | stopcr.max_iterations = 10000; | |
939 | opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr); | |
940 | ||
941 | auto orig_rot = item.rotation(); | |
942 | ||
943 | auto result = solver.optimize_min([&item, &orig_rot](Radians rot){ | |
944 | item.rotation(orig_rot + rot); | |
945 | auto bb = item.boundingBox(); | |
946 | return std::sqrt(bb.height()*bb.width()); | |
947 | }, opt::initvals(Radians(0)), opt::bound<Radians>(-Pi/2, Pi/2)); | |
948 | ||
949 | item.rotation(orig_rot); | |
950 | ||
951 | return std::get<0>(result.optimum); | |
952 | } | |
953 | ||
954 | template<class TIter> inline void __arrange(TIter from, TIter to) | |
962 | template<class TIter> inline void __execute(TIter from, TIter to) | |
955 | 963 | { |
956 | 964 | if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { |
957 | 965 | item.addOffset(static_cast<Unit>(std::ceil(min_obj_distance_/2.0))); |
958 | 966 | }); |
959 | 967 | |
960 | if(use_min_bb_rotation_) | |
961 | std::for_each(from, to, [this](Item& item){ | |
962 | Radians rot = findBestRotation(item); | |
963 | item.rotate(rot); | |
964 | }); | |
965 | ||
966 | 968 | selector_.template packItems<PlacementStrategy>( |
967 | 969 | from, to, bin_, pconfig_); |
968 | 970 |
66 | 66 | // need to wrap that in a type (see metaloop::Int). |
67 | 67 | |
68 | 68 | /* |
69 | * A helper alias to create integer values wrapped as a type. It is nessecary | |
69 | * A helper alias to create integer values wrapped as a type. It is necessary | |
70 | 70 | * because a non type template parameter (such as int) would be prohibited in |
71 | 71 | * a partial specialization. Also for the same reason we have to use a class |
72 | 72 | * _Metaloop instead of a simple function as a functor. A function cannot be |
73 | * partially specialized in a way that is neccesary for this trick. | |
73 | * partially specialized in a way that is necessary for this trick. | |
74 | 74 | */ |
75 | 75 | template<int N> using Int = std::integral_constant<int, N>; |
76 | 76 | |
87 | 87 | // It takes the real functor that can be specified in-place but only |
88 | 88 | // with C++14 because the second parameter's type will depend on the |
89 | 89 | // type of the parameter pack element that is processed. In C++14 we can |
90 | // specify this second parameter type as auto in the lamda parameter list. | |
90 | // specify this second parameter type as auto in the lambda parameter list. | |
91 | 91 | inline MapFn(Fn&& fn): fn_(forward<Fn>(fn)) {} |
92 | 92 | |
93 | 93 | template<class T> void operator ()(T&& pack_element) { |
145 | 145 | * version of run is called which does not call itself anymore. |
146 | 146 | * |
147 | 147 | * If you are utterly annoyed, at least you have learned a super crazy |
148 | * functional metaprogramming pattern. | |
148 | * functional meta-programming pattern. | |
149 | 149 | */ |
150 | 150 | template<class...Args> |
151 | 151 | using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>; |
100 | 100 | |
101 | 101 | /// If the relative value difference between two scores. |
102 | 102 | double relative_score_difference = std::nan(""); |
103 | ||
104 | /// Stop if this value or better is found. | |
105 | double stop_score = std::nan(""); | |
103 | 106 | |
104 | 107 | unsigned max_iterations = 0; |
105 | 108 | }; |
141 | 141 | default: ; |
142 | 142 | } |
143 | 143 | |
144 | auto abs_diff = stopcr_.absolute_score_difference; | |
145 | auto rel_diff = stopcr_.relative_score_difference; | |
144 | double abs_diff = stopcr_.absolute_score_difference; | |
145 | double rel_diff = stopcr_.relative_score_difference; | |
146 | double stopval = stopcr_.stop_score; | |
146 | 147 | if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff); |
147 | 148 | if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff); |
149 | if(!std::isnan(stopval)) opt_.set_stopval(stopval); | |
148 | 150 | |
149 | 151 | if(this->stopcr_.max_iterations > 0) |
150 | 152 | opt_.set_maxeval(this->stopcr_.max_iterations ); |
4 | 4 | |
5 | 5 | #include "placer_boilerplate.hpp" |
6 | 6 | |
7 | namespace libnest2d { namespace strategies { | |
7 | namespace libnest2d { namespace placers { | |
8 | ||
9 | template<class T, class = T> struct Epsilon {}; | |
10 | ||
11 | template<class T> | |
12 | struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > { | |
13 | static const T Value = 1; | |
14 | }; | |
15 | ||
16 | template<class T> | |
17 | struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > { | |
18 | static const T Value = 1e-3; | |
19 | }; | |
8 | 20 | |
9 | 21 | template<class RawShape> |
10 | 22 | struct BLConfig { |
11 | TCoord<TPoint<RawShape>> min_obj_distance = 0; | |
23 | DECLARE_MAIN_TYPES(RawShape); | |
24 | ||
25 | Coord min_obj_distance = 0; | |
26 | Coord epsilon = Epsilon<Coord>::Value; | |
12 | 27 | bool allow_rotations = false; |
13 | 28 | }; |
14 | 29 | |
26 | 41 | |
27 | 42 | explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {} |
28 | 43 | |
29 | PackResult trypack(Item& item) { | |
44 | template<class Range = ConstItemRange<typename Base::DefaultIter>> | |
45 | PackResult trypack(Item& item, | |
46 | const Range& = Range()) | |
47 | { | |
30 | 48 | auto r = _trypack(item); |
31 | 49 | if(!r && Base::config_.allow_rotations) { |
50 | ||
32 | 51 | item.rotate(Degrees(90)); |
33 | 52 | r =_trypack(item); |
34 | 53 | } |
64 | 83 | setInitialPosition(item); |
65 | 84 | |
66 | 85 | Unit d = availableSpaceDown(item); |
67 | bool can_move = d > 1 /*std::numeric_limits<Unit>::epsilon()*/; | |
86 | auto eps = config_.epsilon; | |
87 | bool can_move = d > eps; | |
68 | 88 | bool can_be_packed = can_move; |
69 | 89 | bool left = true; |
70 | 90 | |
71 | 91 | while(can_move) { |
72 | 92 | if(left) { // write previous down move and go down |
73 | item.translate({0, -d+1}); | |
93 | item.translate({0, -d+eps}); | |
74 | 94 | d = availableSpaceLeft(item); |
75 | can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/; | |
95 | can_move = d > eps; | |
76 | 96 | left = false; |
77 | 97 | } else { // write previous left move and go down |
78 | item.translate({-d+1, 0}); | |
98 | item.translate({-d+eps, 0}); | |
79 | 99 | d = availableSpaceDown(item); |
80 | can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/; | |
100 | can_move = d > eps; | |
81 | 101 | left = true; |
82 | 102 | } |
83 | 103 | } |
111 | 131 | const RawShape& scanpoly) |
112 | 132 | { |
113 | 133 | auto tsh = other.transformedShape(); |
114 | return ( ShapeLike::intersects(tsh, scanpoly) || | |
115 | ShapeLike::isInside(tsh, scanpoly) ) && | |
116 | ( !ShapeLike::intersects(tsh, item.rawShape()) && | |
117 | !ShapeLike::isInside(tsh, item.rawShape()) ); | |
134 | return ( sl::intersects(tsh, scanpoly) || | |
135 | sl::isInside(tsh, scanpoly) ) && | |
136 | ( !sl::intersects(tsh, item.rawShape()) && | |
137 | !sl::isInside(tsh, item.rawShape()) ); | |
118 | 138 | } |
119 | 139 | |
120 | 140 | template<class C = Coord> |
125 | 145 | { |
126 | 146 | auto tsh = other.transformedShape(); |
127 | 147 | |
128 | bool inters_scanpoly = ShapeLike::intersects(tsh, scanpoly) && | |
129 | !ShapeLike::touches(tsh, scanpoly); | |
130 | bool inters_item = ShapeLike::intersects(tsh, item.rawShape()) && | |
131 | !ShapeLike::touches(tsh, item.rawShape()); | |
148 | bool inters_scanpoly = sl::intersects(tsh, scanpoly) && | |
149 | !sl::touches(tsh, scanpoly); | |
150 | bool inters_item = sl::intersects(tsh, item.rawShape()) && | |
151 | !sl::touches(tsh, item.rawShape()); | |
132 | 152 | |
133 | 153 | return ( inters_scanpoly || |
134 | ShapeLike::isInside(tsh, scanpoly)) && | |
154 | sl::isInside(tsh, scanpoly)) && | |
135 | 155 | ( !inters_item && |
136 | !ShapeLike::isInside(tsh, item.rawShape()) | |
156 | !sl::isInside(tsh, item.rawShape()) | |
137 | 157 | ); |
138 | 158 | } |
139 | 159 | |
140 | Container itemsInTheWayOf(const Item& item, const Dir dir) { | |
160 | ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) { | |
141 | 161 | // Get the left or down polygon, that has the same area as the shadow |
142 | 162 | // of input item reflected to the left or downwards |
143 | 163 | auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) : |
144 | 164 | downPoly(item); |
145 | 165 | |
146 | Container ret; // packed items 'in the way' of item | |
166 | ItemGroup ret; // packed items 'in the way' of item | |
147 | 167 | ret.reserve(items_.size()); |
148 | 168 | |
149 | 169 | // Predicate to find items that are 'in the way' for left (down) move |
172 | 192 | |
173 | 193 | if(dir == Dir::LEFT) { |
174 | 194 | getCoord = [](const Vertex& v) { return getX(v); }; |
175 | availableDistance = PointLike::horizontalDistance<Vertex>; | |
195 | availableDistance = pointlike::horizontalDistance<Vertex>; | |
176 | 196 | availableDistanceSV = [](const Segment& s, const Vertex& v) { |
177 | auto ret = PointLike::horizontalDistance<Vertex>(v, s); | |
197 | auto ret = pointlike::horizontalDistance<Vertex>(v, s); | |
178 | 198 | if(ret.second) ret.first = -ret.first; |
179 | 199 | return ret; |
180 | 200 | }; |
181 | 201 | } |
182 | 202 | else { |
183 | 203 | getCoord = [](const Vertex& v) { return getY(v); }; |
184 | availableDistance = PointLike::verticalDistance<Vertex>; | |
204 | availableDistance = pointlike::verticalDistance<Vertex>; | |
185 | 205 | availableDistanceSV = [](const Segment& s, const Vertex& v) { |
186 | auto ret = PointLike::verticalDistance<Vertex>(v, s); | |
206 | auto ret = pointlike::verticalDistance<Vertex>(v, s); | |
187 | 207 | if(ret.second) ret.first = -ret.first; |
188 | 208 | return ret; |
189 | 209 | }; |
213 | 233 | assert(pleft.vertexCount() > 0); |
214 | 234 | |
215 | 235 | auto trpleft = pleft.transformedShape(); |
216 | auto first = ShapeLike::begin(trpleft); | |
236 | auto first = sl::begin(trpleft); | |
217 | 237 | auto next = first + 1; |
218 | auto endit = ShapeLike::end(trpleft); | |
238 | auto endit = sl::end(trpleft); | |
219 | 239 | |
220 | 240 | while(next != endit) { |
221 | 241 | Segment seg(*(first++), *(next++)); |
339 | 359 | |
340 | 360 | // reserve for all vertices plus 2 for the left horizontal wall, 2 for |
341 | 361 | // the additional vertices for maintaning min object distance |
342 | ShapeLike::reserve(rsh, finish-start+4); | |
362 | sl::reserve(rsh, finish-start+4); | |
343 | 363 | |
344 | 364 | /*auto addOthers = [&rsh, finish, start, &item](){ |
345 | 365 | for(size_t i = start+1; i < finish; i++) |
346 | ShapeLike::addVertex(rsh, item.vertex(i)); | |
366 | sl::addVertex(rsh, item.vertex(i)); | |
347 | 367 | };*/ |
348 | 368 | |
349 | 369 | auto reverseAddOthers = [&rsh, finish, start, &item](){ |
350 | 370 | for(auto i = finish-1; i > start; i--) |
351 | ShapeLike::addVertex(rsh, item.vertex( | |
371 | sl::addVertex(rsh, item.vertex( | |
352 | 372 | static_cast<unsigned long>(i))); |
353 | 373 | }; |
354 | 374 | |
360 | 380 | |
361 | 381 | // Clockwise polygon construction |
362 | 382 | |
363 | ShapeLike::addVertex(rsh, topleft_vertex); | |
383 | sl::addVertex(rsh, topleft_vertex); | |
364 | 384 | |
365 | 385 | if(dir == Dir::LEFT) reverseAddOthers(); |
366 | 386 | else { |
367 | ShapeLike::addVertex(rsh, getX(topleft_vertex), 0); | |
368 | ShapeLike::addVertex(rsh, getX(bottomleft_vertex), 0); | |
369 | } | |
370 | ||
371 | ShapeLike::addVertex(rsh, bottomleft_vertex); | |
387 | sl::addVertex(rsh, getX(topleft_vertex), 0); | |
388 | sl::addVertex(rsh, getX(bottomleft_vertex), 0); | |
389 | } | |
390 | ||
391 | sl::addVertex(rsh, bottomleft_vertex); | |
372 | 392 | |
373 | 393 | if(dir == Dir::LEFT) { |
374 | ShapeLike::addVertex(rsh, 0, getY(bottomleft_vertex)); | |
375 | ShapeLike::addVertex(rsh, 0, getY(topleft_vertex)); | |
394 | sl::addVertex(rsh, 0, getY(bottomleft_vertex)); | |
395 | sl::addVertex(rsh, 0, getY(topleft_vertex)); | |
376 | 396 | } |
377 | 397 | else reverseAddOthers(); |
378 | 398 | |
379 | 399 | |
380 | 400 | // Close the polygon |
381 | ShapeLike::addVertex(rsh, topleft_vertex); | |
401 | sl::addVertex(rsh, topleft_vertex); | |
382 | 402 | |
383 | 403 | return rsh; |
384 | 404 | } |
0 | 0 | #ifndef NOFITPOLY_HPP |
1 | 1 | #define NOFITPOLY_HPP |
2 | ||
3 | #include <cassert> | |
4 | ||
5 | // For caching nfps | |
6 | #include <unordered_map> | |
7 | ||
8 | // For parallel for | |
9 | #include <functional> | |
10 | #include <iterator> | |
11 | #include <future> | |
12 | #include <atomic> | |
2 | 13 | |
3 | 14 | #ifndef NDEBUG |
4 | 15 | #include <iostream> |
6 | 17 | #include "placer_boilerplate.hpp" |
7 | 18 | #include "../geometry_traits_nfp.hpp" |
8 | 19 | #include "libnest2d/optimizer.hpp" |
9 | #include <cassert> | |
10 | 20 | |
11 | 21 | #include "tools/svgtools.hpp" |
12 | 22 | |
13 | namespace libnest2d { namespace strategies { | |
23 | #ifdef USE_TBB | |
24 | #include <tbb/parallel_for.h> | |
25 | #elif defined(_OPENMP) | |
26 | #include <omp.h> | |
27 | #endif | |
28 | ||
29 | namespace libnest2d { | |
30 | ||
31 | namespace __parallel { | |
32 | ||
33 | using std::function; | |
34 | using std::iterator_traits; | |
35 | template<class It> | |
36 | using TIteratorValue = typename iterator_traits<It>::value_type; | |
37 | ||
38 | template<class Iterator> | |
39 | inline void enumerate( | |
40 | Iterator from, Iterator to, | |
41 | function<void(TIteratorValue<Iterator>, size_t)> fn, | |
42 | std::launch policy = std::launch::deferred | std::launch::async) | |
43 | { | |
44 | using TN = size_t; | |
45 | auto iN = to-from; | |
46 | TN N = iN < 0? 0 : TN(iN); | |
47 | ||
48 | #ifdef USE_TBB | |
49 | if((policy & std::launch::async) == std::launch::async) { | |
50 | tbb::parallel_for<TN>(0, N, [from, fn] (TN n) { fn(*(from + n), n); } ); | |
51 | } else { | |
52 | for(TN n = 0; n < N; n++) fn(*(from + n), n); | |
53 | } | |
54 | #elif defined(_OPENMP) | |
55 | if((policy & std::launch::async) == std::launch::async) { | |
56 | #pragma omp parallel for | |
57 | for(TN n = 0; n < N; n++) fn(*(from + n), n); | |
58 | } | |
59 | else { | |
60 | for(TN n = 0; n < N; n++) fn(*(from + n), n); | |
61 | } | |
62 | #else | |
63 | std::vector<std::future<void>> rets(N); | |
64 | ||
65 | auto it = from; | |
66 | for(TN b = 0; b < N; b++) { | |
67 | rets[b] = std::async(policy, fn, *it++, unsigned(b)); | |
68 | } | |
69 | ||
70 | for(TN fi = 0; fi < N; ++fi) rets[fi].wait(); | |
71 | #endif | |
72 | } | |
73 | ||
74 | class SpinLock { | |
75 | static std::atomic_flag locked; | |
76 | public: | |
77 | void lock() { | |
78 | while (locked.test_and_set(std::memory_order_acquire)) { ; } | |
79 | } | |
80 | void unlock() { | |
81 | locked.clear(std::memory_order_release); | |
82 | } | |
83 | }; | |
84 | ||
85 | std::atomic_flag SpinLock::locked = ATOMIC_FLAG_INIT ; | |
86 | ||
87 | } | |
88 | ||
89 | namespace __itemhash { | |
90 | ||
91 | using Key = size_t; | |
92 | ||
93 | template<class S> | |
94 | Key hash(const _Item<S>& item) { | |
95 | using Point = TPoint<S>; | |
96 | using Segment = _Segment<Point>; | |
97 | ||
98 | static const int N = 26; | |
99 | static const int M = N*N - 1; | |
100 | ||
101 | std::string ret; | |
102 | auto& rhs = item.rawShape(); | |
103 | auto& ctr = sl::getContour(rhs); | |
104 | auto it = ctr.begin(); | |
105 | auto nx = std::next(it); | |
106 | ||
107 | double circ = 0; | |
108 | while(nx != ctr.end()) { | |
109 | Segment seg(*it++, *nx++); | |
110 | Radians a = seg.angleToXaxis(); | |
111 | double deg = Degrees(a); | |
112 | int ms = 'A', ls = 'A'; | |
113 | while(deg > N) { ms++; deg -= N; } | |
114 | ls += int(deg); | |
115 | ret.push_back(char(ms)); ret.push_back(char(ls)); | |
116 | circ += seg.length(); | |
117 | } | |
118 | ||
119 | it = ctr.begin(); nx = std::next(it); | |
120 | ||
121 | while(nx != ctr.end()) { | |
122 | Segment seg(*it++, *nx++); | |
123 | auto l = int(M * seg.length() / circ); | |
124 | int ms = 'A', ls = 'A'; | |
125 | while(l > N) { ms++; l -= N; } | |
126 | ls += l; | |
127 | ret.push_back(char(ms)); ret.push_back(char(ls)); | |
128 | } | |
129 | ||
130 | return std::hash<std::string>()(ret); | |
131 | } | |
132 | ||
133 | template<class S> | |
134 | using Hash = std::unordered_map<Key, nfp::NfpResult<S>>; | |
135 | ||
136 | } | |
137 | ||
138 | namespace placers { | |
14 | 139 | |
15 | 140 | template<class RawShape> |
16 | 141 | struct NfpPConfig { |
142 | ||
143 | using ItemGroup = _ItemGroup<_Item<RawShape>>; | |
17 | 144 | |
18 | 145 | enum class Alignment { |
19 | 146 | CENTER, |
44 | 171 | * that will optimize for the best pack efficiency. With a custom fitting |
45 | 172 | * function you can e.g. influence the shape of the arranged pile. |
46 | 173 | * |
47 | * \param shapes The first parameter is a container with all the placed | |
48 | * polygons excluding the current candidate. You can calculate a bounding | |
49 | * box or convex hull on this pile of polygons without the candidate item | |
50 | * or push back the candidate item into the container and then calculate | |
51 | * some features. | |
52 | * | |
53 | * \param item The second parameter is the candidate item. | |
54 | * | |
55 | * \param occupied_area The third parameter is the sum of areas of the | |
56 | * items in the first parameter so you don't have to iterate through them | |
57 | * if you only need their area. | |
58 | * | |
59 | * \param norm A norming factor for physical dimensions. E.g. if your score | |
60 | * is the distance between the item and the bin center, you should divide | |
61 | * that distance with the norming factor. If the score is an area than | |
62 | * divide it with the square of the norming factor. Imagine it as a unit of | |
63 | * distance. | |
64 | * | |
65 | * \param penality The fifth parameter is the amount of minimum penality if | |
66 | * the arranged pile would't fit into the bin. You can use the wouldFit() | |
67 | * function to check this. Note that the pile can be outside the bin's | |
68 | * boundaries while the placement algorithm is running. Your job is only to | |
69 | * check if the pile could be translated into a position in the bin where | |
70 | * all the items would be inside. For a box shaped bin you can use the | |
71 | * pile's bounding box to check whether it's width and height is small | |
72 | * enough. If the pile would not fit, you have to make sure that the | |
73 | * resulting score will be higher then the penality value. A good solution | |
74 | * would be to set score = 2*penality-score in case the pile wouldn't fit | |
75 | * into the bin. | |
174 | * \param item The only parameter is the candidate item which has info | |
175 | * about its current position. Your job is to rate this position compared to | |
176 | * the already packed items. | |
76 | 177 | * |
77 | 178 | */ |
78 | std::function<double(Nfp::Shapes<RawShape>&, const _Item<RawShape>&, | |
79 | double, double, double)> | |
80 | object_function; | |
179 | std::function<double(const _Item<RawShape>&)> object_function; | |
81 | 180 | |
82 | 181 | /** |
83 | 182 | * @brief The quality of search for an optimal placement. |
84 | 183 | * This is a compromise slider between quality and speed. Zero is the |
85 | 184 | * fast and poor solution while 1.0 is the slowest but most accurate. |
86 | 185 | */ |
87 | float accuracy = 1.0; | |
186 | float accuracy = 0.65f; | |
88 | 187 | |
89 | 188 | /** |
90 | 189 | * @brief If you want to see items inside other item's holes, you have to |
94 | 193 | * The library has no such implementation right now. |
95 | 194 | */ |
96 | 195 | bool explore_holes = false; |
196 | ||
197 | /** | |
198 | * @brief If true, use all CPUs available. Run on a single core otherwise. | |
199 | */ | |
200 | bool parallel = true; | |
201 | ||
202 | /** | |
203 | * @brief before_packing Callback that is called just before a search for | |
204 | * a new item's position is started. You can use this to create various | |
205 | * cache structures and update them between subsequent packings. | |
206 | * | |
207 | * \param merged pile A polygon that is the union of all items in the bin. | |
208 | * | |
209 | * \param pile The items parameter is a container with all the placed | |
210 | * polygons excluding the current candidate. You can for instance check the | |
211 | * alignment with the candidate item or do anything else. | |
212 | * | |
213 | * \param remaining A container with the remaining items waiting to be | |
214 | * placed. You can use some features about the remaining items to alter to | |
215 | * score of the current placement. If you know that you have to leave place | |
216 | * for other items as well, that might influence your decision about where | |
217 | * the current candidate should be placed. E.g. imagine three big circles | |
218 | * which you want to place into a box: you might place them in a triangle | |
219 | * shape which has the maximum pack density. But if there is a 4th big | |
220 | * circle than you won't be able to pack it. If you knew apriori that | |
221 | * there four circles are to be placed, you would have placed the first 3 | |
222 | * into an L shape. This parameter can be used to make these kind of | |
223 | * decisions (for you or a more intelligent AI). | |
224 | */ | |
225 | std::function<void(const nfp::Shapes<RawShape>&, // merged pile | |
226 | const ItemGroup&, // packed items | |
227 | const ItemGroup& // remaining items | |
228 | )> before_packing; | |
97 | 229 | |
98 | 230 | NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), |
99 | 231 | alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {} |
128 | 260 | |
129 | 261 | void createCache(const RawShape& sh) { |
130 | 262 | { // For the contour |
131 | auto first = ShapeLike::cbegin(sh); | |
263 | auto first = shapelike::cbegin(sh); | |
132 | 264 | auto next = std::next(first); |
133 | auto endit = ShapeLike::cend(sh); | |
134 | ||
135 | contour_.distances.reserve(ShapeLike::contourVertexCount(sh)); | |
265 | auto endit = shapelike::cend(sh); | |
266 | ||
267 | contour_.distances.reserve(shapelike::contourVertexCount(sh)); | |
136 | 268 | |
137 | 269 | while(next != endit) { |
138 | 270 | contour_.emap.emplace_back(*(first++), *(next++)); |
141 | 273 | } |
142 | 274 | } |
143 | 275 | |
144 | for(auto& h : ShapeLike::holes(sh)) { // For the holes | |
276 | for(auto& h : shapelike::holes(sh)) { // For the holes | |
145 | 277 | auto first = h.begin(); |
146 | 278 | auto next = std::next(first); |
147 | 279 | auto endit = h.end(); |
160 | 292 | } |
161 | 293 | |
162 | 294 | size_t stride(const size_t N) const { |
163 | using std::ceil; | |
164 | 295 | using std::round; |
165 | 296 | using std::pow; |
166 | 297 | |
167 | 298 | return static_cast<Coord>( |
168 | std::round(N/std::pow(N, std::pow(accuracy_, 1.0/3.0))) | |
299 | round(N/pow(N, pow(accuracy_, 1.0/3.0))) | |
169 | 300 | ); |
170 | 301 | } |
171 | 302 | |
176 | 307 | const auto S = stride(N); |
177 | 308 | |
178 | 309 | contour_.corners.reserve(N / S + 1); |
310 | contour_.corners.emplace_back(0.0); | |
179 | 311 | auto N_1 = N-1; |
180 | 312 | contour_.corners.emplace_back(0.0); |
181 | 313 | for(size_t i = 0; i < N_1; i += S) { |
189 | 321 | if(!hc.corners.empty()) return; |
190 | 322 | |
191 | 323 | const auto N = hc.distances.size(); |
324 | auto N_1 = N-1; | |
192 | 325 | const auto S = stride(N); |
193 | auto N_1 = N-1; | |
194 | 326 | hc.corners.reserve(N / S + 1); |
195 | 327 | hc.corners.emplace_back(0.0); |
196 | 328 | for(size_t i = 0; i < N_1; i += S) { |
289 | 421 | |
290 | 422 | }; |
291 | 423 | |
292 | template<NfpLevel lvl> | |
293 | struct Lvl { static const NfpLevel value = lvl; }; | |
424 | template<nfp::NfpLevel lvl> | |
425 | struct Lvl { static const nfp::NfpLevel value = lvl; }; | |
294 | 426 | |
295 | 427 | template<class RawShape> |
296 | inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp, | |
428 | inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp, | |
297 | 429 | const _Item<RawShape>& stationary, |
298 | 430 | const _Item<RawShape>& orbiter) |
299 | 431 | { |
313 | 445 | auto dtouch = touch_sh - touch_other; |
314 | 446 | auto top_other = orbiter.rightmostTopVertex() + dtouch; |
315 | 447 | auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point |
316 | ShapeLike::translate(nfp.first, dnfp); | |
448 | shapelike::translate(nfp.first, dnfp); | |
317 | 449 | } |
318 | 450 | |
319 | 451 | template<class RawShape> |
320 | inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp, | |
452 | inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp, | |
321 | 453 | const RawShape& stationary, |
322 | 454 | const _Item<RawShape>& orbiter) |
323 | 455 | { |
324 | auto touch_sh = Nfp::rightmostUpVertex(stationary); | |
456 | auto touch_sh = nfp::rightmostUpVertex(stationary); | |
325 | 457 | auto touch_other = orbiter.leftmostBottomVertex(); |
326 | 458 | auto dtouch = touch_sh - touch_other; |
327 | 459 | auto top_other = orbiter.rightmostTopVertex() + dtouch; |
328 | 460 | auto dnfp = top_other - nfp.second; |
329 | ShapeLike::translate(nfp.first, dnfp); | |
461 | shapelike::translate(nfp.first, dnfp); | |
330 | 462 | } |
331 | 463 | |
332 | template<class RawShape, class Container> | |
333 | Nfp::Shapes<RawShape> nfp( const Container& polygons, | |
334 | const _Item<RawShape>& trsh, | |
335 | Lvl<NfpLevel::CONVEX_ONLY>) | |
336 | { | |
337 | using Item = _Item<RawShape>; | |
338 | ||
339 | Nfp::Shapes<RawShape> nfps; | |
340 | ||
341 | //int pi = 0; | |
342 | for(Item& sh : polygons) { | |
343 | auto subnfp_r = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>( | |
344 | sh.transformedShape(), trsh.transformedShape()); | |
345 | #ifndef NDEBUG | |
346 | auto vv = ShapeLike::isValid(sh.transformedShape()); | |
347 | assert(vv.first); | |
348 | ||
349 | auto vnfp = ShapeLike::isValid(subnfp_r.first); | |
350 | assert(vnfp.first); | |
351 | #endif | |
352 | ||
353 | correctNfpPosition(subnfp_r, sh, trsh); | |
354 | ||
355 | nfps = Nfp::merge(nfps, subnfp_r.first); | |
356 | ||
357 | // double SCALE = 1000000; | |
358 | // using SVGWriter = svg::SVGWriter<RawShape>; | |
359 | // SVGWriter::Config conf; | |
360 | // conf.mm_in_coord_units = SCALE; | |
361 | // SVGWriter svgw(conf); | |
362 | // Box bin(250*SCALE, 210*SCALE); | |
363 | // svgw.setSize(bin); | |
364 | // for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]); | |
365 | // svgw.writeItem(trsh); | |
366 | //// svgw.writeItem(Item(subnfp_r.first)); | |
367 | // for(auto& n : nfps) svgw.writeItem(Item(n)); | |
368 | // svgw.save("nfpout"); | |
369 | // pi++; | |
370 | } | |
371 | ||
372 | return nfps; | |
464 | template<class RawShape, class Circle = _Circle<TPoint<RawShape>> > | |
465 | Circle minimizeCircle(const RawShape& sh) { | |
466 | using Point = TPoint<RawShape>; | |
467 | using Coord = TCoord<Point>; | |
468 | ||
469 | auto& ctr = sl::getContour(sh); | |
470 | if(ctr.empty()) return {{0, 0}, 0}; | |
471 | ||
472 | auto bb = sl::boundingBox(sh); | |
473 | auto capprx = bb.center(); | |
474 | auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner()); | |
475 | ||
476 | ||
477 | opt::StopCriteria stopcr; | |
478 | stopcr.max_iterations = 30; | |
479 | stopcr.relative_score_difference = 1e-3; | |
480 | opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr); | |
481 | ||
482 | std::vector<double> dists(ctr.size(), 0); | |
483 | ||
484 | auto result = solver.optimize_min( | |
485 | [capprx, rapprx, &ctr, &dists](double xf, double yf) { | |
486 | auto xt = Coord( std::round(getX(capprx) + rapprx*xf) ); | |
487 | auto yt = Coord( std::round(getY(capprx) + rapprx*yf) ); | |
488 | ||
489 | Point centr(xt, yt); | |
490 | ||
491 | unsigned i = 0; | |
492 | for(auto v : ctr) { | |
493 | dists[i++] = pl::distance(v, centr); | |
494 | } | |
495 | ||
496 | auto mit = std::max_element(dists.begin(), dists.end()); | |
497 | ||
498 | assert(mit != dists.end()); | |
499 | ||
500 | return *mit; | |
501 | }, | |
502 | opt::initvals(0.0, 0.0), | |
503 | opt::bound(-1.0, 1.0), opt::bound(-1.0, 1.0) | |
504 | ); | |
505 | ||
506 | double oxf = std::get<0>(result.optimum); | |
507 | double oyf = std::get<1>(result.optimum); | |
508 | auto xt = Coord( std::round(getX(capprx) + rapprx*oxf) ); | |
509 | auto yt = Coord( std::round(getY(capprx) + rapprx*oyf) ); | |
510 | ||
511 | Point cc(xt, yt); | |
512 | auto r = result.score; | |
513 | ||
514 | return {cc, r}; | |
373 | 515 | } |
374 | 516 | |
375 | template<class RawShape, class Container, class Level> | |
376 | Nfp::Shapes<RawShape> nfp( const Container& polygons, | |
377 | const _Item<RawShape>& trsh, | |
378 | Level) | |
379 | { | |
380 | using Item = _Item<RawShape>; | |
381 | ||
382 | Nfp::Shapes<RawShape> nfps; | |
383 | ||
384 | auto& orb = trsh.transformedShape(); | |
385 | bool orbconvex = trsh.isContourConvex(); | |
386 | ||
387 | for(Item& sh : polygons) { | |
388 | Nfp::NfpResult<RawShape> subnfp; | |
389 | auto& stat = sh.transformedShape(); | |
390 | ||
391 | if(sh.isContourConvex() && orbconvex) | |
392 | subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb); | |
393 | else if(orbconvex) | |
394 | subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb); | |
395 | else | |
396 | subnfp = Nfp::noFitPolygon<Level::value>(stat, orb); | |
397 | ||
398 | correctNfpPosition(subnfp, sh, trsh); | |
399 | ||
400 | nfps = Nfp::merge(nfps, subnfp.first); | |
401 | } | |
402 | ||
403 | return nfps; | |
404 | ||
405 | ||
406 | // using Item = _Item<RawShape>; | |
407 | // using sl = ShapeLike; | |
408 | ||
409 | // Nfp::Shapes<RawShape> nfps, stationary; | |
410 | ||
411 | // for(Item& sh : polygons) { | |
412 | // stationary = Nfp::merge(stationary, sh.transformedShape()); | |
413 | // } | |
414 | ||
415 | // for(RawShape& sh : stationary) { | |
416 | ||
417 | //// auto vv = sl::isValid(sh); | |
418 | //// std::cout << vv.second << std::endl; | |
419 | ||
420 | ||
421 | // Nfp::NfpResult<RawShape> subnfp; | |
422 | // bool shconvex = sl::isConvex<RawShape>(sl::getContour(sh)); | |
423 | // if(shconvex && trsh.isContourConvex()) { | |
424 | // subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>( | |
425 | // sh, trsh.transformedShape()); | |
426 | // } else if(trsh.isContourConvex()) { | |
427 | // subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>( | |
428 | // sh, trsh.transformedShape()); | |
429 | // } | |
430 | // else { | |
431 | // subnfp = Nfp::noFitPolygon<Level::value>( sh, | |
432 | // trsh.transformedShape()); | |
433 | // } | |
434 | ||
435 | // correctNfpPosition(subnfp, sh, trsh); | |
436 | ||
437 | // nfps = Nfp::merge(nfps, subnfp.first); | |
438 | // } | |
439 | ||
440 | // return nfps; | |
517 | template<class RawShape> | |
518 | _Circle<TPoint<RawShape>> boundingCircle(const RawShape& sh) { | |
519 | return minimizeCircle(sh); | |
441 | 520 | } |
442 | 521 | |
443 | 522 | template<class RawShape, class TBin = _Box<TPoint<RawShape>>> |
451 | 530 | |
452 | 531 | using Box = _Box<TPoint<RawShape>>; |
453 | 532 | |
533 | using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>; | |
534 | ||
535 | using ItemKeys = std::vector<__itemhash::Key>; | |
536 | ||
537 | // Norming factor for the optimization function | |
454 | 538 | const double norm_; |
455 | const double penality_; | |
456 | ||
457 | using MaxNfpLevel = Nfp::MaxNfpLevel<RawShape>; | |
458 | using sl = ShapeLike; | |
539 | ||
540 | // Caching calculated nfps | |
541 | __itemhash::Hash<RawShape> nfpcache_; | |
542 | ||
543 | // Storing item hash keys | |
544 | ItemKeys item_keys_; | |
459 | 545 | |
460 | 546 | public: |
461 | 547 | |
462 | using Pile = Nfp::Shapes<RawShape>; | |
548 | using Pile = nfp::Shapes<RawShape>; | |
463 | 549 | |
464 | 550 | inline explicit _NofitPolyPlacer(const BinType& bin): |
465 | 551 | Base(bin), |
466 | norm_(std::sqrt(sl::area<RawShape>(bin))), | |
467 | penality_(1e6*norm_) {} | |
552 | norm_(std::sqrt(sl::area(bin))) {} | |
468 | 553 | |
469 | 554 | _NofitPolyPlacer(const _NofitPolyPlacer&) = default; |
470 | 555 | _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; |
474 | 559 | _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; |
475 | 560 | #endif |
476 | 561 | |
477 | bool static inline wouldFit(const Box& bb, const RawShape& bin) { | |
478 | auto bbin = sl::boundingBox<RawShape>(bin); | |
562 | static inline double overfit(const Box& bb, const RawShape& bin) { | |
563 | auto bbin = sl::boundingBox(bin); | |
479 | 564 | auto d = bbin.center() - bb.center(); |
480 | 565 | _Rectangle<RawShape> rect(bb.width(), bb.height()); |
481 | 566 | rect.translate(bb.minCorner() + d); |
482 | return sl::isInside<RawShape>(rect.transformedShape(), bin); | |
483 | } | |
484 | ||
485 | bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { | |
486 | auto bbch = sl::boundingBox<RawShape>(chull); | |
487 | auto bbin = sl::boundingBox<RawShape>(bin); | |
567 | return sl::isInside(rect.transformedShape(), bin) ? -1.0 : 1; | |
568 | } | |
569 | ||
570 | static inline double overfit(const RawShape& chull, const RawShape& bin) { | |
571 | auto bbch = sl::boundingBox(chull); | |
572 | auto bbin = sl::boundingBox(bin); | |
488 | 573 | auto d = bbch.center() - bbin.center(); |
489 | 574 | auto chullcpy = chull; |
490 | 575 | sl::translate(chullcpy, d); |
491 | return sl::isInside<RawShape>(chullcpy, bin); | |
492 | } | |
493 | ||
494 | bool static inline wouldFit(const RawShape& chull, const Box& bin) | |
576 | return sl::isInside(chullcpy, bin) ? -1.0 : 1.0; | |
577 | } | |
578 | ||
579 | static inline double overfit(const RawShape& chull, const Box& bin) | |
495 | 580 | { |
496 | auto bbch = sl::boundingBox<RawShape>(chull); | |
497 | return wouldFit(bbch, bin); | |
498 | } | |
499 | ||
500 | bool static inline wouldFit(const Box& bb, const Box& bin) | |
581 | auto bbch = sl::boundingBox(chull); | |
582 | return overfit(bbch, bin); | |
583 | } | |
584 | ||
585 | static inline double overfit(const Box& bb, const Box& bin) | |
501 | 586 | { |
502 | return bb.width() <= bin.width() && bb.height() <= bin.height(); | |
503 | } | |
504 | ||
505 | bool static inline wouldFit(const Box& bb, const _Circle<Vertex>& bin) | |
587 | auto wdiff = double(bb.width() - bin.width()); | |
588 | auto hdiff = double(bb.height() - bin.height()); | |
589 | double diff = 0; | |
590 | if(wdiff > 0) diff += wdiff; | |
591 | if(hdiff > 0) diff += hdiff; | |
592 | return diff; | |
593 | } | |
594 | ||
595 | static inline double overfit(const Box& bb, const _Circle<Vertex>& bin) | |
506 | 596 | { |
507 | return sl::isInside<RawShape>(bb, bin); | |
508 | } | |
509 | ||
510 | bool static inline wouldFit(const RawShape& chull, | |
597 | double boxr = 0.5*pl::distance(bb.minCorner(), bb.maxCorner()); | |
598 | double diff = boxr - bin.radius(); | |
599 | return diff; | |
600 | } | |
601 | ||
602 | static inline double overfit(const RawShape& chull, | |
511 | 603 | const _Circle<Vertex>& bin) |
512 | 604 | { |
513 | return sl::isInside<RawShape>(chull, bin); | |
514 | } | |
515 | ||
516 | PackResult trypack(Item& item) { | |
605 | double r = boundingCircle(chull).radius(); | |
606 | double diff = r - bin.radius(); | |
607 | return diff; | |
608 | } | |
609 | ||
610 | template<class Range = ConstItemRange<typename Base::DefaultIter>> | |
611 | PackResult trypack(Item& item, | |
612 | const Range& remaining = Range()) { | |
613 | auto result = _trypack(item, remaining); | |
614 | ||
615 | // Experimental | |
616 | // if(!result) repack(item, result); | |
617 | ||
618 | return result; | |
619 | } | |
620 | ||
621 | ~_NofitPolyPlacer() { | |
622 | clearItems(); | |
623 | } | |
624 | ||
625 | inline void clearItems() { | |
626 | finalAlign(bin_); | |
627 | Base::clearItems(); | |
628 | } | |
629 | ||
630 | private: | |
631 | ||
632 | using Shapes = TMultiShape<RawShape>; | |
633 | using ItemRef = std::reference_wrapper<Item>; | |
634 | using ItemWithHash = const std::pair<ItemRef, __itemhash::Key>; | |
635 | ||
636 | Shapes calcnfp(const ItemWithHash itsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>) | |
637 | { | |
638 | using namespace nfp; | |
639 | ||
640 | Shapes nfps(items_.size()); | |
641 | const Item& trsh = itsh.first; | |
642 | ||
643 | __parallel::enumerate(items_.begin(), items_.end(), | |
644 | [&nfps, &trsh](const Item& sh, size_t n) | |
645 | { | |
646 | auto& fixedp = sh.transformedShape(); | |
647 | auto& orbp = trsh.transformedShape(); | |
648 | auto subnfp_r = noFitPolygon<NfpLevel::CONVEX_ONLY>(fixedp, orbp); | |
649 | correctNfpPosition(subnfp_r, sh, trsh); | |
650 | nfps[n] = subnfp_r.first; | |
651 | }); | |
652 | ||
653 | // for(auto& n : nfps) { | |
654 | // auto valid = sl::isValid(n); | |
655 | // if(!valid.first) std::cout << "Warning: " << valid.second << std::endl; | |
656 | // } | |
657 | ||
658 | return nfp::merge(nfps); | |
659 | } | |
660 | ||
661 | template<class Level> | |
662 | Shapes calcnfp( const ItemWithHash itsh, Level) | |
663 | { // Function for arbitrary level of nfp implementation | |
664 | using namespace nfp; | |
665 | ||
666 | Shapes nfps; | |
667 | const Item& trsh = itsh.first; | |
668 | ||
669 | auto& orb = trsh.transformedShape(); | |
670 | bool orbconvex = trsh.isContourConvex(); | |
671 | ||
672 | for(Item& sh : items_) { | |
673 | nfp::NfpResult<RawShape> subnfp; | |
674 | auto& stat = sh.transformedShape(); | |
675 | ||
676 | if(sh.isContourConvex() && orbconvex) | |
677 | subnfp = nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb); | |
678 | else if(orbconvex) | |
679 | subnfp = nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb); | |
680 | else | |
681 | subnfp = nfp::noFitPolygon<Level::value>(stat, orb); | |
682 | ||
683 | correctNfpPosition(subnfp, sh, trsh); | |
684 | ||
685 | nfps = nfp::merge(nfps, subnfp.first); | |
686 | } | |
687 | ||
688 | return nfps; | |
689 | } | |
690 | ||
691 | // Very much experimental | |
692 | void repack(Item& item, PackResult& result) { | |
693 | ||
694 | if((sl::area(bin_) - this->filledArea()) >= item.area()) { | |
695 | auto prev_func = config_.object_function; | |
696 | ||
697 | unsigned iter = 0; | |
698 | ItemGroup backup_rf = items_; | |
699 | std::vector<Item> backup_cpy; | |
700 | for(Item& itm : items_) backup_cpy.emplace_back(itm); | |
701 | ||
702 | auto ofn = [this, &item, &result, &iter, &backup_cpy, &backup_rf] | |
703 | (double ratio) | |
704 | { | |
705 | auto& bin = bin_; | |
706 | iter++; | |
707 | config_.object_function = [bin, ratio]( | |
708 | nfp::Shapes<RawShape>& pile, | |
709 | const Item& item, | |
710 | const ItemGroup& /*remaining*/) | |
711 | { | |
712 | pile.emplace_back(item.transformedShape()); | |
713 | auto ch = sl::convexHull(pile); | |
714 | auto pbb = sl::boundingBox(pile); | |
715 | pile.pop_back(); | |
716 | ||
717 | double parea = 0.5*(sl::area(ch) + sl::area(pbb)); | |
718 | ||
719 | double pile_area = std::accumulate( | |
720 | pile.begin(), pile.end(), item.area(), | |
721 | [](double sum, const RawShape& sh){ | |
722 | return sum + sl::area(sh); | |
723 | }); | |
724 | ||
725 | // The pack ratio -- how much is the convex hull occupied | |
726 | double pack_rate = (pile_area)/parea; | |
727 | ||
728 | // ratio of waste | |
729 | double waste = 1.0 - pack_rate; | |
730 | ||
731 | // Score is the square root of waste. This will extend the | |
732 | // range of good (lower) values and shrink the range of bad | |
733 | // (larger) values. | |
734 | auto wscore = std::sqrt(waste); | |
735 | ||
736 | ||
737 | auto ibb = item.boundingBox(); | |
738 | auto bbb = sl::boundingBox(bin); | |
739 | auto c = ibb.center(); | |
740 | double norm = 0.5*pl::distance(bbb.minCorner(), | |
741 | bbb.maxCorner()); | |
742 | ||
743 | double dscore = pl::distance(c, pbb.center()) / norm; | |
744 | ||
745 | return ratio*wscore + (1.0 - ratio) * dscore; | |
746 | }; | |
747 | ||
748 | auto bb = sl::boundingBox(bin); | |
749 | double norm = bb.width() + bb.height(); | |
750 | ||
751 | auto items = items_; | |
752 | clearItems(); | |
753 | auto it = items.begin(); | |
754 | while(auto pr = _trypack(*it++)) { | |
755 | this->accept(pr); if(it == items.end()) break; | |
756 | } | |
757 | ||
758 | auto count_diff = items.size() - items_.size(); | |
759 | double score = count_diff; | |
760 | ||
761 | if(count_diff == 0) { | |
762 | result = _trypack(item); | |
763 | ||
764 | if(result) { | |
765 | std::cout << "Success" << std::endl; | |
766 | score = 0.0; | |
767 | } else { | |
768 | score += result.overfit() / norm; | |
769 | } | |
770 | } else { | |
771 | result = PackResult(); | |
772 | items_ = backup_rf; | |
773 | for(unsigned i = 0; i < items_.size(); i++) { | |
774 | items_[i].get() = backup_cpy[i]; | |
775 | } | |
776 | } | |
777 | ||
778 | std::cout << iter << " repack result: " << score << " " | |
779 | << ratio << " " << count_diff << std::endl; | |
780 | ||
781 | return score; | |
782 | }; | |
783 | ||
784 | opt::StopCriteria stopcr; | |
785 | stopcr.max_iterations = 30; | |
786 | stopcr.stop_score = 1e-20; | |
787 | opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr); | |
788 | solver.optimize_min(ofn, opt::initvals(0.5), | |
789 | opt::bound(0.0, 1.0)); | |
790 | ||
791 | // optimize | |
792 | config_.object_function = prev_func; | |
793 | } | |
794 | ||
795 | } | |
796 | ||
797 | struct Optimum { | |
798 | double relpos; | |
799 | unsigned nfpidx; | |
800 | int hidx; | |
801 | Optimum(double pos, unsigned nidx): | |
802 | relpos(pos), nfpidx(nidx), hidx(-1) {} | |
803 | Optimum(double pos, unsigned nidx, int holeidx): | |
804 | relpos(pos), nfpidx(nidx), hidx(holeidx) {} | |
805 | }; | |
806 | ||
807 | class Optimizer: public opt::TOptimizer<opt::Method::L_SUBPLEX> { | |
808 | public: | |
809 | Optimizer() { | |
810 | opt::StopCriteria stopcr; | |
811 | stopcr.max_iterations = 200; | |
812 | stopcr.relative_score_difference = 1e-20; | |
813 | this->stopcr_ = stopcr; | |
814 | } | |
815 | }; | |
816 | ||
817 | static Box boundingBox(const Box& pilebb, const Box& ibb ) { | |
818 | auto& pminc = pilebb.minCorner(); | |
819 | auto& pmaxc = pilebb.maxCorner(); | |
820 | auto& iminc = ibb.minCorner(); | |
821 | auto& imaxc = ibb.maxCorner(); | |
822 | Vertex minc, maxc; | |
823 | ||
824 | setX(minc, std::min(getX(pminc), getX(iminc))); | |
825 | setY(minc, std::min(getY(pminc), getY(iminc))); | |
826 | ||
827 | setX(maxc, std::max(getX(pmaxc), getX(imaxc))); | |
828 | setY(maxc, std::max(getY(pmaxc), getY(imaxc))); | |
829 | return Box(minc, maxc); | |
830 | } | |
831 | ||
832 | using Edges = EdgeCache<RawShape>; | |
833 | ||
834 | template<class Range = ConstItemRange<typename Base::DefaultIter>> | |
835 | PackResult _trypack( | |
836 | Item& item, | |
837 | const Range& remaining = Range()) { | |
517 | 838 | |
518 | 839 | PackResult ret; |
519 | 840 | |
520 | 841 | bool can_pack = false; |
842 | double best_overfit = std::numeric_limits<double>::max(); | |
843 | ||
844 | auto remlist = ItemGroup(remaining.from, remaining.to); | |
845 | size_t itemhash = __itemhash::hash(item); | |
521 | 846 | |
522 | 847 | if(items_.empty()) { |
523 | 848 | setInitialPosition(item); |
524 | can_pack = item.isInside(bin_); | |
849 | best_overfit = overfit(item.transformedShape(), bin_); | |
850 | can_pack = best_overfit <= 0; | |
525 | 851 | } else { |
526 | 852 | |
527 | double global_score = penality_; | |
853 | double global_score = std::numeric_limits<double>::max(); | |
528 | 854 | |
529 | 855 | auto initial_tr = item.translation(); |
530 | 856 | auto initial_rot = item.rotation(); |
531 | 857 | Vertex final_tr = {0, 0}; |
532 | 858 | Radians final_rot = initial_rot; |
533 | Nfp::Shapes<RawShape> nfps; | |
859 | Shapes nfps; | |
534 | 860 | |
535 | 861 | for(auto rot : config_.rotations) { |
536 | 862 | |
537 | 863 | item.translation(initial_tr); |
538 | 864 | item.rotation(initial_rot + rot); |
865 | item.boundingBox(); // fill the bb cache | |
539 | 866 | |
540 | 867 | // place the new item outside of the print bed to make sure |
541 | // it is disjuct from the current merged pile | |
868 | // it is disjunct from the current merged pile | |
542 | 869 | placeOutsideOfBin(item); |
543 | 870 | |
544 | auto trsh = item.transformedShape(); | |
545 | ||
546 | nfps = nfp(items_, item, Lvl<MaxNfpLevel::value>()); | |
547 | auto iv = Nfp::referenceVertex(trsh); | |
871 | nfps = calcnfp({item, itemhash}, Lvl<MaxNfpLevel::value>()); | |
872 | ||
873 | auto iv = item.referenceVertex(); | |
548 | 874 | |
549 | 875 | auto startpos = item.translation(); |
550 | 876 | |
551 | std::vector<EdgeCache<RawShape>> ecache; | |
877 | std::vector<Edges> ecache; | |
552 | 878 | ecache.reserve(nfps.size()); |
553 | 879 | |
554 | 880 | for(auto& nfp : nfps ) { |
556 | 882 | ecache.back().accuracy(config_.accuracy); |
557 | 883 | } |
558 | 884 | |
559 | struct Optimum { | |
560 | double relpos; | |
561 | unsigned nfpidx; | |
562 | int hidx; | |
563 | Optimum(double pos, unsigned nidx): | |
564 | relpos(pos), nfpidx(nidx), hidx(-1) {} | |
565 | Optimum(double pos, unsigned nidx, int holeidx): | |
566 | relpos(pos), nfpidx(nidx), hidx(holeidx) {} | |
885 | Shapes pile; | |
886 | pile.reserve(items_.size()+1); | |
887 | // double pile_area = 0; | |
888 | for(Item& mitem : items_) { | |
889 | pile.emplace_back(mitem.transformedShape()); | |
890 | // pile_area += mitem.area(); | |
891 | } | |
892 | ||
893 | auto merged_pile = nfp::merge(pile); | |
894 | auto& bin = bin_; | |
895 | double norm = norm_; | |
896 | auto pbb = sl::boundingBox(merged_pile); | |
897 | auto binbb = sl::boundingBox(bin); | |
898 | ||
899 | // This is the kernel part of the object function that is | |
900 | // customizable by the library client | |
901 | auto _objfunc = config_.object_function? | |
902 | config_.object_function : | |
903 | [norm, bin, binbb, pbb](const Item& item) | |
904 | { | |
905 | auto ibb = item.boundingBox(); | |
906 | auto fullbb = boundingBox(pbb, ibb); | |
907 | ||
908 | double score = pl::distance(ibb.center(), binbb.center()); | |
909 | score /= norm; | |
910 | ||
911 | double miss = overfit(fullbb, bin); | |
912 | miss = miss > 0? miss : 0; | |
913 | score += std::pow(miss, 2); | |
914 | ||
915 | return score; | |
916 | }; | |
917 | ||
918 | // Our object function for placement | |
919 | auto rawobjfunc = | |
920 | [_objfunc, iv, startpos] (Vertex v, Item& itm) | |
921 | { | |
922 | auto d = v - iv; | |
923 | d += startpos; | |
924 | itm.translation(d); | |
925 | return _objfunc(itm); | |
567 | 926 | }; |
568 | 927 | |
569 | 928 | auto getNfpPoint = [&ecache](const Optimum& opt) |
572 | 931 | ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); |
573 | 932 | }; |
574 | 933 | |
575 | Nfp::Shapes<RawShape> pile; | |
576 | pile.reserve(items_.size()+1); | |
577 | double pile_area = 0; | |
578 | for(Item& mitem : items_) { | |
579 | pile.emplace_back(mitem.transformedShape()); | |
580 | pile_area += mitem.area(); | |
581 | } | |
582 | ||
583 | auto merged_pile = Nfp::merge(pile); | |
584 | ||
585 | // This is the kernel part of the object function that is | |
586 | // customizable by the library client | |
587 | auto _objfunc = config_.object_function? | |
588 | config_.object_function : | |
589 | [this, &merged_pile]( | |
590 | Nfp::Shapes<RawShape>& /*pile*/, | |
591 | const Item& item, | |
592 | double occupied_area, | |
593 | double norm, | |
594 | double /*penality*/) | |
934 | auto boundaryCheck = | |
935 | [&merged_pile, &getNfpPoint, &item, &bin, &iv, &startpos] | |
936 | (const Optimum& o) | |
595 | 937 | { |
596 | merged_pile.emplace_back(item.transformedShape()); | |
597 | auto ch = sl::convexHull(merged_pile); | |
598 | merged_pile.pop_back(); | |
599 | ||
600 | // The pack ratio -- how much is the convex hull occupied | |
601 | double pack_rate = occupied_area/sl::area(ch); | |
602 | ||
603 | // ratio of waste | |
604 | double waste = 1.0 - pack_rate; | |
605 | ||
606 | // Score is the square root of waste. This will extend the | |
607 | // range of good (lower) values and shring the range of bad | |
608 | // (larger) values. | |
609 | auto score = std::sqrt(waste); | |
610 | ||
611 | if(!wouldFit(ch, bin_)) score += norm; | |
612 | ||
613 | return score; | |
614 | }; | |
615 | ||
616 | // Our object function for placement | |
617 | auto rawobjfunc = [&] (Vertex v) | |
618 | { | |
619 | auto d = v - iv; | |
620 | d += startpos; | |
621 | item.translation(d); | |
622 | ||
623 | double occupied_area = pile_area + item.area(); | |
624 | ||
625 | double score = _objfunc(pile, item, occupied_area, | |
626 | norm_, penality_); | |
627 | ||
628 | return score; | |
629 | }; | |
630 | ||
631 | auto boundaryCheck = [&](const Optimum& o) { | |
632 | 938 | auto v = getNfpPoint(o); |
633 | 939 | auto d = v - iv; |
634 | 940 | d += startpos; |
638 | 944 | auto chull = sl::convexHull(merged_pile); |
639 | 945 | merged_pile.pop_back(); |
640 | 946 | |
641 | return wouldFit(chull, bin_); | |
947 | return overfit(chull, bin); | |
642 | 948 | }; |
643 | 949 | |
644 | opt::StopCriteria stopcr; | |
645 | stopcr.max_iterations = 100; | |
646 | stopcr.relative_score_difference = 1e-6; | |
647 | opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr); | |
648 | ||
649 | 950 | Optimum optimum(0, 0); |
650 | double best_score = penality_; | |
951 | double best_score = std::numeric_limits<double>::max(); | |
952 | std::launch policy = std::launch::deferred; | |
953 | if(config_.parallel) policy |= std::launch::async; | |
954 | ||
955 | if(config_.before_packing) | |
956 | config_.before_packing(merged_pile, items_, remlist); | |
957 | ||
958 | using OptResult = opt::Result<double>; | |
959 | using OptResults = std::vector<OptResult>; | |
651 | 960 | |
652 | 961 | // Local optimization with the four polygon corners as |
653 | 962 | // starting points |
654 | 963 | for(unsigned ch = 0; ch < ecache.size(); ch++) { |
655 | 964 | auto& cache = ecache[ch]; |
656 | 965 | |
657 | auto contour_ofn = [&rawobjfunc, &getNfpPoint, ch] | |
658 | (double relpos) | |
966 | OptResults results(cache.corners().size()); | |
967 | ||
968 | auto& rofn = rawobjfunc; | |
969 | auto& nfpoint = getNfpPoint; | |
970 | ||
971 | __parallel::enumerate( | |
972 | cache.corners().begin(), | |
973 | cache.corners().end(), | |
974 | [&results, &item, &rofn, &nfpoint, ch] | |
975 | (double pos, size_t n) | |
659 | 976 | { |
660 | return rawobjfunc(getNfpPoint(Optimum(relpos, ch))); | |
661 | }; | |
662 | ||
663 | std::for_each(cache.corners().begin(), | |
664 | cache.corners().end(), | |
665 | [ch, &contour_ofn, &solver, &best_score, | |
666 | &optimum, &boundaryCheck] (double pos) | |
667 | { | |
977 | Optimizer solver; | |
978 | ||
979 | Item itemcpy = item; | |
980 | auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy] | |
981 | (double relpos) | |
982 | { | |
983 | Optimum op(relpos, ch); | |
984 | return rofn(nfpoint(op), itemcpy); | |
985 | }; | |
986 | ||
668 | 987 | try { |
669 | auto result = solver.optimize_min(contour_ofn, | |
988 | results[n] = solver.optimize_min(contour_ofn, | |
670 | 989 | opt::initvals<double>(pos), |
671 | 990 | opt::bound<double>(0, 1.0) |
672 | 991 | ); |
673 | ||
674 | if(result.score < best_score) { | |
675 | Optimum o(std::get<0>(result.optimum), ch, -1); | |
676 | if(boundaryCheck(o)) { | |
677 | best_score = result.score; | |
678 | optimum = o; | |
679 | } | |
680 | } | |
681 | 992 | } catch(std::exception& e) { |
682 | 993 | derr() << "ERROR: " << e.what() << "\n"; |
683 | 994 | } |
684 | }); | |
995 | }, policy); | |
996 | ||
997 | auto resultcomp = | |
998 | []( const OptResult& r1, const OptResult& r2 ) { | |
999 | return r1.score < r2.score; | |
1000 | }; | |
1001 | ||
1002 | auto mr = *std::min_element(results.begin(), results.end(), | |
1003 | resultcomp); | |
1004 | ||
1005 | if(mr.score < best_score) { | |
1006 | Optimum o(std::get<0>(mr.optimum), ch, -1); | |
1007 | double miss = boundaryCheck(o); | |
1008 | if(miss <= 0) { | |
1009 | best_score = mr.score; | |
1010 | optimum = o; | |
1011 | } else { | |
1012 | best_overfit = std::min(miss, best_overfit); | |
1013 | } | |
1014 | } | |
685 | 1015 | |
686 | 1016 | for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { |
687 | auto hole_ofn = | |
688 | [&rawobjfunc, &getNfpPoint, ch, hidx] | |
689 | (double pos) | |
1017 | results.clear(); | |
1018 | results.resize(cache.corners(hidx).size()); | |
1019 | ||
1020 | // TODO : use parallel for | |
1021 | __parallel::enumerate(cache.corners(hidx).begin(), | |
1022 | cache.corners(hidx).end(), | |
1023 | [&results, &item, &nfpoint, | |
1024 | &rofn, ch, hidx] | |
1025 | (double pos, size_t n) | |
690 | 1026 | { |
691 | Optimum opt(pos, ch, hidx); | |
692 | return rawobjfunc(getNfpPoint(opt)); | |
693 | }; | |
694 | ||
695 | std::for_each(cache.corners(hidx).begin(), | |
696 | cache.corners(hidx).end(), | |
697 | [&hole_ofn, &solver, &best_score, | |
698 | &optimum, ch, hidx, &boundaryCheck] | |
699 | (double pos) | |
700 | { | |
1027 | Optimizer solver; | |
1028 | ||
1029 | Item itmcpy = item; | |
1030 | auto hole_ofn = | |
1031 | [&rofn, &nfpoint, ch, hidx, &itmcpy] | |
1032 | (double pos) | |
1033 | { | |
1034 | Optimum opt(pos, ch, hidx); | |
1035 | return rofn(nfpoint(opt), itmcpy); | |
1036 | }; | |
1037 | ||
701 | 1038 | try { |
702 | auto result = solver.optimize_min(hole_ofn, | |
1039 | results[n] = solver.optimize_min(hole_ofn, | |
703 | 1040 | opt::initvals<double>(pos), |
704 | 1041 | opt::bound<double>(0, 1.0) |
705 | 1042 | ); |
706 | 1043 | |
707 | if(result.score < best_score) { | |
708 | Optimum o(std::get<0>(result.optimum), | |
709 | ch, hidx); | |
710 | if(boundaryCheck(o)) { | |
711 | best_score = result.score; | |
712 | optimum = o; | |
713 | } | |
714 | } | |
715 | 1044 | } catch(std::exception& e) { |
716 | 1045 | derr() << "ERROR: " << e.what() << "\n"; |
717 | 1046 | } |
718 | }); | |
1047 | }, policy); | |
1048 | ||
1049 | auto hmr = *std::min_element(results.begin(), | |
1050 | results.end(), | |
1051 | resultcomp); | |
1052 | ||
1053 | if(hmr.score < best_score) { | |
1054 | Optimum o(std::get<0>(hmr.optimum), | |
1055 | ch, hidx); | |
1056 | double miss = boundaryCheck(o); | |
1057 | if(miss <= 0.0) { | |
1058 | best_score = hmr.score; | |
1059 | optimum = o; | |
1060 | } else { | |
1061 | best_overfit = std::min(miss, best_overfit); | |
1062 | } | |
1063 | } | |
719 | 1064 | } |
720 | 1065 | } |
721 | 1066 | |
735 | 1080 | |
736 | 1081 | if(can_pack) { |
737 | 1082 | ret = PackResult(item); |
1083 | item_keys_.emplace_back(itemhash); | |
1084 | } else { | |
1085 | ret = PackResult(best_overfit); | |
738 | 1086 | } |
739 | 1087 | |
740 | 1088 | return ret; |
741 | 1089 | } |
742 | 1090 | |
743 | ~_NofitPolyPlacer() { | |
744 | clearItems(); | |
745 | } | |
746 | ||
747 | inline void clearItems() { | |
748 | Nfp::Shapes<RawShape> m; | |
1091 | inline void finalAlign(const RawShape& pbin) { | |
1092 | auto bbin = sl::boundingBox(pbin); | |
1093 | finalAlign(bbin); | |
1094 | } | |
1095 | ||
1096 | inline void finalAlign(_Circle<TPoint<RawShape>> cbin) { | |
1097 | if(items_.empty()) return; | |
1098 | nfp::Shapes<RawShape> m; | |
749 | 1099 | m.reserve(items_.size()); |
750 | ||
751 | 1100 | for(Item& item : items_) m.emplace_back(item.transformedShape()); |
752 | auto&& bb = sl::boundingBox<RawShape>(m); | |
1101 | ||
1102 | auto c = boundingCircle(sl::convexHull(m)); | |
1103 | ||
1104 | auto d = cbin.center() - c.center(); | |
1105 | for(Item& item : items_) item.translate(d); | |
1106 | } | |
1107 | ||
1108 | inline void finalAlign(Box bbin) { | |
1109 | if(items_.empty()) return; | |
1110 | nfp::Shapes<RawShape> m; | |
1111 | m.reserve(items_.size()); | |
1112 | for(Item& item : items_) m.emplace_back(item.transformedShape()); | |
1113 | auto&& bb = sl::boundingBox(m); | |
753 | 1114 | |
754 | 1115 | Vertex ci, cb; |
755 | auto bbin = sl::boundingBox<RawShape>(bin_); | |
756 | 1116 | |
757 | 1117 | switch(config_.alignment) { |
758 | 1118 | case Config::Alignment::CENTER: { |
784 | 1144 | |
785 | 1145 | auto d = cb - ci; |
786 | 1146 | for(Item& item : items_) item.translate(d); |
787 | ||
788 | Base::clearItems(); | |
789 | } | |
790 | ||
791 | private: | |
1147 | } | |
792 | 1148 | |
793 | 1149 | void setInitialPosition(Item& item) { |
794 | 1150 | Box&& bb = item.boundingBox(); |
795 | 1151 | Vertex ci, cb; |
796 | auto bbin = sl::boundingBox<RawShape>(bin_); | |
1152 | auto bbin = sl::boundingBox(bin_); | |
797 | 1153 | |
798 | 1154 | switch(config_.starting_point) { |
799 | 1155 | case Config::Alignment::CENTER: { |
829 | 1185 | |
830 | 1186 | void placeOutsideOfBin(Item& item) { |
831 | 1187 | auto&& bb = item.boundingBox(); |
832 | Box binbb = sl::boundingBox<RawShape>(bin_); | |
1188 | Box binbb = sl::boundingBox(bin_); | |
833 | 1189 | |
834 | 1190 | Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) }; |
835 | 1191 |
2 | 2 | |
3 | 3 | #include "../libnest2d.hpp" |
4 | 4 | |
5 | namespace libnest2d { namespace strategies { | |
5 | namespace libnest2d { namespace placers { | |
6 | 6 | |
7 | 7 | struct EmptyConfig {}; |
8 | 8 | |
9 | template<class Subclass, class RawShape, class TBin, | |
10 | class Cfg = EmptyConfig, | |
11 | class Store = std::vector<std::reference_wrapper<_Item<RawShape>>> | |
12 | > | |
9 | template<class Subclass, class RawShape, class TBin, class Cfg = EmptyConfig> | |
13 | 10 | class PlacerBoilerplate { |
14 | 11 | mutable bool farea_valid_ = false; |
15 | 12 | mutable double farea_ = 0.0; |
21 | 18 | using Coord = TCoord<Vertex>; |
22 | 19 | using Unit = Coord; |
23 | 20 | using Config = Cfg; |
24 | using Container = Store; | |
21 | using ItemGroup = _ItemGroup<Item>; | |
22 | using DefaultIter = typename ItemGroup::const_iterator; | |
25 | 23 | |
26 | 24 | class PackResult { |
27 | 25 | Item *item_ptr_; |
28 | 26 | Vertex move_; |
29 | 27 | Radians rot_; |
28 | double overfit_; | |
30 | 29 | friend class PlacerBoilerplate; |
31 | 30 | friend Subclass; |
31 | ||
32 | 32 | PackResult(Item& item): |
33 | 33 | item_ptr_(&item), |
34 | 34 | move_(item.translation()), |
35 | 35 | rot_(item.rotation()) {} |
36 | PackResult(): item_ptr_(nullptr) {} | |
36 | ||
37 | PackResult(double overfit = 1.0): | |
38 | item_ptr_(nullptr), overfit_(overfit) {} | |
39 | ||
37 | 40 | public: |
38 | 41 | operator bool() { return item_ptr_ != nullptr; } |
42 | double overfit() const { return overfit_; } | |
39 | 43 | }; |
40 | ||
41 | using ItemGroup = const Container&; | |
42 | 44 | |
43 | 45 | inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin) |
44 | 46 | { |
55 | 57 | config_ = config; |
56 | 58 | } |
57 | 59 | |
58 | bool pack(Item& item) { | |
59 | auto&& r = static_cast<Subclass*>(this)->trypack(item); | |
60 | template<class Range = ConstItemRange<DefaultIter>> | |
61 | bool pack(Item& item, | |
62 | const Range& rem = Range()) { | |
63 | auto&& r = static_cast<Subclass*>(this)->trypack(item, rem); | |
60 | 64 | if(r) { |
61 | 65 | items_.push_back(*(r.item_ptr_)); |
62 | 66 | farea_valid_ = false; |
78 | 82 | farea_valid_ = false; |
79 | 83 | } |
80 | 84 | |
81 | inline ItemGroup getItems() const { return items_; } | |
85 | inline const ItemGroup& getItems() const { return items_; } | |
82 | 86 | |
83 | 87 | inline void clearItems() { |
84 | 88 | items_.clear(); |
85 | 89 | farea_valid_ = false; |
86 | #ifndef NDEBUG | |
87 | debug_items_.clear(); | |
88 | #endif | |
89 | 90 | } |
90 | 91 | |
91 | 92 | inline double filledArea() const { |
102 | 103 | return farea_; |
103 | 104 | } |
104 | 105 | |
105 | #ifndef NDEBUG | |
106 | std::vector<Item> debug_items_; | |
107 | #endif | |
108 | ||
109 | 106 | protected: |
110 | 107 | |
111 | 108 | BinType bin_; |
112 | Container items_; | |
109 | ItemGroup items_; | |
113 | 110 | Cfg config_; |
114 | 111 | }; |
115 | 112 | |
120 | 117 | using Base::config_; \ |
121 | 118 | public: \ |
122 | 119 | using typename Base::Item; \ |
120 | using typename Base::ItemGroup; \ | |
123 | 121 | using typename Base::BinType; \ |
124 | 122 | using typename Base::Config; \ |
125 | 123 | using typename Base::Vertex; \ |
127 | 125 | using typename Base::PackResult; \ |
128 | 126 | using typename Base::Coord; \ |
129 | 127 | using typename Base::Unit; \ |
130 | using typename Base::Container; \ | |
131 | 128 | private: |
132 | 129 | |
133 | 130 | } |
0 | #ifndef ROTFINDER_HPP | |
1 | #define ROTFINDER_HPP | |
2 | ||
3 | #include <libnest2d/libnest2d.hpp> | |
4 | #include <libnest2d/optimizer.hpp> | |
5 | #include <iterator> | |
6 | ||
7 | namespace libnest2d { | |
8 | ||
9 | template<class RawShape> | |
10 | Radians findBestRotation(_Item<RawShape>& item) { | |
11 | opt::StopCriteria stopcr; | |
12 | stopcr.absolute_score_difference = 0.01; | |
13 | stopcr.max_iterations = 10000; | |
14 | opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr); | |
15 | ||
16 | auto orig_rot = item.rotation(); | |
17 | ||
18 | auto result = solver.optimize_min([&item, &orig_rot](Radians rot){ | |
19 | item.rotation(orig_rot + rot); | |
20 | auto bb = item.boundingBox(); | |
21 | return std::sqrt(bb.height()*bb.width()); | |
22 | }, opt::initvals(Radians(0)), opt::bound<Radians>(-Pi/2, Pi/2)); | |
23 | ||
24 | item.rotation(orig_rot); | |
25 | ||
26 | return std::get<0>(result.optimum); | |
27 | } | |
28 | ||
29 | template<class Iterator> | |
30 | void findMinimumBoundingBoxRotations(Iterator from, Iterator to) { | |
31 | using V = typename std::iterator_traits<Iterator>::value_type; | |
32 | std::for_each(from, to, [](V& item){ | |
33 | Radians rot = findBestRotation(item); | |
34 | item.rotate(rot); | |
35 | }); | |
36 | } | |
37 | ||
38 | } | |
39 | ||
40 | #endif // ROTFINDER_HPP |
7 | 7 | |
8 | 8 | #include "selection_boilerplate.hpp" |
9 | 9 | |
10 | namespace libnest2d { namespace strategies { | |
10 | namespace libnest2d { namespace selections { | |
11 | 11 | |
12 | 12 | /** |
13 | 13 | * Selection heuristic based on [López-Camacho]\ |
117 | 117 | using Placer = PlacementStrategyLike<TPlacer>; |
118 | 118 | using ItemList = std::list<ItemRef>; |
119 | 119 | |
120 | const double bin_area = ShapeLike::area<RawShape>(bin); | |
120 | const double bin_area = sl::area(bin); | |
121 | 121 | const double w = bin_area * config_.waste_increment; |
122 | 122 | |
123 | 123 | const double INITIAL_FILL_PROPORTION = config_.initial_fill_proportion; |
226 | 226 | bool ret = false; |
227 | 227 | auto it = not_packed.begin(); |
228 | 228 | |
229 | auto pack = [&placer, ¬_packed](ItemListIt it) { | |
230 | return placer.pack(*it, rem(it, not_packed)); | |
231 | }; | |
232 | ||
229 | 233 | while(it != not_packed.end() && !ret && |
230 | 234 | free_area - (item_area = it->get().area()) <= waste) |
231 | 235 | { |
232 | if(item_area <= free_area && placer.pack(*it) ) { | |
236 | if(item_area <= free_area && pack(it) ) { | |
233 | 237 | free_area -= item_area; |
234 | 238 | filled_area = bin_area - free_area; |
235 | 239 | ret = true; |
269 | 273 | auto it2 = it; |
270 | 274 | |
271 | 275 | std::vector<TPair> wrong_pairs; |
276 | using std::placeholders::_1; | |
277 | ||
278 | auto trypack = [&placer, ¬_packed](ItemListIt it) { | |
279 | return placer.trypack(*it, rem(it, not_packed)); | |
280 | }; | |
272 | 281 | |
273 | 282 | while(it != endit && !ret && |
274 | 283 | free_area - (item_area = it->get().area()) - |
277 | 286 | if(item_area + smallestPiece(it, not_packed)->get().area() > |
278 | 287 | free_area ) { it++; continue; } |
279 | 288 | |
280 | auto pr = placer.trypack(*it); | |
289 | auto pr = trypack(it); | |
281 | 290 | |
282 | 291 | // First would fit |
283 | 292 | it2 = not_packed.begin(); |
293 | 302 | } |
294 | 303 | |
295 | 304 | placer.accept(pr); |
296 | auto pr2 = placer.trypack(*it2); | |
305 | auto pr2 = trypack(it2); | |
297 | 306 | if(!pr2) { |
298 | 307 | placer.unpackLast(); // remove first |
299 | 308 | if(try_reverse) { |
300 | pr2 = placer.trypack(*it2); | |
309 | pr2 = trypack(it2); | |
301 | 310 | if(pr2) { |
302 | 311 | placer.accept(pr2); |
303 | auto pr12 = placer.trypack(*it); | |
312 | auto pr12 = trypack(it); | |
304 | 313 | if(pr12) { |
305 | 314 | placer.accept(pr12); |
306 | 315 | ret = true; |
364 | 373 | return it->get().area(); |
365 | 374 | }; |
366 | 375 | |
376 | auto trypack = [&placer, ¬_packed](ItemListIt it) { | |
377 | return placer.trypack(*it, rem(it, not_packed)); | |
378 | }; | |
379 | ||
380 | auto pack = [&placer, ¬_packed](ItemListIt it) { | |
381 | return placer.pack(*it, rem(it, not_packed)); | |
382 | }; | |
383 | ||
367 | 384 | while (it != endit && !ret) { // drill down 1st level |
368 | 385 | |
369 | 386 | // We need to determine in each iteration the largest, second |
393 | 410 | it++; continue; |
394 | 411 | } |
395 | 412 | |
396 | auto pr = placer.trypack(*it); | |
413 | auto pr = trypack(it); | |
397 | 414 | |
398 | 415 | // Check for free area and try to pack the 1st item... |
399 | 416 | if(!pr) { it++; continue; } |
419 | 436 | bool can_pack2 = false; |
420 | 437 | |
421 | 438 | placer.accept(pr); |
422 | auto pr2 = placer.trypack(*it2); | |
439 | auto pr2 = trypack(it2); | |
423 | 440 | auto pr12 = pr; |
424 | 441 | if(!pr2) { |
425 | 442 | placer.unpackLast(); // remove first |
426 | 443 | if(try_reverse) { |
427 | pr2 = placer.trypack(*it2); | |
444 | pr2 = trypack(it2); | |
428 | 445 | if(pr2) { |
429 | 446 | placer.accept(pr2); |
430 | pr12 = placer.trypack(*it); | |
447 | pr12 = trypack(it); | |
431 | 448 | if(pr12) can_pack2 = true; |
432 | 449 | placer.unpackLast(); |
433 | 450 | } |
462 | 479 | if(a3_sum > free_area) { it3++; continue; } |
463 | 480 | |
464 | 481 | placer.accept(pr12); placer.accept(pr2); |
465 | bool can_pack3 = placer.pack(*it3); | |
482 | bool can_pack3 = pack(it3); | |
466 | 483 | |
467 | 484 | if(!can_pack3) { |
468 | 485 | placer.unpackLast(); |
472 | 489 | if(!can_pack3 && try_reverse) { |
473 | 490 | |
474 | 491 | std::array<size_t, 3> indices = {0, 1, 2}; |
475 | std::array<ItemRef, 3> | |
476 | candidates = {*it, *it2, *it3}; | |
477 | ||
478 | auto tryPack = [&placer, &candidates]( | |
492 | std::array<typename ItemList::iterator, 3> | |
493 | candidates = {it, it2, it3}; | |
494 | ||
495 | auto tryPack = [&placer, &candidates, &pack]( | |
479 | 496 | const decltype(indices)& idx) |
480 | 497 | { |
481 | 498 | std::array<bool, 3> packed = {false}; |
482 | 499 | |
483 | 500 | for(auto id : idx) packed.at(id) = |
484 | placer.pack(candidates[id]); | |
501 | pack(candidates[id]); | |
485 | 502 | |
486 | 503 | bool check = |
487 | 504 | std::all_of(packed.begin(), |
535 | 552 | { auto it = store_.begin(); |
536 | 553 | while (it != store_.end()) { |
537 | 554 | Placer p(bin); p.configure(pconfig); |
538 | if(!p.pack(*it)) { | |
555 | if(!p.pack(*it, rem(it, store_))) { | |
539 | 556 | it = store_.erase(it); |
540 | 557 | } else it++; |
541 | 558 | } |
550 | 567 | { |
551 | 568 | |
552 | 569 | packed_bins_[idx] = placer.getItems(); |
553 | #ifndef NDEBUG | |
554 | packed_bins_[idx].insert(packed_bins_[idx].end(), | |
555 | placer.getDebugItems().begin(), | |
556 | placer.getDebugItems().end()); | |
557 | #endif | |
570 | ||
558 | 571 | // TODO here should be a spinlock |
559 | 572 | slock.lock(); |
560 | 573 | acounter -= packednum; |
600 | 613 | while(it != not_packed.end() && |
601 | 614 | filled_area < INITIAL_FILL_AREA) |
602 | 615 | { |
603 | if(placer.pack(*it)) { | |
616 | if(placer.pack(*it, rem(it, not_packed))) { | |
604 | 617 | filled_area += it->get().area(); |
605 | 618 | free_area = bin_area - filled_area; |
606 | 619 | it = not_packed.erase(it); |
2 | 2 | |
3 | 3 | #include "selection_boilerplate.hpp" |
4 | 4 | |
5 | namespace libnest2d { namespace strategies { | |
5 | namespace libnest2d { namespace selections { | |
6 | 6 | |
7 | 7 | template<class RawShape> |
8 | 8 | class _FillerSelection: public SelectionBoilerplate<RawShape> { |
55 | 55 | |
56 | 56 | std::sort(store_.begin(), store_.end(), sortfunc); |
57 | 57 | |
58 | // Container a = {store_[0], store_[1], store_[4], store_[5] }; | |
59 | //// a.insert(a.end(), store_.end()-10, store_.end()); | |
60 | // store_ = a; | |
61 | ||
62 | 58 | PlacementStrategyLike<TPlacer> placer(bin); |
63 | 59 | placer.configure(pconfig); |
64 | 60 | |
65 | 61 | auto it = store_.begin(); |
66 | 62 | while(it != store_.end()) { |
67 | if(!placer.pack(*it)) { | |
63 | if(!placer.pack(*it, {std::next(it), store_.end()})) { | |
68 | 64 | if(packed_bins_.back().empty()) ++it; |
69 | // makeProgress(placer); | |
70 | 65 | placer.clearItems(); |
71 | 66 | packed_bins_.emplace_back(); |
72 | 67 | } else { |
75 | 70 | } |
76 | 71 | } |
77 | 72 | |
78 | // if(was_packed) { | |
79 | // packed_bins_.push_back(placer.getItems()); | |
80 | // } | |
81 | 73 | } |
82 | 74 | }; |
83 | 75 |
3 | 3 | #include "../libnest2d.hpp" |
4 | 4 | #include "selection_boilerplate.hpp" |
5 | 5 | |
6 | namespace libnest2d { namespace strategies { | |
6 | namespace libnest2d { namespace selections { | |
7 | 7 | |
8 | 8 | template<class RawShape> |
9 | 9 | class _FirstFitSelection: public SelectionBoilerplate<RawShape> { |
39 | 39 | packed_bins_.clear(); |
40 | 40 | |
41 | 41 | std::vector<Placer> placers; |
42 | placers.reserve(last-first); | |
42 | 43 | |
43 | 44 | std::copy(first, last, std::back_inserter(store_)); |
44 | 45 | |
65 | 66 | } |
66 | 67 | } |
67 | 68 | |
68 | for(auto& item : store_ ) { | |
69 | auto it = store_.begin(); | |
70 | ||
71 | while(it != store_.end()) { | |
69 | 72 | bool was_packed = false; |
73 | size_t j = 0; | |
70 | 74 | while(!was_packed) { |
71 | ||
72 | for(size_t j = 0; j < placers.size() && !was_packed; j++) { | |
73 | if((was_packed = placers[j].pack(item))) | |
74 | makeProgress(placers[j], j); | |
75 | for(; j < placers.size() && !was_packed; j++) { | |
76 | if((was_packed = placers[j].pack(*it, rem(it, store_) ))) | |
77 | makeProgress(placers[j], j); | |
75 | 78 | } |
76 | 79 | |
77 | 80 | if(!was_packed) { |
78 | 81 | placers.emplace_back(bin); |
79 | 82 | placers.back().configure(pconfig); |
80 | 83 | packed_bins_.emplace_back(); |
84 | j = placers.size() - 1; | |
81 | 85 | } |
82 | 86 | } |
87 | ++it; | |
83 | 88 | } |
84 | 89 | } |
85 | 90 |
2 | 2 | |
3 | 3 | #include "../libnest2d.hpp" |
4 | 4 | |
5 | namespace libnest2d { | |
6 | namespace strategies { | |
5 | namespace libnest2d { namespace selections { | |
7 | 6 | |
8 | 7 | template<class RawShape> |
9 | 8 | class SelectionBoilerplate { |
21 | 21 | using Coord = TCoord<PointImpl>; |
22 | 22 | using Box = _Box<PointImpl>; |
23 | 23 | using Segment = _Segment<PointImpl>; |
24 | using Circle = _Circle<PointImpl>; | |
24 | 25 | |
25 | 26 | using Item = _Item<PolygonImpl>; |
26 | 27 | using Rectangle = _Rectangle<PolygonImpl>; |
28 | 29 | using PackGroup = _PackGroup<PolygonImpl>; |
29 | 30 | using IndexedPackGroup = _IndexedPackGroup<PolygonImpl>; |
30 | 31 | |
31 | using FillerSelection = strategies::_FillerSelection<PolygonImpl>; | |
32 | using FirstFitSelection = strategies::_FirstFitSelection<PolygonImpl>; | |
33 | using DJDHeuristic = strategies::_DJDHeuristic<PolygonImpl>; | |
32 | using FillerSelection = selections::_FillerSelection<PolygonImpl>; | |
33 | using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>; | |
34 | using DJDHeuristic = selections::_DJDHeuristic<PolygonImpl>; | |
34 | 35 | |
35 | using NfpPlacer = strategies::_NofitPolyPlacer<PolygonImpl>; | |
36 | using BottomLeftPlacer = strategies::_BottomLeftPlacer<PolygonImpl>; | |
37 | ||
38 | //template<NfpLevel lvl = NfpLevel::BOTH_CONCAVE_WITH_HOLES> | |
39 | //using NofitPolyPlacer = strategies::_NofitPolyPlacer<PolygonImpl, lvl>; | |
36 | using NfpPlacer = placers::_NofitPolyPlacer<PolygonImpl>; | |
37 | using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>; | |
40 | 38 | |
41 | 39 | } |
42 | 40 |
4 | 4 | #include "printer_parts.h" |
5 | 5 | #include <libnest2d/geometry_traits_nfp.hpp> |
6 | 6 | //#include "../tools/libnfpglue.hpp" |
7 | //#include "../tools/nfp_svgnest_glue.hpp" | |
7 | 8 | |
8 | 9 | std::vector<libnest2d::Item>& prusaParts() { |
9 | 10 | static std::vector<libnest2d::Item> ret; |
98 | 99 | |
99 | 100 | } |
100 | 101 | |
102 | TEST(GeometryAlgorithms, boundingCircle) { | |
103 | using namespace libnest2d; | |
104 | using placers::boundingCircle; | |
105 | ||
106 | PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; | |
107 | Circle c = boundingCircle(p); | |
108 | ||
109 | ASSERT_EQ(c.center().X, 0); | |
110 | ASSERT_EQ(c.center().Y, 0); | |
111 | ASSERT_DOUBLE_EQ(c.radius(), 10); | |
112 | ||
113 | shapelike::translate(p, PointImpl{10, 10}); | |
114 | c = boundingCircle(p); | |
115 | ||
116 | ASSERT_EQ(c.center().X, 10); | |
117 | ASSERT_EQ(c.center().Y, 10); | |
118 | ASSERT_DOUBLE_EQ(c.radius(), 10); | |
119 | ||
120 | auto parts = prusaParts(); | |
121 | ||
122 | int i = 0; | |
123 | for(auto& part : parts) { | |
124 | c = boundingCircle(part.transformedShape()); | |
125 | if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; | |
126 | ||
127 | else for(auto v : shapelike::getContour(part.transformedShape()) ) { | |
128 | auto d = pointlike::distance(v, c.center()); | |
129 | if(d > c.radius() ) { | |
130 | auto e = std::abs( 1.0 - d/c.radius()); | |
131 | ASSERT_LE(e, 1e-3); | |
132 | } | |
133 | } | |
134 | i++; | |
135 | } | |
136 | ||
137 | } | |
138 | ||
101 | 139 | TEST(GeometryAlgorithms, Distance) { |
102 | 140 | using namespace libnest2d; |
103 | 141 | |
106 | 144 | Point p2 = {10, 0}; |
107 | 145 | Point p3 = {10, 10}; |
108 | 146 | |
109 | ASSERT_DOUBLE_EQ(PointLike::distance(p1, p2), 10); | |
110 | ASSERT_DOUBLE_EQ(PointLike::distance(p1, p3), sqrt(200)); | |
147 | ASSERT_DOUBLE_EQ(pointlike::distance(p1, p2), 10); | |
148 | ASSERT_DOUBLE_EQ(pointlike::distance(p1, p3), sqrt(200)); | |
111 | 149 | |
112 | 150 | Segment seg(p1, p3); |
113 | 151 | |
114 | ASSERT_DOUBLE_EQ(PointLike::distance(p2, seg), 7.0710678118654755); | |
115 | ||
116 | auto result = PointLike::horizontalDistance(p2, seg); | |
152 | ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); | |
153 | ||
154 | auto result = pointlike::horizontalDistance(p2, seg); | |
117 | 155 | |
118 | 156 | auto check = [](Coord val, Coord expected) { |
119 | 157 | if(std::is_floating_point<Coord>::value) |
126 | 164 | ASSERT_TRUE(result.second); |
127 | 165 | check(result.first, 10); |
128 | 166 | |
129 | result = PointLike::verticalDistance(p2, seg); | |
167 | result = pointlike::verticalDistance(p2, seg); | |
130 | 168 | ASSERT_TRUE(result.second); |
131 | 169 | check(result.first, -10); |
132 | 170 | |
133 | result = PointLike::verticalDistance(Point{10, 20}, seg); | |
171 | result = pointlike::verticalDistance(Point{10, 20}, seg); | |
134 | 172 | ASSERT_TRUE(result.second); |
135 | 173 | check(result.first, 10); |
136 | 174 | |
138 | 176 | Point p4 = {80, 0}; |
139 | 177 | Segment seg2 = { {0, 0}, {0, 40} }; |
140 | 178 | |
141 | result = PointLike::horizontalDistance(p4, seg2); | |
179 | result = pointlike::horizontalDistance(p4, seg2); | |
142 | 180 | |
143 | 181 | ASSERT_TRUE(result.second); |
144 | 182 | check(result.first, 80); |
145 | 183 | |
146 | result = PointLike::verticalDistance(p4, seg2); | |
184 | result = pointlike::verticalDistance(p4, seg2); | |
147 | 185 | // Point should not be related to the segment |
148 | 186 | ASSERT_FALSE(result.second); |
149 | 187 | |
171 | 209 | {61, 97} |
172 | 210 | }; |
173 | 211 | |
174 | ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 ); | |
212 | ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 ); | |
175 | 213 | } |
176 | 214 | |
177 | 215 | TEST(GeometryAlgorithms, IsPointInsidePolygon) { |
181 | 219 | |
182 | 220 | Point p = {1, 1}; |
183 | 221 | |
184 | ASSERT_TRUE(rect.isPointInside(p)); | |
222 | ASSERT_TRUE(rect.isInside(p)); | |
185 | 223 | |
186 | 224 | p = {11, 11}; |
187 | 225 | |
188 | ASSERT_FALSE(rect.isPointInside(p)); | |
226 | ASSERT_FALSE(rect.isInside(p)); | |
189 | 227 | |
190 | 228 | |
191 | 229 | p = {11, 12}; |
192 | 230 | |
193 | ASSERT_FALSE(rect.isPointInside(p)); | |
231 | ASSERT_FALSE(rect.isInside(p)); | |
194 | 232 | |
195 | 233 | |
196 | 234 | p = {3, 3}; |
197 | 235 | |
198 | ASSERT_TRUE(rect.isPointInside(p)); | |
236 | ASSERT_TRUE(rect.isInside(p)); | |
199 | 237 | |
200 | 238 | } |
201 | 239 | |
249 | 287 | |
250 | 288 | Item leftp(placer.leftPoly(item)); |
251 | 289 | |
252 | ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first); | |
290 | ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first); | |
253 | 291 | ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount()); |
254 | 292 | |
255 | 293 | for(unsigned long i = 0; i < leftControl.vertexCount(); i++) { |
259 | 297 | |
260 | 298 | Item downp(placer.downPoly(item)); |
261 | 299 | |
262 | ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first); | |
300 | ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first); | |
263 | 301 | ASSERT_EQ(downp.vertexCount(), downControl.vertexCount()); |
264 | 302 | |
265 | 303 | for(unsigned long i = 0; i < downControl.vertexCount(); i++) { |
296 | 334 | {20, 20} }; |
297 | 335 | |
298 | 336 | |
299 | Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250)); | |
337 | Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250)); | |
300 | 338 | |
301 | 339 | auto groups = arrange(rects.begin(), rects.end()); |
302 | 340 | |
349 | 387 | |
350 | 388 | Coord min_obj_distance = 5; |
351 | 389 | |
352 | Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250), | |
390 | Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250), | |
353 | 391 | min_obj_distance); |
354 | 392 | |
355 | 393 | auto groups = arrange(rects.begin(), rects.end()); |
400 | 438 | setX(v, getX(v)/SCALE); |
401 | 439 | rbin.setVertex(i, v); |
402 | 440 | } |
403 | out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl; | |
441 | out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl; | |
404 | 442 | for(Item& sh : r) { |
405 | 443 | Item tsh(sh.transformedShape()); |
406 | 444 | for(unsigned i = 0; i < tsh.vertexCount(); i++) { |
409 | 447 | setX(v, getX(v)/SCALE); |
410 | 448 | tsh.setVertex(i, v); |
411 | 449 | } |
412 | out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl; | |
450 | out << shapelike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl; | |
413 | 451 | } |
414 | 452 | out << "\n</svg>" << std::endl; |
415 | 453 | } |
663 | 701 | } |
664 | 702 | }; |
665 | 703 | |
666 | template<NfpLevel lvl, Coord SCALE> | |
704 | template<nfp::NfpLevel lvl, Coord SCALE> | |
667 | 705 | void testNfp(const std::vector<ItemPair>& testdata) { |
668 | 706 | using namespace libnest2d; |
669 | 707 | |
673 | 711 | |
674 | 712 | auto& exportfun = exportSVG<SCALE, Box>; |
675 | 713 | |
676 | auto onetest = [&](Item& orbiter, Item& stationary){ | |
714 | auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){ | |
677 | 715 | testcase++; |
678 | 716 | |
679 | 717 | orbiter.translate({210*SCALE, 0}); |
680 | 718 | |
681 | auto&& nfp = Nfp::noFitPolygon<lvl>(stationary.rawShape(), | |
719 | auto&& nfp = nfp::noFitPolygon<lvl>(stationary.rawShape(), | |
682 | 720 | orbiter.transformedShape()); |
683 | 721 | |
684 | strategies::correctNfpPosition(nfp, stationary, orbiter); | |
685 | ||
686 | auto v = ShapeLike::isValid(nfp.first); | |
687 | ||
688 | if(!v.first) { | |
689 | std::cout << v.second << std::endl; | |
690 | } | |
691 | ||
692 | ASSERT_TRUE(v.first); | |
722 | placers::correctNfpPosition(nfp, stationary, orbiter); | |
723 | ||
724 | auto valid = shapelike::isValid(nfp.first); | |
725 | ||
726 | /*Item infp(nfp.first); | |
727 | if(!valid.first) { | |
728 | std::cout << "test instance: " << testidx << " " | |
729 | << valid.second << std::endl; | |
730 | std::vector<std::reference_wrapper<Item>> inp = {std::ref(infp)}; | |
731 | exportfun(inp, bin, testidx); | |
732 | }*/ | |
733 | ||
734 | ASSERT_TRUE(valid.first); | |
693 | 735 | |
694 | 736 | Item infp(nfp.first); |
695 | 737 | |
696 | 738 | int i = 0; |
697 | 739 | auto rorbiter = orbiter.transformedShape(); |
698 | auto vo = Nfp::referenceVertex(rorbiter); | |
740 | auto vo = nfp::referenceVertex(rorbiter); | |
699 | 741 | |
700 | 742 | ASSERT_TRUE(stationary.isInside(infp)); |
701 | 743 | |
709 | 751 | |
710 | 752 | bool touching = Item::touches(tmp, stationary); |
711 | 753 | |
712 | if(!touching) { | |
754 | if(!touching || !valid.first) { | |
713 | 755 | std::vector<std::reference_wrapper<Item>> inp = { |
714 | 756 | std::ref(stationary), std::ref(tmp), std::ref(infp) |
715 | 757 | }; |
721 | 763 | } |
722 | 764 | }; |
723 | 765 | |
766 | unsigned tidx = 0; | |
724 | 767 | for(auto& td : testdata) { |
725 | 768 | auto orbiter = td.orbiter; |
726 | 769 | auto stationary = td.stationary; |
727 | onetest(orbiter, stationary); | |
728 | } | |
729 | ||
770 | onetest(orbiter, stationary, tidx++); | |
771 | } | |
772 | ||
773 | tidx = 0; | |
730 | 774 | for(auto& td : testdata) { |
731 | 775 | auto orbiter = td.stationary; |
732 | 776 | auto stationary = td.orbiter; |
733 | onetest(orbiter, stationary); | |
777 | onetest(orbiter, stationary, tidx++); | |
734 | 778 | } |
735 | 779 | } |
736 | 780 | } |
737 | 781 | |
738 | 782 | TEST(GeometryAlgorithms, nfpConvexConvex) { |
739 | testNfp<NfpLevel::CONVEX_ONLY, 1>(nfp_testdata); | |
783 | testNfp<nfp::NfpLevel::CONVEX_ONLY, 1>(nfp_testdata); | |
740 | 784 | } |
741 | 785 | |
742 | 786 | //TEST(GeometryAlgorithms, nfpConcaveConcave) { |
757 | 801 | |
758 | 802 | Rectangle input(10, 10); |
759 | 803 | |
760 | strategies::EdgeCache<PolygonImpl> ecache(input); | |
804 | placers::EdgeCache<PolygonImpl> ecache(input); | |
761 | 805 | |
762 | 806 | auto first = *input.begin(); |
763 | 807 | ASSERT_TRUE(getX(first) == getX(ecache.coords(0))); |
769 | 813 | |
770 | 814 | for(int i = 0; i <= 100; i++) { |
771 | 815 | auto v = ecache.coords(i*(0.01)); |
772 | ASSERT_TRUE(ShapeLike::touches(v, input.transformedShape())); | |
816 | ASSERT_TRUE(shapelike::touches(v, input.transformedShape())); | |
773 | 817 | } |
774 | 818 | } |
775 | 819 | |
783 | 827 | rect2.translate({10, 0}); |
784 | 828 | rect3.translate({25, 0}); |
785 | 829 | |
786 | ShapeLike::Shapes<PolygonImpl> pile; | |
830 | shapelike::Shapes<PolygonImpl> pile; | |
787 | 831 | pile.push_back(rect1.transformedShape()); |
788 | 832 | pile.push_back(rect2.transformedShape()); |
789 | 833 | |
790 | auto result = Nfp::merge(pile, rect3.transformedShape()); | |
834 | auto result = nfp::merge(pile, rect3.transformedShape()); | |
791 | 835 | |
792 | 836 | ASSERT_EQ(result.size(), 1); |
793 | 837 | |
794 | 838 | Rectangle ref(45, 15); |
795 | 839 | |
796 | ASSERT_EQ(ShapeLike::area(result.front()), ref.area()); | |
840 | ASSERT_EQ(shapelike::area(result.front()), ref.area()); | |
797 | 841 | } |
798 | 842 | |
799 | 843 | int main(int argc, char **argv) { |
55 | 55 | |
56 | 56 | NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) |
57 | 57 | { |
58 | using Vertex = PointImpl; | |
58 | namespace sl = shapelike; | |
59 | 59 | |
60 | 60 | NfpR ret; |
61 | 61 | |
84 | 84 | // this can throw |
85 | 85 | auto nfp = libnfporb::generateNFP(pstat, porb, true); |
86 | 86 | |
87 | auto &ct = ShapeLike::getContour(ret.first); | |
87 | auto &ct = sl::getContour(ret.first); | |
88 | 88 | ct.reserve(nfp.front().size()+1); |
89 | 89 | for(auto v : nfp.front()) { |
90 | 90 | v = scale(v, refactor); |
93 | 93 | ct.push_back(ct.front()); |
94 | 94 | std::reverse(ct.begin(), ct.end()); |
95 | 95 | |
96 | auto &rholes = ShapeLike::holes(ret.first); | |
96 | auto &rholes = sl::holes(ret.first); | |
97 | 97 | for(size_t hidx = 1; hidx < nfp.size(); ++hidx) { |
98 | 98 | if(nfp[hidx].size() >= 3) { |
99 | 99 | rholes.emplace_back(); |
109 | 109 | } |
110 | 110 | } |
111 | 111 | |
112 | ret.second = Nfp::referenceVertex(ret.first); | |
112 | ret.second = nfp::referenceVertex(ret.first); | |
113 | 113 | |
114 | 114 | } catch(std::exception& e) { |
115 | 115 | std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl; |
116 | 116 | // auto ch_stat = ShapeLike::convexHull(sh); |
117 | 117 | // auto ch_orb = ShapeLike::convexHull(cother); |
118 | ret = Nfp::nfpConvexOnly(sh, cother); | |
118 | ret = nfp::nfpConvexOnly(sh, cother); | |
119 | 119 | } |
120 | 120 | |
121 | 121 | return ret; |
122 | 122 | } |
123 | 123 | |
124 | NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()( | |
124 | NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY>::operator()( | |
125 | 125 | const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) |
126 | 126 | { |
127 | 127 | return _nfp(sh, cother);//nfpConvexOnly(sh, cother); |
128 | 128 | } |
129 | 129 | |
130 | NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()( | |
130 | NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX>::operator()( | |
131 | 131 | const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) |
132 | 132 | { |
133 | 133 | return _nfp(sh, cother); |
134 | 134 | } |
135 | 135 | |
136 | NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()( | |
136 | NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE>::operator()( | |
137 | 137 | const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) |
138 | 138 | { |
139 | 139 | return _nfp(sh, cother); |
4 | 4 | |
5 | 5 | namespace libnest2d { |
6 | 6 | |
7 | using NfpR = Nfp::NfpResult<PolygonImpl>; | |
7 | using NfpR = nfp::NfpResult<PolygonImpl>; | |
8 | 8 | |
9 | 9 | NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother); |
10 | 10 | |
11 | 11 | template<> |
12 | struct Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> { | |
12 | struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY> { | |
13 | 13 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); |
14 | 14 | }; |
15 | 15 | |
16 | 16 | template<> |
17 | struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> { | |
17 | struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX> { | |
18 | 18 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); |
19 | 19 | }; |
20 | 20 | |
21 | 21 | template<> |
22 | struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> { | |
22 | struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE> { | |
23 | 23 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); |
24 | 24 | }; |
25 | 25 | |
33 | 33 | // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); |
34 | 34 | //}; |
35 | 35 | |
36 | template<> struct Nfp::MaxNfpLevel<PolygonImpl> { | |
36 | template<> struct nfp::MaxNfpLevel<PolygonImpl> { | |
37 | 37 | static const BP2D_CONSTEXPR NfpLevel value = |
38 | 38 | // NfpLevel::CONVEX_ONLY; |
39 | 39 | NfpLevel::BOTH_CONCAVE; |
0 | #ifndef NFP_SVGNEST_HPP | |
1 | #define NFP_SVGNEST_HPP | |
2 | ||
3 | #include <limits> | |
4 | #include <unordered_map> | |
5 | ||
6 | #include <libnest2d/geometry_traits_nfp.hpp> | |
7 | ||
8 | namespace libnest2d { | |
9 | ||
10 | namespace __svgnest { | |
11 | ||
12 | using std::sqrt; | |
13 | using std::min; | |
14 | using std::max; | |
15 | using std::abs; | |
16 | using std::isnan; | |
17 | ||
18 | //template<class Coord> struct _Scale { | |
19 | // static const BP2D_CONSTEXPR long long Value = 1000000; | |
20 | //}; | |
21 | ||
22 | template<class S> struct _alg { | |
23 | using Contour = TContour<S>; | |
24 | using Point = TPoint<S>; | |
25 | using iCoord = TCoord<Point>; | |
26 | using Coord = double; | |
27 | using Shapes = nfp::Shapes<S>; | |
28 | ||
29 | static const Coord TOL; | |
30 | ||
31 | #define dNAN std::nan("") | |
32 | ||
33 | struct Vector { | |
34 | Coord x = 0.0, y = 0.0; | |
35 | bool marked = false; | |
36 | Vector() = default; | |
37 | Vector(Coord X, Coord Y): x(X), y(Y) {} | |
38 | Vector(const Point& p): x(Coord(getX(p))), y(Coord(getY(p))) {} | |
39 | operator Point() const { return {iCoord(x), iCoord(y)}; } | |
40 | Vector& operator=(const Point& p) { | |
41 | x = getX(p), y = getY(p); return *this; | |
42 | } | |
43 | bool operator!=(const Vector& v) const { | |
44 | return v.x != x || v.y != y; | |
45 | } | |
46 | Vector(std::initializer_list<Coord> il): | |
47 | x(*il.begin()), y(*std::next(il.begin())) {} | |
48 | }; | |
49 | ||
50 | static inline Coord x(const Point& p) { return Coord(getX(p)); } | |
51 | static inline Coord y(const Point& p) { return Coord(getY(p)); } | |
52 | ||
53 | static inline Coord x(const Vector& p) { return p.x; } | |
54 | static inline Coord y(const Vector& p) { return p.y; } | |
55 | ||
56 | class Cntr { | |
57 | std::vector<Vector> v_; | |
58 | public: | |
59 | Cntr(const Contour& c) { | |
60 | v_.reserve(c.size()); | |
61 | std::transform(c.begin(), c.end(), std::back_inserter(v_), | |
62 | [](const Point& p) { | |
63 | return Vector(double(x(p)) / 1e6, double(y(p)) / 1e6); | |
64 | }); | |
65 | std::reverse(v_.begin(), v_.end()); | |
66 | v_.pop_back(); | |
67 | } | |
68 | Cntr() = default; | |
69 | ||
70 | Coord offsetx = 0; | |
71 | Coord offsety = 0; | |
72 | size_t size() const { return v_.size(); } | |
73 | bool empty() const { return v_.empty(); } | |
74 | typename std::vector<Vector>::const_iterator cbegin() const { return v_.cbegin(); } | |
75 | typename std::vector<Vector>::const_iterator cend() const { return v_.cend(); } | |
76 | typename std::vector<Vector>::iterator begin() { return v_.begin(); } | |
77 | typename std::vector<Vector>::iterator end() { return v_.end(); } | |
78 | Vector& operator[](size_t idx) { return v_[idx]; } | |
79 | const Vector& operator[](size_t idx) const { return v_[idx]; } | |
80 | template<class...Args> | |
81 | void emplace_back(Args&&...args) { | |
82 | v_.emplace_back(std::forward<Args>(args)...); | |
83 | } | |
84 | template<class...Args> | |
85 | void push(Args&&...args) { | |
86 | v_.emplace_back(std::forward<Args>(args)...); | |
87 | } | |
88 | void clear() { v_.clear(); } | |
89 | ||
90 | operator Contour() const { | |
91 | Contour cnt; | |
92 | cnt.reserve(v_.size() + 1); | |
93 | std::transform(v_.begin(), v_.end(), std::back_inserter(cnt), | |
94 | [](const Vector& vertex) { | |
95 | return Point(iCoord(vertex.x) * 1000000, iCoord(vertex.y) * 1000000); | |
96 | }); | |
97 | if(!cnt.empty()) cnt.emplace_back(cnt.front()); | |
98 | S sh = shapelike::create<S>(cnt); | |
99 | ||
100 | // std::reverse(cnt.begin(), cnt.end()); | |
101 | return shapelike::getContour(sh); | |
102 | } | |
103 | }; | |
104 | ||
105 | inline static bool _almostEqual(Coord a, Coord b, | |
106 | Coord tolerance = TOL) | |
107 | { | |
108 | return std::abs(a - b) < tolerance; | |
109 | } | |
110 | ||
111 | // returns true if p lies on the line segment defined by AB, | |
112 | // but not at any endpoints may need work! | |
113 | static bool _onSegment(const Vector& A, const Vector& B, const Vector& p) { | |
114 | ||
115 | // vertical line | |
116 | if(_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x)) { | |
117 | if(!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) && | |
118 | p.y < max(B.y, A.y) && p.y > min(B.y, A.y)){ | |
119 | return true; | |
120 | } | |
121 | else{ | |
122 | return false; | |
123 | } | |
124 | } | |
125 | ||
126 | // horizontal line | |
127 | if(_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y)){ | |
128 | if(!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) && | |
129 | p.x < max(B.x, A.x) && p.x > min(B.x, A.x)){ | |
130 | return true; | |
131 | } | |
132 | else{ | |
133 | return false; | |
134 | } | |
135 | } | |
136 | ||
137 | //range check | |
138 | if((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) || | |
139 | (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y)) | |
140 | return false; | |
141 | ||
142 | // exclude end points | |
143 | if((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) || | |
144 | (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y))) | |
145 | return false; | |
146 | ||
147 | ||
148 | double cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y); | |
149 | ||
150 | if(abs(cross) > TOL) return false; | |
151 | ||
152 | double dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y)*(B.y - A.y); | |
153 | ||
154 | if(dot < 0 || _almostEqual(dot, 0)) return false; | |
155 | ||
156 | double len2 = (B.x - A.x)*(B.x - A.x) + (B.y - A.y)*(B.y - A.y); | |
157 | ||
158 | if(dot > len2 || _almostEqual(dot, len2)) return false; | |
159 | ||
160 | return true; | |
161 | } | |
162 | ||
163 | // return true if point is in the polygon, false if outside, and null if exactly on a point or edge | |
164 | static int pointInPolygon(const Vector& point, const Cntr& polygon) { | |
165 | if(polygon.size() < 3){ | |
166 | return 0; | |
167 | } | |
168 | ||
169 | bool inside = false; | |
170 | Coord offsetx = polygon.offsetx; | |
171 | Coord offsety = polygon.offsety; | |
172 | ||
173 | for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j=i++) { | |
174 | auto xi = polygon[i].x + offsetx; | |
175 | auto yi = polygon[i].y + offsety; | |
176 | auto xj = polygon[j].x + offsetx; | |
177 | auto yj = polygon[j].y + offsety; | |
178 | ||
179 | if(_almostEqual(xi, point.x) && _almostEqual(yi, point.y)){ | |
180 | return 0; // no result | |
181 | } | |
182 | ||
183 | if(_onSegment({xi, yi}, {xj, yj}, point)){ | |
184 | return 0; // exactly on the segment | |
185 | } | |
186 | ||
187 | if(_almostEqual(xi, xj) && _almostEqual(yi, yj)){ // ignore very small lines | |
188 | continue; | |
189 | } | |
190 | ||
191 | bool intersect = ((yi > point.y) != (yj > point.y)) && | |
192 | (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi); | |
193 | if (intersect) inside = !inside; | |
194 | } | |
195 | ||
196 | return inside? 1 : -1; | |
197 | } | |
198 | ||
199 | static bool intersect(const Cntr& A, const Cntr& B){ | |
200 | Contour a = A, b = B; | |
201 | return shapelike::intersects(shapelike::create<S>(a), shapelike::create<S>(b)); | |
202 | } | |
203 | ||
204 | static Vector _normalizeVector(const Vector& v) { | |
205 | if(_almostEqual(v.x*v.x + v.y*v.y, Coord(1))){ | |
206 | return Point(v); // given vector was already a unit vector | |
207 | } | |
208 | auto len = sqrt(v.x*v.x + v.y*v.y); | |
209 | auto inverse = 1/len; | |
210 | ||
211 | return { Coord(v.x*inverse), Coord(v.y*inverse) }; | |
212 | } | |
213 | ||
214 | static double pointDistance( const Vector& p, | |
215 | const Vector& s1, | |
216 | const Vector& s2, | |
217 | Vector normal, | |
218 | bool infinite = false) | |
219 | { | |
220 | normal = _normalizeVector(normal); | |
221 | ||
222 | Vector dir = { | |
223 | normal.y, | |
224 | -normal.x | |
225 | }; | |
226 | ||
227 | auto pdot = p.x*dir.x + p.y*dir.y; | |
228 | auto s1dot = s1.x*dir.x + s1.y*dir.y; | |
229 | auto s2dot = s2.x*dir.x + s2.y*dir.y; | |
230 | ||
231 | auto pdotnorm = p.x*normal.x + p.y*normal.y; | |
232 | auto s1dotnorm = s1.x*normal.x + s1.y*normal.y; | |
233 | auto s2dotnorm = s2.x*normal.x + s2.y*normal.y; | |
234 | ||
235 | if(!infinite){ | |
236 | if (((pdot<s1dot || _almostEqual(pdot, s1dot)) && | |
237 | (pdot<s2dot || _almostEqual(pdot, s2dot))) || | |
238 | ((pdot>s1dot || _almostEqual(pdot, s1dot)) && | |
239 | (pdot>s2dot || _almostEqual(pdot, s2dot)))) | |
240 | { | |
241 | // dot doesn't collide with segment, | |
242 | // or lies directly on the vertex | |
243 | return dNAN; | |
244 | } | |
245 | if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && | |
246 | (pdotnorm>s1dotnorm && pdotnorm>s2dotnorm)) | |
247 | { | |
248 | return min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm); | |
249 | } | |
250 | if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && | |
251 | (pdotnorm<s1dotnorm && pdotnorm<s2dotnorm)){ | |
252 | return -min(s1dotnorm-pdotnorm, s2dotnorm-pdotnorm); | |
253 | } | |
254 | } | |
255 | ||
256 | return -(pdotnorm - s1dotnorm + (s1dotnorm - s2dotnorm)*(s1dot - pdot) | |
257 | / double(s1dot - s2dot)); | |
258 | } | |
259 | ||
260 | static double segmentDistance( const Vector& A, | |
261 | const Vector& B, | |
262 | const Vector& E, | |
263 | const Vector& F, | |
264 | Vector direction) | |
265 | { | |
266 | Vector normal = { | |
267 | direction.y, | |
268 | -direction.x | |
269 | }; | |
270 | ||
271 | Vector reverse = { | |
272 | -direction.x, | |
273 | -direction.y | |
274 | }; | |
275 | ||
276 | auto dotA = A.x*normal.x + A.y*normal.y; | |
277 | auto dotB = B.x*normal.x + B.y*normal.y; | |
278 | auto dotE = E.x*normal.x + E.y*normal.y; | |
279 | auto dotF = F.x*normal.x + F.y*normal.y; | |
280 | ||
281 | auto crossA = A.x*direction.x + A.y*direction.y; | |
282 | auto crossB = B.x*direction.x + B.y*direction.y; | |
283 | auto crossE = E.x*direction.x + E.y*direction.y; | |
284 | auto crossF = F.x*direction.x + F.y*direction.y; | |
285 | ||
286 | // auto crossABmin = min(crossA, crossB); | |
287 | // auto crossABmax = max(crossA, crossB); | |
288 | ||
289 | // auto crossEFmax = max(crossE, crossF); | |
290 | // auto crossEFmin = min(crossE, crossF); | |
291 | ||
292 | auto ABmin = min(dotA, dotB); | |
293 | auto ABmax = max(dotA, dotB); | |
294 | ||
295 | auto EFmax = max(dotE, dotF); | |
296 | auto EFmin = min(dotE, dotF); | |
297 | ||
298 | // segments that will merely touch at one point | |
299 | if(_almostEqual(ABmax, EFmin, TOL) || _almostEqual(ABmin, EFmax,TOL)) { | |
300 | return dNAN; | |
301 | } | |
302 | // segments miss eachother completely | |
303 | if(ABmax < EFmin || ABmin > EFmax){ | |
304 | return dNAN; | |
305 | } | |
306 | ||
307 | double overlap = 0; | |
308 | ||
309 | if((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin)) | |
310 | { | |
311 | overlap = 1; | |
312 | } | |
313 | else{ | |
314 | auto minMax = min(ABmax, EFmax); | |
315 | auto maxMin = max(ABmin, EFmin); | |
316 | ||
317 | auto maxMax = max(ABmax, EFmax); | |
318 | auto minMin = min(ABmin, EFmin); | |
319 | ||
320 | overlap = (minMax-maxMin)/(maxMax-minMin); | |
321 | } | |
322 | ||
323 | auto crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y); | |
324 | auto crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y); | |
325 | ||
326 | // lines are colinear | |
327 | if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){ | |
328 | ||
329 | Vector ABnorm = {B.y-A.y, A.x-B.x}; | |
330 | Vector EFnorm = {F.y-E.y, E.x-F.x}; | |
331 | ||
332 | auto ABnormlength = sqrt(ABnorm.x*ABnorm.x + ABnorm.y*ABnorm.y); | |
333 | ABnorm.x /= ABnormlength; | |
334 | ABnorm.y /= ABnormlength; | |
335 | ||
336 | auto EFnormlength = sqrt(EFnorm.x*EFnorm.x + EFnorm.y*EFnorm.y); | |
337 | EFnorm.x /= EFnormlength; | |
338 | EFnorm.y /= EFnormlength; | |
339 | ||
340 | // segment normals must point in opposite directions | |
341 | if(abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL && | |
342 | ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0){ | |
343 | // normal of AB segment must point in same direction as | |
344 | // given direction vector | |
345 | auto normdot = ABnorm.y * direction.y + ABnorm.x * direction.x; | |
346 | // the segments merely slide along eachother | |
347 | if(_almostEqual(normdot,0, TOL)){ | |
348 | return dNAN; | |
349 | } | |
350 | if(normdot < 0){ | |
351 | return 0.0; | |
352 | } | |
353 | } | |
354 | return dNAN; | |
355 | } | |
356 | ||
357 | std::vector<double> distances; distances.reserve(10); | |
358 | ||
359 | // coincident points | |
360 | if(_almostEqual(dotA, dotE)){ | |
361 | distances.emplace_back(crossA-crossE); | |
362 | } | |
363 | else if(_almostEqual(dotA, dotF)){ | |
364 | distances.emplace_back(crossA-crossF); | |
365 | } | |
366 | else if(dotA > EFmin && dotA < EFmax){ | |
367 | auto d = pointDistance(A,E,F,reverse); | |
368 | if(!isnan(d) && _almostEqual(d, 0)) | |
369 | { // A currently touches EF, but AB is moving away from EF | |
370 | auto dB = pointDistance(B,E,F,reverse,true); | |
371 | if(dB < 0 || _almostEqual(dB*overlap,0)){ | |
372 | d = dNAN; | |
373 | } | |
374 | } | |
375 | if(!isnan(d)){ | |
376 | distances.emplace_back(d); | |
377 | } | |
378 | } | |
379 | ||
380 | if(_almostEqual(dotB, dotE)){ | |
381 | distances.emplace_back(crossB-crossE); | |
382 | } | |
383 | else if(_almostEqual(dotB, dotF)){ | |
384 | distances.emplace_back(crossB-crossF); | |
385 | } | |
386 | else if(dotB > EFmin && dotB < EFmax){ | |
387 | auto d = pointDistance(B,E,F,reverse); | |
388 | ||
389 | if(!isnan(d) && _almostEqual(d, 0)) | |
390 | { // crossA>crossB A currently touches EF, but AB is moving away from EF | |
391 | double dA = pointDistance(A,E,F,reverse,true); | |
392 | if(dA < 0 || _almostEqual(dA*overlap,0)){ | |
393 | d = dNAN; | |
394 | } | |
395 | } | |
396 | if(!isnan(d)){ | |
397 | distances.emplace_back(d); | |
398 | } | |
399 | } | |
400 | ||
401 | if(dotE > ABmin && dotE < ABmax){ | |
402 | auto d = pointDistance(E,A,B,direction); | |
403 | if(!isnan(d) && _almostEqual(d, 0)) | |
404 | { // crossF<crossE A currently touches EF, but AB is moving away from EF | |
405 | double dF = pointDistance(F,A,B,direction, true); | |
406 | if(dF < 0 || _almostEqual(dF*overlap,0)){ | |
407 | d = dNAN; | |
408 | } | |
409 | } | |
410 | if(!isnan(d)){ | |
411 | distances.emplace_back(d); | |
412 | } | |
413 | } | |
414 | ||
415 | if(dotF > ABmin && dotF < ABmax){ | |
416 | auto d = pointDistance(F,A,B,direction); | |
417 | if(!isnan(d) && _almostEqual(d, 0)) | |
418 | { // && crossE<crossF A currently touches EF, | |
419 | // but AB is moving away from EF | |
420 | double dE = pointDistance(E,A,B,direction, true); | |
421 | if(dE < 0 || _almostEqual(dE*overlap,0)){ | |
422 | d = dNAN; | |
423 | } | |
424 | } | |
425 | if(!isnan(d)){ | |
426 | distances.emplace_back(d); | |
427 | } | |
428 | } | |
429 | ||
430 | if(distances.empty()){ | |
431 | return dNAN; | |
432 | } | |
433 | ||
434 | return *std::min_element(distances.begin(), distances.end()); | |
435 | } | |
436 | ||
437 | static double polygonSlideDistance( const Cntr& AA, | |
438 | const Cntr& BB, | |
439 | Vector direction, | |
440 | bool ignoreNegative) | |
441 | { | |
442 | // Vector A1, A2, B1, B2; | |
443 | Cntr A = AA; | |
444 | Cntr B = BB; | |
445 | ||
446 | Coord Aoffsetx = A.offsetx; | |
447 | Coord Boffsetx = B.offsetx; | |
448 | Coord Aoffsety = A.offsety; | |
449 | Coord Boffsety = B.offsety; | |
450 | ||
451 | // close the loop for polygons | |
452 | if(A[0] != A[A.size()-1]){ | |
453 | A.emplace_back(AA[0]); | |
454 | } | |
455 | ||
456 | if(B[0] != B[B.size()-1]){ | |
457 | B.emplace_back(BB[0]); | |
458 | } | |
459 | ||
460 | auto& edgeA = A; | |
461 | auto& edgeB = B; | |
462 | ||
463 | double distance = dNAN, d = dNAN; | |
464 | ||
465 | Vector dir = _normalizeVector(direction); | |
466 | ||
467 | // Vector normal = { | |
468 | // dir.y, | |
469 | // -dir.x | |
470 | // }; | |
471 | ||
472 | // Vector reverse = { | |
473 | // -dir.x, | |
474 | // -dir.y, | |
475 | // }; | |
476 | ||
477 | for(size_t i = 0; i < edgeB.size() - 1; i++){ | |
478 | for(size_t j = 0; j < edgeA.size() - 1; j++){ | |
479 | Vector A1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety }; | |
480 | Vector A2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety}; | |
481 | Vector B1 = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety }; | |
482 | Vector B2 = {x(edgeB[i+1]) + Boffsetx, y(edgeB[i+1]) + Boffsety}; | |
483 | ||
484 | if((_almostEqual(A1.x, A2.x) && _almostEqual(A1.y, A2.y)) || | |
485 | (_almostEqual(B1.x, B2.x) && _almostEqual(B1.y, B2.y))){ | |
486 | continue; // ignore extremely small lines | |
487 | } | |
488 | ||
489 | d = segmentDistance(A1, A2, B1, B2, dir); | |
490 | ||
491 | if(!isnan(d) && (isnan(distance) || d < distance)){ | |
492 | if(!ignoreNegative || d > 0 || _almostEqual(d, 0)){ | |
493 | distance = d; | |
494 | } | |
495 | } | |
496 | } | |
497 | } | |
498 | return distance; | |
499 | } | |
500 | ||
501 | static double polygonProjectionDistance(const Cntr& AA, | |
502 | const Cntr& BB, | |
503 | Vector direction) | |
504 | { | |
505 | Cntr A = AA; | |
506 | Cntr B = BB; | |
507 | ||
508 | auto Boffsetx = B.offsetx; | |
509 | auto Boffsety = B.offsety; | |
510 | auto Aoffsetx = A.offsetx; | |
511 | auto Aoffsety = A.offsety; | |
512 | ||
513 | // close the loop for polygons | |
514 | if(A[0] != A[A.size()-1]){ | |
515 | A.push(A[0]); | |
516 | } | |
517 | ||
518 | if(B[0] != B[B.size()-1]){ | |
519 | B.push(B[0]); | |
520 | } | |
521 | ||
522 | auto& edgeA = A; | |
523 | auto& edgeB = B; | |
524 | ||
525 | double distance = dNAN, d; | |
526 | // Vector p, s1, s2; | |
527 | ||
528 | for(size_t i = 0; i < edgeB.size(); i++) { | |
529 | // the shortest/most negative projection of B onto A | |
530 | double minprojection = dNAN; | |
531 | Vector minp; | |
532 | for(size_t j = 0; j < edgeA.size() - 1; j++){ | |
533 | Vector p = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety }; | |
534 | Vector s1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety }; | |
535 | Vector s2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety }; | |
536 | ||
537 | if(abs((s2.y-s1.y) * direction.x - | |
538 | (s2.x-s1.x) * direction.y) < TOL) continue; | |
539 | ||
540 | // project point, ignore edge boundaries | |
541 | d = pointDistance(p, s1, s2, direction); | |
542 | ||
543 | if(!isnan(d) && (isnan(minprojection) || d < minprojection)) { | |
544 | minprojection = d; | |
545 | minp = p; | |
546 | } | |
547 | } | |
548 | ||
549 | if(!isnan(minprojection) && (isnan(distance) || | |
550 | minprojection > distance)){ | |
551 | distance = minprojection; | |
552 | } | |
553 | } | |
554 | ||
555 | return distance; | |
556 | } | |
557 | ||
558 | static std::pair<bool, Vector> searchStartPoint( | |
559 | const Cntr& AA, const Cntr& BB, bool inside, const std::vector<Cntr>& NFP = {}) | |
560 | { | |
561 | // clone arrays | |
562 | auto A = AA; | |
563 | auto B = BB; | |
564 | ||
565 | // // close the loop for polygons | |
566 | // if(A[0] != A[A.size()-1]){ | |
567 | // A.push(A[0]); | |
568 | // } | |
569 | ||
570 | // if(B[0] != B[B.size()-1]){ | |
571 | // B.push(B[0]); | |
572 | // } | |
573 | ||
574 | // returns true if point already exists in the given nfp | |
575 | auto inNfp = [](const Vector& p, const std::vector<Cntr>& nfp){ | |
576 | if(nfp.empty()){ | |
577 | return false; | |
578 | } | |
579 | ||
580 | for(size_t i=0; i < nfp.size(); i++){ | |
581 | for(size_t j = 0; j< nfp[i].size(); j++){ | |
582 | if(_almostEqual(p.x, nfp[i][j].x) && | |
583 | _almostEqual(p.y, nfp[i][j].y)){ | |
584 | return true; | |
585 | } | |
586 | } | |
587 | } | |
588 | ||
589 | return false; | |
590 | }; | |
591 | ||
592 | for(size_t i = 0; i < A.size() - 1; i++){ | |
593 | if(!A[i].marked) { | |
594 | A[i].marked = true; | |
595 | for(size_t j = 0; j < B.size(); j++){ | |
596 | B.offsetx = A[i].x - B[j].x; | |
597 | B.offsety = A[i].y - B[j].y; | |
598 | ||
599 | int Binside = 0; | |
600 | for(size_t k = 0; k < B.size(); k++){ | |
601 | int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); | |
602 | if(inpoly != 0){ | |
603 | Binside = inpoly; | |
604 | break; | |
605 | } | |
606 | } | |
607 | ||
608 | if(Binside == 0){ // A and B are the same | |
609 | return {false, {}}; | |
610 | } | |
611 | ||
612 | auto startPoint = std::make_pair(true, Vector(B.offsetx, B.offsety)); | |
613 | if(((Binside && inside) || (!Binside && !inside)) && | |
614 | !intersect(A,B) && !inNfp(startPoint.second, NFP)){ | |
615 | return startPoint; | |
616 | } | |
617 | ||
618 | // slide B along vector | |
619 | auto vx = A[i+1].x - A[i].x; | |
620 | auto vy = A[i+1].y - A[i].y; | |
621 | ||
622 | double d1 = polygonProjectionDistance(A,B,{vx, vy}); | |
623 | double d2 = polygonProjectionDistance(B,A,{-vx, -vy}); | |
624 | ||
625 | double d = dNAN; | |
626 | ||
627 | // todo: clean this up | |
628 | if(isnan(d1) && isnan(d2)){ | |
629 | // nothin | |
630 | } | |
631 | else if(isnan(d1)){ | |
632 | d = d2; | |
633 | } | |
634 | else if(isnan(d2)){ | |
635 | d = d1; | |
636 | } | |
637 | else{ | |
638 | d = min(d1,d2); | |
639 | } | |
640 | ||
641 | // only slide until no longer negative | |
642 | // todo: clean this up | |
643 | if(!isnan(d) && !_almostEqual(d,0) && d > 0){ | |
644 | ||
645 | } | |
646 | else{ | |
647 | continue; | |
648 | } | |
649 | ||
650 | auto vd2 = vx*vx + vy*vy; | |
651 | ||
652 | if(d*d < vd2 && !_almostEqual(d*d, vd2)){ | |
653 | auto vd = sqrt(vx*vx + vy*vy); | |
654 | vx *= d/vd; | |
655 | vy *= d/vd; | |
656 | } | |
657 | ||
658 | B.offsetx += vx; | |
659 | B.offsety += vy; | |
660 | ||
661 | for(size_t k = 0; k < B.size(); k++){ | |
662 | int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); | |
663 | if(inpoly != 0){ | |
664 | Binside = inpoly; | |
665 | break; | |
666 | } | |
667 | } | |
668 | startPoint = std::make_pair(true, Vector{B.offsetx, B.offsety}); | |
669 | if(((Binside && inside) || (!Binside && !inside)) && | |
670 | !intersect(A,B) && !inNfp(startPoint.second, NFP)){ | |
671 | return startPoint; | |
672 | } | |
673 | } | |
674 | } | |
675 | } | |
676 | ||
677 | return {false, Vector(0, 0)}; | |
678 | } | |
679 | ||
680 | static std::vector<Cntr> noFitPolygon(Cntr A, | |
681 | Cntr B, | |
682 | bool inside, | |
683 | bool searchEdges) | |
684 | { | |
685 | if(A.size() < 3 || B.size() < 3) { | |
686 | throw GeometryException(GeomErr::NFP); | |
687 | return {}; | |
688 | } | |
689 | ||
690 | A.offsetx = 0; | |
691 | A.offsety = 0; | |
692 | ||
693 | long i = 0, j = 0; | |
694 | ||
695 | auto minA = y(A[0]); | |
696 | long minAindex = 0; | |
697 | ||
698 | auto maxB = y(B[0]); | |
699 | long maxBindex = 0; | |
700 | ||
701 | for(i = 1; i < A.size(); i++){ | |
702 | A[i].marked = false; | |
703 | if(y(A[i]) < minA){ | |
704 | minA = y(A[i]); | |
705 | minAindex = i; | |
706 | } | |
707 | } | |
708 | ||
709 | for(i = 1; i < B.size(); i++){ | |
710 | B[i].marked = false; | |
711 | if(y(B[i]) > maxB){ | |
712 | maxB = y(B[i]); | |
713 | maxBindex = i; | |
714 | } | |
715 | } | |
716 | ||
717 | std::pair<bool, Vector> startpoint; | |
718 | ||
719 | if(!inside){ | |
720 | // shift B such that the bottom-most point of B is at the top-most | |
721 | // point of A. This guarantees an initial placement with no | |
722 | // intersections | |
723 | startpoint = { true, | |
724 | { x(A[minAindex]) - x(B[maxBindex]), | |
725 | y(A[minAindex]) - y(B[maxBindex]) } | |
726 | }; | |
727 | } | |
728 | else { | |
729 | // no reliable heuristic for inside | |
730 | startpoint = searchStartPoint(A, B, true); | |
731 | } | |
732 | ||
733 | std::vector<Cntr> NFPlist; | |
734 | ||
735 | struct Touch { | |
736 | int type; | |
737 | long A; | |
738 | long B; | |
739 | Touch(int t, long a, long b): type(t), A(a), B(b) {} | |
740 | }; | |
741 | ||
742 | while(startpoint.first) { | |
743 | ||
744 | B.offsetx = startpoint.second.x; | |
745 | B.offsety = startpoint.second.y; | |
746 | ||
747 | // maintain a list of touching points/edges | |
748 | std::vector<Touch> touching; | |
749 | ||
750 | struct V { | |
751 | Coord x, y; | |
752 | Vector *start, *end; | |
753 | operator bool() { | |
754 | return start != nullptr && end != nullptr; | |
755 | } | |
756 | operator Vector() const { return {x, y}; } | |
757 | } prevvector = {0, 0, nullptr, nullptr}; | |
758 | ||
759 | Cntr NFP; | |
760 | NFP.emplace_back(x(B[0]) + B.offsetx, y(B[0]) + B.offsety); | |
761 | ||
762 | auto referencex = x(B[0]) + B.offsetx; | |
763 | auto referencey = y(B[0]) + B.offsety; | |
764 | auto startx = referencex; | |
765 | auto starty = referencey; | |
766 | unsigned counter = 0; | |
767 | ||
768 | // sanity check, prevent infinite loop | |
769 | while(counter < 10*(A.size() + B.size())){ | |
770 | touching.clear(); | |
771 | ||
772 | // find touching vertices/edges | |
773 | for(i = 0; i < A.size(); i++){ | |
774 | long nexti = (i == A.size() - 1) ? 0 : i + 1; | |
775 | for(j = 0; j < B.size(); j++){ | |
776 | ||
777 | long nextj = (j == B.size() - 1) ? 0 : j + 1; | |
778 | ||
779 | if( _almostEqual(A[i].x, B[j].x+B.offsetx) && | |
780 | _almostEqual(A[i].y, B[j].y+B.offsety)) | |
781 | { | |
782 | touching.emplace_back(0, i, j); | |
783 | } | |
784 | else if( _onSegment( | |
785 | A[i], A[nexti], | |
786 | { B[j].x+B.offsetx, B[j].y + B.offsety}) ) | |
787 | { | |
788 | touching.emplace_back(1, nexti, j); | |
789 | } | |
790 | else if( _onSegment( | |
791 | {B[j].x+B.offsetx, B[j].y + B.offsety}, | |
792 | {B[nextj].x+B.offsetx, B[nextj].y + B.offsety}, | |
793 | A[i]) ) | |
794 | { | |
795 | touching.emplace_back(2, i, nextj); | |
796 | } | |
797 | } | |
798 | } | |
799 | ||
800 | // generate translation vectors from touching vertices/edges | |
801 | std::vector<V> vectors; | |
802 | for(i=0; i < touching.size(); i++){ | |
803 | auto& vertexA = A[touching[i].A]; | |
804 | vertexA.marked = true; | |
805 | ||
806 | // adjacent A vertices | |
807 | auto prevAindex = touching[i].A - 1; | |
808 | auto nextAindex = touching[i].A + 1; | |
809 | ||
810 | prevAindex = (prevAindex < 0) ? A.size() - 1 : prevAindex; // loop | |
811 | nextAindex = (nextAindex >= A.size()) ? 0 : nextAindex; // loop | |
812 | ||
813 | auto& prevA = A[prevAindex]; | |
814 | auto& nextA = A[nextAindex]; | |
815 | ||
816 | // adjacent B vertices | |
817 | auto& vertexB = B[touching[i].B]; | |
818 | ||
819 | auto prevBindex = touching[i].B-1; | |
820 | auto nextBindex = touching[i].B+1; | |
821 | ||
822 | prevBindex = (prevBindex < 0) ? B.size() - 1 : prevBindex; // loop | |
823 | nextBindex = (nextBindex >= B.size()) ? 0 : nextBindex; // loop | |
824 | ||
825 | auto& prevB = B[prevBindex]; | |
826 | auto& nextB = B[nextBindex]; | |
827 | ||
828 | if(touching[i].type == 0){ | |
829 | ||
830 | V vA1 = { | |
831 | prevA.x - vertexA.x, | |
832 | prevA.y - vertexA.y, | |
833 | &vertexA, | |
834 | &prevA | |
835 | }; | |
836 | ||
837 | V vA2 = { | |
838 | nextA.x - vertexA.x, | |
839 | nextA.y - vertexA.y, | |
840 | &vertexA, | |
841 | &nextA | |
842 | }; | |
843 | ||
844 | // B vectors need to be inverted | |
845 | V vB1 = { | |
846 | vertexB.x - prevB.x, | |
847 | vertexB.y - prevB.y, | |
848 | &prevB, | |
849 | &vertexB | |
850 | }; | |
851 | ||
852 | V vB2 = { | |
853 | vertexB.x - nextB.x, | |
854 | vertexB.y - nextB.y, | |
855 | &nextB, | |
856 | &vertexB | |
857 | }; | |
858 | ||
859 | vectors.emplace_back(vA1); | |
860 | vectors.emplace_back(vA2); | |
861 | vectors.emplace_back(vB1); | |
862 | vectors.emplace_back(vB2); | |
863 | } | |
864 | else if(touching[i].type == 1){ | |
865 | vectors.emplace_back(V{ | |
866 | vertexA.x-(vertexB.x+B.offsetx), | |
867 | vertexA.y-(vertexB.y+B.offsety), | |
868 | &prevA, | |
869 | &vertexA | |
870 | }); | |
871 | ||
872 | vectors.emplace_back(V{ | |
873 | prevA.x-(vertexB.x+B.offsetx), | |
874 | prevA.y-(vertexB.y+B.offsety), | |
875 | &vertexA, | |
876 | &prevA | |
877 | }); | |
878 | } | |
879 | else if(touching[i].type == 2){ | |
880 | vectors.emplace_back(V{ | |
881 | vertexA.x-(vertexB.x+B.offsetx), | |
882 | vertexA.y-(vertexB.y+B.offsety), | |
883 | &prevB, | |
884 | &vertexB | |
885 | }); | |
886 | ||
887 | vectors.emplace_back(V{ | |
888 | vertexA.x-(prevB.x+B.offsetx), | |
889 | vertexA.y-(prevB.y+B.offsety), | |
890 | &vertexB, | |
891 | &prevB | |
892 | }); | |
893 | } | |
894 | } | |
895 | ||
896 | // TODO: there should be a faster way to reject vectors that | |
897 | // will cause immediate intersection. For now just check them all | |
898 | ||
899 | V translate = {0, 0, nullptr, nullptr}; | |
900 | double maxd = 0; | |
901 | ||
902 | for(i = 0; i < vectors.size(); i++) { | |
903 | if(vectors[i].x == 0 && vectors[i].y == 0){ | |
904 | continue; | |
905 | } | |
906 | ||
907 | // if this vector points us back to where we came from, ignore it. | |
908 | // ie cross product = 0, dot product < 0 | |
909 | if(prevvector && vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0){ | |
910 | ||
911 | // compare magnitude with unit vectors | |
912 | double vectorlength = sqrt(vectors[i].x*vectors[i].x+vectors[i].y*vectors[i].y); | |
913 | Vector unitv = {Coord(vectors[i].x/vectorlength), | |
914 | Coord(vectors[i].y/vectorlength)}; | |
915 | ||
916 | double prevlength = sqrt(prevvector.x*prevvector.x+prevvector.y*prevvector.y); | |
917 | Vector prevunit = { prevvector.x/prevlength, prevvector.y/prevlength}; | |
918 | ||
919 | // we need to scale down to unit vectors to normalize vector length. Could also just do a tan here | |
920 | if(abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001){ | |
921 | continue; | |
922 | } | |
923 | } | |
924 | ||
925 | V vi = vectors[i]; | |
926 | double d = polygonSlideDistance(A, B, vi, true); | |
927 | double vecd2 = vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y; | |
928 | ||
929 | if(isnan(d) || d*d > vecd2){ | |
930 | double vecd = sqrt(vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y); | |
931 | d = vecd; | |
932 | } | |
933 | ||
934 | if(!isnan(d) && d > maxd){ | |
935 | maxd = d; | |
936 | translate = vectors[i]; | |
937 | } | |
938 | } | |
939 | ||
940 | if(!translate || _almostEqual(maxd, 0)) | |
941 | { | |
942 | // didn't close the loop, something went wrong here | |
943 | NFP.clear(); | |
944 | break; | |
945 | } | |
946 | ||
947 | translate.start->marked = true; | |
948 | translate.end->marked = true; | |
949 | ||
950 | prevvector = translate; | |
951 | ||
952 | // trim | |
953 | double vlength2 = translate.x*translate.x + translate.y*translate.y; | |
954 | if(maxd*maxd < vlength2 && !_almostEqual(maxd*maxd, vlength2)){ | |
955 | double scale = sqrt((maxd*maxd)/vlength2); | |
956 | translate.x *= scale; | |
957 | translate.y *= scale; | |
958 | } | |
959 | ||
960 | referencex += translate.x; | |
961 | referencey += translate.y; | |
962 | ||
963 | if(_almostEqual(referencex, startx) && | |
964 | _almostEqual(referencey, starty)) { | |
965 | // we've made a full loop | |
966 | break; | |
967 | } | |
968 | ||
969 | // if A and B start on a touching horizontal line, | |
970 | // the end point may not be the start point | |
971 | bool looped = false; | |
972 | if(NFP.size() > 0) { | |
973 | for(i = 0; i < NFP.size() - 1; i++) { | |
974 | if(_almostEqual(referencex, NFP[i].x) && | |
975 | _almostEqual(referencey, NFP[i].y)){ | |
976 | looped = true; | |
977 | } | |
978 | } | |
979 | } | |
980 | ||
981 | if(looped){ | |
982 | // we've made a full loop | |
983 | break; | |
984 | } | |
985 | ||
986 | NFP.emplace_back(referencex, referencey); | |
987 | ||
988 | B.offsetx += translate.x; | |
989 | B.offsety += translate.y; | |
990 | ||
991 | counter++; | |
992 | } | |
993 | ||
994 | if(NFP.size() > 0){ | |
995 | NFPlist.emplace_back(NFP); | |
996 | } | |
997 | ||
998 | if(!searchEdges){ | |
999 | // only get outer NFP or first inner NFP | |
1000 | break; | |
1001 | } | |
1002 | ||
1003 | startpoint = | |
1004 | searchStartPoint(A, B, inside, NFPlist); | |
1005 | ||
1006 | } | |
1007 | ||
1008 | return NFPlist; | |
1009 | } | |
1010 | }; | |
1011 | ||
1012 | template<class S> const double _alg<S>::TOL = std::pow(10, -9); | |
1013 | ||
1014 | } | |
1015 | } | |
1016 | ||
1017 | #endif // NFP_SVGNEST_HPP |
0 | #ifndef NFP_SVGNEST_GLUE_HPP | |
1 | #define NFP_SVGNEST_GLUE_HPP | |
2 | ||
3 | #include "nfp_svgnest.hpp" | |
4 | ||
5 | #include <libnest2d/clipper_backend/clipper_backend.hpp> | |
6 | ||
7 | namespace libnest2d { | |
8 | ||
9 | namespace __svgnest { | |
10 | ||
11 | //template<> struct _Tol<double> { | |
12 | // static const BP2D_CONSTEXPR TCoord<PointImpl> Value = 1000000; | |
13 | //}; | |
14 | ||
15 | } | |
16 | ||
17 | namespace nfp { | |
18 | ||
19 | using NfpR = NfpResult<PolygonImpl>; | |
20 | ||
21 | template<> struct NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> { | |
22 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { | |
23 | // return nfpConvexOnly(sh, cother); | |
24 | namespace sl = shapelike; | |
25 | using alg = __svgnest::_alg<PolygonImpl>; | |
26 | ||
27 | auto nfp_p = alg::noFitPolygon(sl::getContour(sh), | |
28 | sl::getContour(cother), false, false); | |
29 | ||
30 | PolygonImpl nfp_cntr; | |
31 | if(!nfp_p.empty()) nfp_cntr.Contour = nfp_p.front(); | |
32 | return {nfp_cntr, referenceVertex(nfp_cntr)}; | |
33 | } | |
34 | }; | |
35 | ||
36 | template<> struct NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> { | |
37 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { | |
38 | // return nfpConvexOnly(sh, cother); | |
39 | namespace sl = shapelike; | |
40 | using alg = __svgnest::_alg<PolygonImpl>; | |
41 | ||
42 | std::cout << "Itt vagyok" << std::endl; | |
43 | auto nfp_p = alg::noFitPolygon(sl::getContour(sh), | |
44 | sl::getContour(cother), false, false); | |
45 | ||
46 | PolygonImpl nfp_cntr; | |
47 | nfp_cntr.Contour = nfp_p.front(); | |
48 | return {nfp_cntr, referenceVertex(nfp_cntr)}; | |
49 | } | |
50 | }; | |
51 | ||
52 | template<> | |
53 | struct NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> { | |
54 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { | |
55 | namespace sl = shapelike; | |
56 | using alg = __svgnest::_alg<PolygonImpl>; | |
57 | ||
58 | auto nfp_p = alg::noFitPolygon(sl::getContour(sh), | |
59 | sl::getContour(cother), true, false); | |
60 | ||
61 | PolygonImpl nfp_cntr; | |
62 | nfp_cntr.Contour = nfp_p.front(); | |
63 | return {nfp_cntr, referenceVertex(nfp_cntr)}; | |
64 | } | |
65 | }; | |
66 | ||
67 | template<> struct MaxNfpLevel<PolygonImpl> { | |
68 | // static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE; | |
69 | static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; | |
70 | }; | |
71 | ||
72 | }} | |
73 | ||
74 | #endif // NFP_SVGNEST_GLUE_HPP |
55 | 55 | auto d = static_cast<Coord>( |
56 | 56 | std::round(conf_.height*conf_.mm_in_coord_units) ); |
57 | 57 | |
58 | auto& contour = ShapeLike::getContour(tsh); | |
58 | auto& contour = shapelike::getContour(tsh); | |
59 | 59 | for(auto& v : contour) setY(v, -getY(v) + d); |
60 | 60 | |
61 | auto& holes = ShapeLike::holes(tsh); | |
61 | auto& holes = shapelike::holes(tsh); | |
62 | 62 | for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); |
63 | 63 | |
64 | 64 | } |
65 | currentLayer() += ShapeLike::serialize<Formats::SVG>(tsh, | |
65 | currentLayer() += shapelike::serialize<Formats::SVG>(tsh, | |
66 | 66 | 1.0/conf_.mm_in_coord_units) + "\n"; |
67 | 67 | } |
68 | 68 |
1170 | 1170 | // Allow DynamicConfig to be instantiated on ints own without a definition. |
1171 | 1171 | // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. |
1172 | 1172 | const ConfigDef* def() const override { return nullptr; }; |
1173 | bool has(const t_config_option_key &opt_key) const { return this->options.find(opt_key) != this->options.end(); } | |
1173 | 1174 | template<class T> T* opt(const t_config_option_key &opt_key, bool create = false) |
1174 | 1175 | { return dynamic_cast<T*>(this->option(opt_key, create)); } |
1175 | 1176 | template<class T> const T* opt(const t_config_option_key &opt_key) const |
1213 | 1214 | bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; } |
1214 | 1215 | bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; } |
1215 | 1216 | |
1216 | private: | |
1217 | protected: | |
1217 | 1218 | typedef std::map<t_config_option_key,ConfigOption*> t_options_map; |
1218 | 1219 | t_options_map options; |
1219 | 1220 | }; |
8 | 8 | #endif /* SLIC3R_GUI */ |
9 | 9 | |
10 | 10 | #include "libslic3r.h" |
11 | #include "ClipperUtils.hpp" | |
11 | 12 | #include "EdgeGrid.hpp" |
13 | #include "SVG.hpp" | |
12 | 14 | |
13 | 15 | #if 0 |
14 | 16 | // Enable debugging and assert in this file. |
755 | 757 | float search_radius = float(m_resolution<<1); |
756 | 758 | m_signed_distance_field.assign(nrows * ncols, search_radius); |
757 | 759 | // For each cell: |
758 | for (size_t r = 0; r < m_rows; ++ r) { | |
759 | for (size_t c = 0; c < m_cols; ++ c) { | |
760 | for (int r = 0; r < (int)m_rows; ++ r) { | |
761 | for (int c = 0; c < (int)m_cols; ++ c) { | |
760 | 762 | const Cell &cell = m_cells[r * m_cols + c]; |
761 | 763 | // For each segment in the cell: |
762 | 764 | for (size_t i = cell.begin; i != cell.end; ++ i) { |
841 | 843 | #if 0 |
842 | 844 | static int iRun = 0; |
843 | 845 | ++ iRun; |
846 | if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) | |
847 | wxImage::AddHandler(new wxPNGHandler); | |
844 | 848 | //#ifdef SLIC3R_GUI |
845 | 849 | { |
846 | 850 | wxImage img(ncols, nrows); |
1224 | 1228 | return true; |
1225 | 1229 | } |
1226 | 1230 | |
1227 | Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const | |
1228 | { | |
1231 | Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) const | |
1232 | { | |
1233 | assert(std::abs(2 * offset) < m_resolution); | |
1234 | ||
1229 | 1235 | typedef std::unordered_multimap<Point, int, PointHash> EndPointMapType; |
1230 | 1236 | // 0) Prepare a binary grid. |
1231 | 1237 | size_t cell_rows = m_rows + 2; |
1236 | 1242 | cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1); |
1237 | 1243 | // Fill in empty cells, which have a left / right neighbor filled. |
1238 | 1244 | // Fill in empty cells, which have the top / bottom neighbor filled. |
1239 | { | |
1245 | if (fill_holes) { | |
1240 | 1246 | std::vector<char> cell_inside2(cell_inside); |
1241 | 1247 | for (int r = 1; r + 1 < int(cell_rows); ++ r) { |
1242 | 1248 | for (int c = 1; c + 1 < int(cell_cols); ++ c) { |
1353 | 1359 | return out; |
1354 | 1360 | } |
1355 | 1361 | |
1362 | inline int segments_could_intersect( | |
1363 | const Slic3r::Point &ip1, const Slic3r::Point &ip2, | |
1364 | const Slic3r::Point &jp1, const Slic3r::Point &jp2) | |
1365 | { | |
1366 | Slic3r::Point iv = ip1.vector_to(ip2); | |
1367 | Slic3r::Point vij1 = ip1.vector_to(jp1); | |
1368 | Slic3r::Point vij2 = ip1.vector_to(jp2); | |
1369 | int64_t tij1 = int64_t(iv.x) * int64_t(vij1.y) - int64_t(iv.y) * int64_t(vij1.x); // cross(iv, vij1) | |
1370 | int64_t tij2 = int64_t(iv.x) * int64_t(vij2.y) - int64_t(iv.y) * int64_t(vij2.x); // cross(iv, vij2) | |
1371 | int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum | |
1372 | int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); | |
1373 | return sij1 * sij2; | |
1374 | } | |
1375 | ||
1376 | inline bool segments_intersect( | |
1377 | const Slic3r::Point &ip1, const Slic3r::Point &ip2, | |
1378 | const Slic3r::Point &jp1, const Slic3r::Point &jp2) | |
1379 | { | |
1380 | return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && | |
1381 | segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; | |
1382 | } | |
1383 | ||
1384 | std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> EdgeGrid::Grid::intersecting_edges() const | |
1385 | { | |
1386 | std::vector<std::pair<ContourEdge, ContourEdge>> out; | |
1387 | // For each cell: | |
1388 | for (int r = 0; r < (int)m_rows; ++ r) { | |
1389 | for (int c = 0; c < (int)m_cols; ++ c) { | |
1390 | const Cell &cell = m_cells[r * m_cols + c]; | |
1391 | // For each pair of segments in the cell: | |
1392 | for (size_t i = cell.begin; i != cell.end; ++ i) { | |
1393 | const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first]; | |
1394 | size_t ipt = m_cell_data[i].second; | |
1395 | // End points of the line segment and their vector. | |
1396 | const Slic3r::Point &ip1 = ipts[ipt]; | |
1397 | const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]; | |
1398 | for (size_t j = i + 1; j != cell.end; ++ j) { | |
1399 | const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first]; | |
1400 | size_t jpt = m_cell_data[j].second; | |
1401 | // End points of the line segment and their vector. | |
1402 | const Slic3r::Point &jp1 = jpts[jpt]; | |
1403 | const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; | |
1404 | if (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) | |
1405 | // Segments of the same contour share a common vertex. | |
1406 | continue; | |
1407 | if (segments_intersect(ip1, ip2, jp1, jp2)) { | |
1408 | // The two segments intersect. Add them to the output. | |
1409 | int jfirst = (&jpts < &ipts) || (&jpts == &ipts && jpt < ipt); | |
1410 | out.emplace_back(jfirst ? | |
1411 | std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt)) : | |
1412 | std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt))); | |
1413 | } | |
1414 | } | |
1415 | } | |
1416 | } | |
1417 | } | |
1418 | Slic3r::sort_remove_duplicates(out); | |
1419 | return out; | |
1420 | } | |
1421 | ||
1422 | bool EdgeGrid::Grid::has_intersecting_edges() const | |
1423 | { | |
1424 | // For each cell: | |
1425 | for (int r = 0; r < (int)m_rows; ++ r) { | |
1426 | for (int c = 0; c < (int)m_cols; ++ c) { | |
1427 | const Cell &cell = m_cells[r * m_cols + c]; | |
1428 | // For each pair of segments in the cell: | |
1429 | for (size_t i = cell.begin; i != cell.end; ++ i) { | |
1430 | const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first]; | |
1431 | size_t ipt = m_cell_data[i].second; | |
1432 | // End points of the line segment and their vector. | |
1433 | const Slic3r::Point &ip1 = ipts[ipt]; | |
1434 | const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]; | |
1435 | for (size_t j = i + 1; j != cell.end; ++ j) { | |
1436 | const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first]; | |
1437 | size_t jpt = m_cell_data[j].second; | |
1438 | // End points of the line segment and their vector. | |
1439 | const Slic3r::Point &jp1 = jpts[jpt]; | |
1440 | const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; | |
1441 | if (! (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) && | |
1442 | segments_intersect(ip1, ip2, jp1, jp2)) | |
1443 | return true; | |
1444 | } | |
1445 | } | |
1446 | } | |
1447 | } | |
1448 | return false; | |
1449 | } | |
1450 | ||
1356 | 1451 | #if 0 |
1357 | 1452 | void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) |
1358 | 1453 | { |
1454 | if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) | |
1455 | wxImage::AddHandler(new wxPNGHandler); | |
1456 | ||
1359 | 1457 | unsigned int w = (bbox.max.x - bbox.min.x + resolution - 1) / resolution; |
1360 | 1458 | unsigned int h = (bbox.max.y - bbox.min.y + resolution - 1) / resolution; |
1361 | 1459 | wxImage img(w, h); |
1447 | 1545 | } |
1448 | 1546 | #endif /* SLIC3R_GUI */ |
1449 | 1547 | |
1548 | // Find all pairs of intersectiong edges from the set of polygons. | |
1549 | std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersecting_edges(const Polygons &polygons) | |
1550 | { | |
1551 | double len = 0; | |
1552 | size_t cnt = 0; | |
1553 | BoundingBox bbox; | |
1554 | for (const Polygon &poly : polygons) { | |
1555 | if (poly.points.size() < 2) | |
1556 | continue; | |
1557 | for (size_t i = 0; i < poly.points.size(); ++ i) { | |
1558 | bbox.merge(poly.points[i]); | |
1559 | size_t j = (i == 0) ? (poly.points.size() - 1) : i - 1; | |
1560 | len += poly.points[i].distance_to(poly.points[j]); | |
1561 | ++ cnt; | |
1562 | } | |
1563 | } | |
1564 | len /= double(cnt); | |
1565 | bbox.offset(20); | |
1566 | EdgeGrid::Grid grid; | |
1567 | grid.set_bbox(bbox); | |
1568 | grid.create(polygons, len); | |
1569 | return grid.intersecting_edges(); | |
1570 | } | |
1571 | ||
1572 | // Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. | |
1573 | void export_intersections_to_svg(const std::string &filename, const Polygons &polygons) | |
1574 | { | |
1575 | std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersections = intersecting_edges(polygons); | |
1576 | BoundingBox bbox = get_extents(polygons); | |
1577 | SVG svg(filename.c_str(), bbox); | |
1578 | svg.draw(union_ex(polygons), "gray", 0.25f); | |
1579 | svg.draw_outline(polygons, "black"); | |
1580 | std::set<const Points*> intersecting_contours; | |
1581 | for (const std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge> &ie : intersections) { | |
1582 | intersecting_contours.insert(ie.first.first); | |
1583 | intersecting_contours.insert(ie.second.first); | |
1584 | } | |
1585 | // Highlight the contours with intersections. | |
1586 | coord_t line_width = coord_t(scale_(0.01)); | |
1587 | for (const Points *ic : intersecting_contours) { | |
1588 | svg.draw_outline(Polygon(*ic), "green"); | |
1589 | svg.draw_outline(Polygon(*ic), "black", line_width); | |
1590 | } | |
1591 | // Paint the intersections. | |
1592 | for (const std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge> &intersecting_edges : intersections) { | |
1593 | auto edge = [](const EdgeGrid::Grid::ContourEdge &e) { | |
1594 | return Line(e.first->at(e.second), | |
1595 | e.first->at((e.second + 1 == e.first->size()) ? 0 : e.second + 1)); | |
1596 | }; | |
1597 | svg.draw(edge(intersecting_edges.first), "red", line_width); | |
1598 | svg.draw(edge(intersecting_edges.second), "red", line_width); | |
1599 | } | |
1600 | svg.Close(); | |
1601 | } | |
1602 | ||
1450 | 1603 | } // namespace Slic3r |
57 | 57 | const size_t cols() const { return m_cols; } |
58 | 58 | |
59 | 59 | // For supports: Contours enclosing the rasterized edges. |
60 | Polygons contours_simplified(coord_t offset) const; | |
60 | Polygons contours_simplified(coord_t offset, bool fill_holes) const; | |
61 | ||
62 | typedef std::pair<const Slic3r::Points*, size_t> ContourPoint; | |
63 | typedef std::pair<const Slic3r::Points*, size_t> ContourEdge; | |
64 | std::vector<std::pair<ContourEdge, ContourEdge>> intersecting_edges() const; | |
65 | bool has_intersecting_edges() const; | |
61 | 66 | |
62 | 67 | protected: |
63 | 68 | struct Cell { |
112 | 117 | #endif /* SLIC3R_GUI */ |
113 | 118 | |
114 | 119 | } // namespace EdgeGrid |
120 | ||
121 | // Find all pairs of intersectiong edges from the set of polygons. | |
122 | extern std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersecting_edges(const Polygons &polygons); | |
123 | ||
124 | // Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. | |
125 | extern void export_intersections_to_svg(const std::string &filename, const Polygons &polygons); | |
126 | ||
115 | 127 | } // namespace Slic3r |
116 | 128 | |
117 | 129 | #endif /* slic3r_EdgeGrid_hpp_ */ |
60 | 60 | } |
61 | 61 | |
62 | 62 | template <class T> |
63 | bool | |
64 | ExPolygonCollection::contains(const T &item) const | |
63 | bool ExPolygonCollection::contains(const T &item) const | |
65 | 64 | { |
66 | for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { | |
67 | if (it->contains(item)) return true; | |
68 | } | |
65 | for (const ExPolygon &poly : this->expolygons) | |
66 | if (poly.contains(item)) | |
67 | return true; | |
69 | 68 | return false; |
70 | 69 | } |
71 | 70 | template bool ExPolygonCollection::contains<Point>(const Point &item) const; |
90 | 90 | // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. |
91 | 91 | virtual double min_mm3_per_mm() const = 0; |
92 | 92 | virtual Polyline as_polyline() const = 0; |
93 | virtual void collect_polylines(Polylines &dst) const = 0; | |
94 | virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; } | |
93 | 95 | virtual double length() const = 0; |
94 | 96 | virtual double total_volume() const = 0; |
95 | 97 | }; |
122 | 124 | |
123 | 125 | ExtrusionPath* clone() const { return new ExtrusionPath (*this); } |
124 | 126 | void reverse() { this->polyline.reverse(); } |
125 | Point first_point() const { return this->polyline.points.front(); } | |
126 | Point last_point() const { return this->polyline.points.back(); } | |
127 | Point first_point() const override { return this->polyline.points.front(); } | |
128 | Point last_point() const override { return this->polyline.points.back(); } | |
129 | size_t size() const { return this->polyline.size(); } | |
130 | bool empty() const { return this->polyline.empty(); } | |
131 | bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); } | |
127 | 132 | // Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection. |
128 | 133 | // Currently not used. |
129 | 134 | void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; |
132 | 137 | void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; |
133 | 138 | void clip_end(double distance); |
134 | 139 | void simplify(double tolerance); |
135 | virtual double length() const; | |
136 | virtual ExtrusionRole role() const { return m_role; } | |
140 | double length() const override; | |
141 | ExtrusionRole role() const override { return m_role; } | |
137 | 142 | // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. |
138 | 143 | // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. |
139 | 144 | void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; |
148 | 153 | // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. |
149 | 154 | double min_mm3_per_mm() const { return this->mm3_per_mm; } |
150 | 155 | Polyline as_polyline() const { return this->polyline; } |
151 | virtual double total_volume() const { return mm3_per_mm * unscale(length()); } | |
156 | void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } | |
157 | double total_volume() const override { return mm3_per_mm * unscale(length()); } | |
152 | 158 | |
153 | 159 | private: |
154 | 160 | void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; |
177 | 183 | bool can_reverse() const { return true; } |
178 | 184 | ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); } |
179 | 185 | void reverse(); |
180 | Point first_point() const { return this->paths.front().polyline.points.front(); } | |
181 | Point last_point() const { return this->paths.back().polyline.points.back(); } | |
182 | virtual double length() const; | |
183 | virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } | |
186 | Point first_point() const override { return this->paths.front().polyline.points.front(); } | |
187 | Point last_point() const override { return this->paths.back().polyline.points.back(); } | |
188 | double length() const override; | |
189 | ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } | |
184 | 190 | // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. |
185 | 191 | // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. |
186 | 192 | void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; |
195 | 201 | // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. |
196 | 202 | double min_mm3_per_mm() const; |
197 | 203 | Polyline as_polyline() const; |
198 | virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } | |
204 | void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); } | |
205 | double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } | |
199 | 206 | }; |
200 | 207 | |
201 | 208 | // Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging. |
217 | 224 | bool make_clockwise(); |
218 | 225 | bool make_counter_clockwise(); |
219 | 226 | void reverse(); |
220 | Point first_point() const { return this->paths.front().polyline.points.front(); } | |
221 | Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } | |
227 | Point first_point() const override { return this->paths.front().polyline.points.front(); } | |
228 | Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } | |
222 | 229 | Polygon polygon() const; |
223 | virtual double length() const; | |
230 | double length() const override; | |
224 | 231 | bool split_at_vertex(const Point &point); |
225 | 232 | void split_at(const Point &point, bool prefer_non_overhang); |
226 | 233 | void clip_end(double distance, ExtrusionPaths* paths) const; |
227 | 234 | // Test, whether the point is extruded by a bridging flow. |
228 | 235 | // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. |
229 | 236 | bool has_overhang_point(const Point &point) const; |
230 | virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } | |
231 | ExtrusionLoopRole loop_role() const { return m_loop_role; } | |
237 | ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } | |
238 | ExtrusionLoopRole loop_role() const { return m_loop_role; } | |
232 | 239 | // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. |
233 | 240 | // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. |
234 | 241 | void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; |
243 | 250 | // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. |
244 | 251 | double min_mm3_per_mm() const; |
245 | 252 | Polyline as_polyline() const { return this->polygon().split_at_first_point(); } |
246 | virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } | |
253 | void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); } | |
254 | double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } | |
247 | 255 | |
248 | 256 | private: |
249 | 257 | ExtrusionLoopRole m_loop_role; |
23 | 23 | explicit operator ExtrusionPaths() const; |
24 | 24 | |
25 | 25 | bool is_collection() const { return true; }; |
26 | virtual ExtrusionRole role() const { | |
26 | ExtrusionRole role() const override { | |
27 | 27 | ExtrusionRole out = erNone; |
28 | 28 | for (const ExtrusionEntity *ee : entities) { |
29 | 29 | ExtrusionRole er = ee->role(); |
65 | 65 | Point last_point() const { return this->entities.back()->last_point(); } |
66 | 66 | // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. |
67 | 67 | // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. |
68 | virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; | |
68 | void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; | |
69 | 69 | // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. |
70 | 70 | // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. |
71 | 71 | // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. |
72 | virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const; | |
72 | void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override; | |
73 | 73 | Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const |
74 | 74 | { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } |
75 | 75 | Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const |
78 | 78 | void flatten(ExtrusionEntityCollection* retval) const; |
79 | 79 | ExtrusionEntityCollection flatten() const; |
80 | 80 | double min_mm3_per_mm() const; |
81 | virtual double total_volume() const {double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } | |
81 | double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } | |
82 | 82 | |
83 | 83 | // Following methods shall never be called on an ExtrusionEntityCollection. |
84 | 84 | Polyline as_polyline() const { |
85 | 85 | CONFESS("Calling as_polyline() on a ExtrusionEntityCollection"); |
86 | 86 | return Polyline(); |
87 | 87 | }; |
88 | virtual double length() const { | |
88 | ||
89 | void collect_polylines(Polylines &dst) const override { | |
90 | for (ExtrusionEntity* extrusion_entity : this->entities) | |
91 | extrusion_entity->collect_polylines(dst); | |
92 | } | |
93 | ||
94 | double length() const override { | |
89 | 95 | CONFESS("Calling length() on a ExtrusionEntityCollection"); |
90 | 96 | return 0.; |
91 | 97 | } |
130 | 130 | // no rotation is supported for this infill pattern (yet) |
131 | 131 | BoundingBox bb = expolygon.contour.bounding_box(); |
132 | 132 | // Density adjusted to have a good %of weight. |
133 | double density_adjusted = std::max(0., params.density * 2.); | |
133 | double density_adjusted = std::max(0., params.density * 2.44); | |
134 | 134 | // Distance between the gyroid waves in scaled coordinates. |
135 | 135 | coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); |
136 | 136 |
13 | 13 | virtual Fill* clone() const { return new FillGyroid(*this); } |
14 | 14 | |
15 | 15 | // require bridge flow since most of this pattern hangs in air |
16 | virtual bool use_bridge_flow() const { return true; } | |
16 | virtual bool use_bridge_flow() const { return false; } | |
17 | 17 | |
18 | 18 | protected: |
19 | 19 | virtual void _fill_surface_single( |
85 | 85 | Polylines paths; |
86 | 86 | { |
87 | 87 | Polylines p; |
88 | for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) | |
89 | p.push_back((Polyline)(*it)); | |
88 | for (Polygon &poly : polygons) | |
89 | p.emplace_back(poly.points); | |
90 | 90 | paths = intersection_pl(p, to_polygons(expolygon)); |
91 | 91 | } |
92 | 92 |
114 | 114 | // 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. |
115 | 115 | float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), |
116 | 116 | (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value), |
117 | false); | |
117 | // bridge_flow_ratio | |
118 | 0.f); | |
118 | 119 | } |
119 | 120 | |
120 | 121 | Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) |
126 | 127 | (width.value > 0) ? width : object->config.extrusion_width, |
127 | 128 | float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), |
128 | 129 | (layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)), |
129 | false); | |
130 | // bridge_flow_ratio | |
131 | 0.f); | |
130 | 132 | } |
131 | 133 | |
132 | 134 | Flow support_material_interface_flow(const PrintObject *object, float layer_height) |
138 | 140 | // 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. |
139 | 141 | float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)), |
140 | 142 | (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value), |
141 | false); | |
143 | // bridge_flow_ratio | |
144 | 0.f); | |
142 | 145 | } |
143 | 146 | |
144 | 147 | } |
70 | 70 | |
71 | 71 | const char* NAME_KEY = "name"; |
72 | 72 | const char* MODIFIER_KEY = "modifier"; |
73 | const char* VOLUME_TYPE_KEY = "volume_type"; | |
73 | 74 | |
74 | 75 | const unsigned int VALID_OBJECT_TYPES_COUNT = 1; |
75 | 76 | const char* VALID_OBJECT_TYPES[] = |
602 | 603 | |
603 | 604 | if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr)) |
604 | 605 | return false; |
605 | ||
606 | object.second->center_around_origin(); | |
607 | 606 | } |
608 | 607 | |
609 | 608 | // fixes the min z of the model if negative |
1500 | 1499 | if (metadata.key == NAME_KEY) |
1501 | 1500 | volume->name = metadata.value; |
1502 | 1501 | else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) |
1503 | volume->modifier = true; | |
1502 | volume->set_type(ModelVolume::PARAMETER_MODIFIER); | |
1503 | else if (metadata.key == VOLUME_TYPE_KEY) | |
1504 | volume->set_type(ModelVolume::type_from_string(metadata.value)); | |
1504 | 1505 | else |
1505 | 1506 | volume->config.set_deserialize(metadata.key, metadata.value); |
1506 | 1507 | } |
2016 | 2017 | if (!volume->name.empty()) |
2017 | 2018 | stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; |
2018 | 2019 | |
2019 | // stores volume's modifier field | |
2020 | if (volume->modifier) | |
2020 | // stores volume's modifier field (legacy, to support old slicers) | |
2021 | if (volume->is_modifier()) | |
2021 | 2022 | stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; |
2023 | // stores volume's type (overrides the modifier field above) | |
2024 | stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " << | |
2025 | VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n"; | |
2022 | 2026 | |
2023 | 2027 | // stores volume's config data |
2024 | 2028 | for (const std::string& key : volume->config.keys()) |
457 | 457 | p = end + 1; |
458 | 458 | } |
459 | 459 | m_object->layer_height_profile_valid = true; |
460 | } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume && strcmp(opt_key, "modifier") == 0) { | |
461 | // Is this volume a modifier volume? | |
462 | m_volume->modifier = atoi(m_value[1].c_str()) == 1; | |
460 | } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { | |
461 | if (strcmp(opt_key, "modifier") == 0) { | |
462 | // Is this volume a modifier volume? | |
463 | // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag. | |
464 | m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); | |
465 | } else if (strcmp(opt_key, "volume_type") == 0) { | |
466 | m_volume->set_type(ModelVolume::type_from_string(m_value[1])); | |
467 | } | |
463 | 468 | } |
464 | 469 | } else if (m_path.size() == 3) { |
465 | 470 | if (m_path[1] == NODE_TYPE_MATERIAL) { |
780 | 785 | stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n"; |
781 | 786 | if (!volume->name.empty()) |
782 | 787 | stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n"; |
783 | if (volume->modifier) | |
788 | if (volume->is_modifier()) | |
784 | 789 | stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n"; |
790 | stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n"; | |
785 | 791 | for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) { |
786 | 792 | stream << " <triangle>\n"; |
787 | 793 | for (int j = 0; j < 3; ++j) |
154 | 154 | // the wipe tower has been completely covered by the tool change extrusions, |
155 | 155 | // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. |
156 | 156 | virtual bool layer_finished() const = 0; |
157 | ||
158 | // Returns used filament length per extruder: | |
159 | virtual std::vector<float> get_used_filament() const = 0; | |
160 | ||
161 | // Returns total number of toolchanges: | |
162 | virtual int get_number_of_toolchanges() const = 0; | |
157 | 163 | }; |
158 | 164 | |
159 | 165 | }; // namespace Slic3r |
110 | 110 | const WipeTower::xy start_pos_rotated() const { return m_start_pos; } |
111 | 111 | 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); } |
112 | 112 | float elapsed_time() const { return m_elapsed_time; } |
113 | float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } | |
113 | 114 | |
114 | 115 | // Extrude with an explicitely provided amount of extrusion. |
115 | Writer& extrude_explicit(float x, float y, float e, float f = 0.f) | |
116 | Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false) | |
116 | 117 | { |
117 | 118 | if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) |
118 | 119 | // Neither extrusion nor a travel move. |
121 | 122 | float dx = x - m_current_pos.x; |
122 | 123 | float dy = y - m_current_pos.y; |
123 | 124 | double len = sqrt(dx*dx+dy*dy); |
125 | if (record_length) | |
126 | m_used_filament_length += e; | |
124 | 127 | |
125 | 128 | |
126 | 129 | // Now do the "internal rotation" with respect to the wipe tower center |
161 | 164 | return *this; |
162 | 165 | } |
163 | 166 | |
164 | Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f) | |
165 | { return extrude_explicit(dest.x, dest.y, e, f); } | |
167 | Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false) | |
168 | { return extrude_explicit(dest.x, dest.y, e, f, record_length); } | |
166 | 169 | |
167 | 170 | // Travel to a new XY position. f=0 means use the current value. |
168 | 171 | Writer& travel(float x, float y, float f = 0.f) |
176 | 179 | { |
177 | 180 | float dx = x - m_current_pos.x; |
178 | 181 | float dy = y - m_current_pos.y; |
179 | return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f); | |
182 | return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); | |
180 | 183 | } |
181 | 184 | |
182 | 185 | Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) |
258 | 261 | // extrude quickly amount e to x2 with feed f. |
259 | 262 | Writer& ram(float x1, float x2, float dy, float e0, float e, float f) |
260 | 263 | { |
261 | extrude_explicit(x1, m_current_pos.y + dy, e0, f); | |
262 | extrude_explicit(x2, m_current_pos.y, e); | |
264 | extrude_explicit(x1, m_current_pos.y + dy, e0, f, true); | |
265 | extrude_explicit(x2, m_current_pos.y, e, 0.f, true); | |
263 | 266 | return *this; |
264 | 267 | } |
265 | 268 | |
403 | 406 | float m_last_fan_speed = 0.f; |
404 | 407 | int current_temp = -1; |
405 | 408 | const float m_default_analyzer_line_width; |
409 | float m_used_filament_length = 0.f; | |
406 | 410 | |
407 | 411 | std::string set_format_X(float x) |
408 | 412 | { |
524 | 528 | ++ m_num_tool_changes; |
525 | 529 | } |
526 | 530 | |
531 | m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear | |
532 | // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) | |
533 | ||
527 | 534 | // Reset the extruder current to a normal value. |
528 | 535 | writer.set_extruder_trimpot(550) |
529 | 536 | .feedrate(6000) |
535 | 542 | |
536 | 543 | // so that tool_change() will know to extrude the wipe tower brim: |
537 | 544 | m_print_brim = true; |
545 | ||
546 | // Ask our writer about how much material was consumed: | |
547 | m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); | |
538 | 548 | |
539 | 549 | ToolChangeResult result; |
540 | 550 | result.priming = true; |
605 | 615 | toolchange_Load(writer, cleaning_box); |
606 | 616 | writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road |
607 | 617 | toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. |
618 | ++ m_num_tool_changes; | |
608 | 619 | } else |
609 | 620 | toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); |
610 | 621 | |
611 | ++ m_num_tool_changes; | |
612 | 622 | m_depth_traversed += wipe_area; |
613 | 623 | |
614 | 624 | if (last_change_in_layer) {// draw perimeter line |
631 | 641 | ";------------------\n" |
632 | 642 | "\n\n"); |
633 | 643 | |
644 | // Ask our writer about how much material was consumed: | |
645 | if (m_current_tool < m_used_filament_length.size()) | |
646 | m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); | |
647 | ||
634 | 648 | ToolChangeResult result; |
635 | 649 | result.priming = false; |
636 | 650 | result.print_z = this->m_z_pos; |
681 | 695 | ";-----------------------------------\n"); |
682 | 696 | |
683 | 697 | m_print_brim = false; // Mark the brim as extruded |
698 | ||
699 | // Ask our writer about how much material was consumed: | |
700 | m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); | |
684 | 701 | |
685 | 702 | ToolChangeResult result; |
686 | 703 | result.priming = false; |
803 | 820 | .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) |
804 | 821 | .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ |
805 | 822 | .resume_preview(); |
806 | ||
807 | if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait. | |
823 | if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait. | |
824 | // 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) | |
825 | // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). | |
808 | 826 | writer.set_extruder_temp(new_temperature, false); |
809 | 827 | m_old_temperature = new_temperature; |
810 | 828 | } |
848 | 866 | const unsigned int new_tool, |
849 | 867 | material_type new_material) |
850 | 868 | { |
869 | // Ask the writer about how much of the old filament we consumed: | |
870 | m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); | |
871 | ||
851 | 872 | // Speed override for the material. Go slow for flex and soluble materials. |
852 | 873 | int speed_override; |
853 | 874 | switch (new_material) { |
910 | 931 | const float& xl = cleaning_box.ld.x; |
911 | 932 | const float& xr = cleaning_box.rd.x; |
912 | 933 | |
913 | ||
914 | 934 | // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least |
915 | 935 | // the ordered volume, even if it means violating the box. This can later be removed and simply |
916 | 936 | // wipe until the end of the assigned area. |
925 | 945 | m_left_to_right = !m_left_to_right; |
926 | 946 | } |
927 | 947 | |
928 | ||
929 | 948 | // now the wiping itself: |
930 | 949 | for (int i = 0; true; ++i) { |
931 | 950 | if (i!=0) { |
934 | 953 | else if (wipe_speed < 2210.f) wipe_speed = 4200.f; |
935 | 954 | else wipe_speed = std::min(4800.f, wipe_speed + 50.f); |
936 | 955 | } |
937 | ||
956 | ||
938 | 957 | float traversed_x = writer.x(); |
939 | 958 | if (m_left_to_right) |
940 | 959 | writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); |
1049 | 1068 | |
1050 | 1069 | m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; |
1051 | 1070 | |
1071 | // Ask our writer about how much material was consumed. | |
1072 | if (m_current_tool < m_used_filament_length.size()) | |
1073 | m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); | |
1074 | ||
1052 | 1075 | ToolChangeResult result; |
1053 | 1076 | result.priming = false; |
1054 | 1077 | result.print_z = this->m_z_pos; |
1144 | 1167 | } |
1145 | 1168 | } |
1146 | 1169 | |
1147 | ||
1148 | 1170 | // Processes vector m_plan and calls respective functions to generate G-code for the wipe tower |
1149 | 1171 | // Resulting ToolChangeResults are appended into vector "result" |
1150 | 1172 | void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result) |
1166 | 1188 | |
1167 | 1189 | m_layer_info = m_plan.begin(); |
1168 | 1190 | m_current_tool = (unsigned int)(-2); // we don't know which extruder to start with - we'll set it according to the first toolchange |
1191 | for (auto& used : m_used_filament_length) // reset used filament stats | |
1192 | used = 0.f; | |
1169 | 1193 | |
1170 | 1194 | std::vector<WipeTower::ToolChangeResult> layer_result; |
1171 | 1195 | for (auto layer : m_plan) |
1207 | 1231 | } |
1208 | 1232 | } |
1209 | 1233 | |
1210 | ||
1211 | ||
1212 | ||
1213 | 1234 | void WipeTowerPrusaMM::make_wipe_tower_square() |
1214 | 1235 | { |
1215 | 1236 | const float width = m_wipe_tower_width - 3 * m_perimeter_width; |
1233 | 1254 | plan_tower(); // propagates depth downwards again (width has changed) |
1234 | 1255 | for (auto& lay : m_plan) // depths set, now the spacing |
1235 | 1256 | lay.extra_spacing = lay.depth / lay.toolchanges_depth(); |
1236 | ||
1237 | } | |
1238 | ||
1239 | ||
1257 | } | |
1240 | 1258 | |
1241 | 1259 | }; // namespace Slic3r |
45 | 45 | WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, |
46 | 46 | float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging, |
47 | 47 | const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) : |
48 | m_wipe_tower_pos(x, y), | |
48 | m_wipe_tower_pos(x, y), | |
49 | 49 | m_wipe_tower_width(width), |
50 | 50 | m_wipe_tower_rotation_angle(rotation_angle), |
51 | 51 | m_y_shift(0.f), |
93 | 93 | m_filpar[idx].ramming_step_multiplicator /= 100; |
94 | 94 | while (stream >> speed) |
95 | 95 | m_filpar[idx].ramming_speed.push_back(speed); |
96 | ||
97 | 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 | |
96 | 98 | } |
97 | 99 | |
98 | 100 | |
170 | 172 | virtual bool layer_finished() const { |
171 | 173 | return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); |
172 | 174 | } |
175 | ||
176 | virtual std::vector<float> get_used_filament() const override { return m_used_filament_length; } | |
177 | virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } | |
173 | 178 | |
174 | 179 | |
175 | 180 | private: |
330 | 335 | std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) |
331 | 336 | std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end(); |
332 | 337 | |
338 | // Stores information about used filament length per extruder: | |
339 | std::vector<float> m_used_filament_length; | |
340 | ||
333 | 341 | |
334 | 342 | // Returns gcode for wipe tower brim |
335 | 343 | // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower |
273 | 273 | } |
274 | 274 | return gcode_out; |
275 | 275 | } |
276 | ||
277 | 276 | |
278 | 277 | |
279 | 278 | std::string WipeTowerIntegration::prime(GCode &gcodegen) |
664 | 663 | _write_format(file, "\n"); |
665 | 664 | } |
666 | 665 | |
666 | // adds tags for time estimators | |
667 | if (print.config.remaining_times.value) | |
668 | { | |
669 | _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag); | |
670 | if (m_silent_time_estimator_enabled) | |
671 | _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); | |
672 | } | |
673 | ||
667 | 674 | // Prepare the helper object for replacing placeholders in custom G-code and output filename. |
668 | 675 | m_placeholder_parser = print.placeholder_parser; |
669 | 676 | m_placeholder_parser.update_timestamp(); |
723 | 730 | m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); |
724 | 731 | m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming); |
725 | 732 | std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id); |
726 | ||
727 | 733 | // Set bed temperature if the start G-code does not contain any bed temp control G-codes. |
728 | 734 | this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); |
729 | 735 | // Set extruder(s) temperature before and after start G-code. |
959 | 965 | |
960 | 966 | // Get filament stats. |
961 | 967 | print.filament_stats.clear(); |
962 | print.total_used_filament = 0.; | |
963 | print.total_extruded_volume = 0.; | |
964 | print.total_weight = 0.; | |
965 | print.total_cost = 0.; | |
968 | print.total_used_filament = 0.; | |
969 | print.total_extruded_volume = 0.; | |
970 | print.total_weight = 0.; | |
971 | print.total_cost = 0.; | |
972 | print.total_wipe_tower_cost = 0.; | |
973 | print.total_wipe_tower_filament = 0.; | |
966 | 974 | print.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); |
967 | 975 | print.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; |
968 | 976 | for (const Extruder &extruder : m_writer.extruders()) { |
969 | double used_filament = extruder.used_filament(); | |
970 | double extruded_volume = extruder.extruded_volume(); | |
977 | double used_filament = extruder.used_filament() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] : 0.f); | |
978 | 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 | |
971 | 979 | double filament_weight = extruded_volume * extruder.filament_density() * 0.001; |
972 | 980 | double filament_cost = filament_weight * extruder.filament_cost() * 0.001; |
981 | ||
973 | 982 | print.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament)); |
974 | 983 | _write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001); |
975 | 984 | if (filament_weight > 0.) { |
980 | 989 | _write_format(file, "; filament cost = %.1lf\n", filament_cost); |
981 | 990 | } |
982 | 991 | } |
983 | print.total_used_filament = print.total_used_filament + used_filament; | |
984 | print.total_extruded_volume = print.total_extruded_volume + extruded_volume; | |
992 | print.total_used_filament += used_filament; | |
993 | print.total_extruded_volume += extruded_volume; | |
994 | print.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; | |
995 | print.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; | |
985 | 996 | } |
986 | 997 | _write_format(file, "; total filament cost = %.1lf\n", print.total_cost); |
987 | 998 | _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); |
97 | 97 | void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } |
98 | 98 | std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); |
99 | 99 | std::string finalize(GCode &gcodegen); |
100 | std::vector<float> used_filament_length() const; | |
100 | 101 | |
101 | 102 | private: |
102 | 103 | WipeTowerIntegration& operator=(const WipeTowerIntegration&); |
167 | 167 | } |
168 | 168 | #endif // ENABLE_MOVE_STATS |
169 | 169 | |
170 | const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER"; | |
171 | const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; SILENT_FIRST_M73_OUTPUT_PLACEHOLDER"; | |
172 | ||
170 | 173 | GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) |
171 | 174 | : _mode(mode) |
172 | 175 | { |
293 | 296 | throw std::runtime_error(std::string("Remaining times export failed.\nError while reading from file.\n")); |
294 | 297 | } |
295 | 298 | |
296 | gcode_line += "\n"; | |
299 | // replaces placeholders for initial line M73 with the real lines | |
300 | if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || | |
301 | ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) | |
302 | { | |
303 | sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str()); | |
304 | gcode_line = time_line; | |
305 | } | |
306 | else | |
307 | gcode_line += "\n"; | |
297 | 308 | |
298 | 309 | // add remaining time lines where needed |
299 | 310 | _parser.parse_line(gcode_line, |
16 | 16 | class GCodeTimeEstimator |
17 | 17 | { |
18 | 18 | public: |
19 | static const std::string Normal_First_M73_Output_Placeholder_Tag; | |
20 | static const std::string Silent_First_M73_Output_Placeholder_Tag; | |
21 | ||
19 | 22 | enum EMode : unsigned char |
20 | 23 | { |
21 | 24 | Normal, |
20 | 20 | friend class Layer; |
21 | 21 | |
22 | 22 | public: |
23 | Layer* layer() { return this->_layer; } | |
24 | const Layer* layer() const { return this->_layer; } | |
25 | PrintRegion* region() { return this->_region; } | |
26 | const PrintRegion* region() const { return this->_region; } | |
23 | Layer* layer() { return this->_layer; } | |
24 | const Layer* layer() const { return this->_layer; } | |
25 | PrintRegion* region() { return this->_region; } | |
26 | const PrintRegion* region() const { return this->_region; } | |
27 | 27 | |
28 | // collection of surfaces generated by slicing the original geometry | |
29 | // divided by type top/bottom/internal | |
30 | SurfaceCollection slices; | |
31 | ||
32 | // collection of extrusion paths/loops filling gaps | |
33 | // These fills are generated by the perimeter generator. | |
34 | // They are not printed on their own, but they are copied to this->fills during infill generation. | |
35 | ExtrusionEntityCollection thin_fills; | |
28 | // Collection of surfaces generated by slicing the original geometry, divided by type top/bottom/internal. | |
29 | SurfaceCollection slices; | |
36 | 30 | |
37 | 31 | // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") |
38 | 32 | // and for re-starting of infills. |
39 | ExPolygons fill_expolygons; | |
33 | ExPolygons fill_expolygons; | |
40 | 34 | // collection of surfaces for infill generation |
41 | SurfaceCollection fill_surfaces; | |
35 | SurfaceCollection fill_surfaces; | |
36 | // Collection of extrusion paths/loops filling gaps. | |
37 | // These fills are generated by the perimeter generator. | |
38 | // They are not printed on their own, but they are copied to this->fills during infill generation. | |
39 | ExtrusionEntityCollection thin_fills; | |
42 | 40 | |
43 | // Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces). | |
44 | // While not necessary, the memory consumption is meager and it speeds up calculation. | |
45 | // The perimeter_surfaces keep the IDs of the slices (top/bottom/) | |
46 | SurfaceCollection perimeter_surfaces; | |
47 | ||
48 | // collection of expolygons representing the bridged areas (thus not | |
49 | // needing support material) | |
50 | Polygons bridged; | |
41 | // Collection of expolygons representing the bridged areas (thus not needing support material). | |
42 | //FIXME Not used as of now. | |
43 | Polygons bridged; | |
51 | 44 | |
52 | 45 | // collection of polylines representing the unsupported bridge edges |
53 | PolylineCollection unsupported_bridge_edges; | |
46 | PolylineCollection unsupported_bridge_edges; | |
54 | 47 | |
55 | // ordered collection of extrusion paths/loops to build all perimeters | |
56 | // (this collection contains only ExtrusionEntityCollection objects) | |
57 | ExtrusionEntityCollection perimeters; | |
58 | ||
59 | // ordered collection of extrusion paths to fill surfaces | |
60 | // (this collection contains only ExtrusionEntityCollection objects) | |
61 | ExtrusionEntityCollection fills; | |
48 | // Ordered collection of extrusion paths/loops to build all perimeters. | |
49 | // This collection contains only ExtrusionEntityCollection objects. | |
50 | ExtrusionEntityCollection perimeters; | |
51 | // Ordered collection of extrusion paths to fill surfaces. | |
52 | // This collection contains only ExtrusionEntityCollection objects. | |
53 | ExtrusionEntityCollection fills; | |
62 | 54 | |
63 | 55 | Flow flow(FlowRole role, bool bridge = false, double width = -1) const; |
64 | 56 | void slices_to_fill_surfaces_clipped(); |
14 | 14 | |
15 | 15 | namespace Slic3r { |
16 | 16 | |
17 | Flow | |
18 | LayerRegion::flow(FlowRole role, bool bridge, double width) const | |
17 | Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const | |
19 | 18 | { |
20 | 19 | return this->_region->flow( |
21 | 20 | role, |
50 | 49 | } |
51 | 50 | } |
52 | 51 | |
53 | void | |
54 | LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) | |
52 | void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) | |
55 | 53 | { |
56 | 54 | this->perimeters.clear(); |
57 | 55 | this->thin_fills.clear(); |
339 | 337 | #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ |
340 | 338 | } |
341 | 339 | |
342 | void | |
343 | LayerRegion::prepare_fill_surfaces() | |
340 | void LayerRegion::prepare_fill_surfaces() | |
344 | 341 | { |
345 | 342 | #ifdef SLIC3R_DEBUG_SLICE_PROCESSING |
346 | 343 | export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); |
381 | 378 | #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ |
382 | 379 | } |
383 | 380 | |
384 | double | |
385 | LayerRegion::infill_area_threshold() const | |
381 | double LayerRegion::infill_area_threshold() const | |
386 | 382 | { |
387 | 383 | double ss = this->flow(frSolidInfill).scaled_spacing(); |
388 | 384 | return ss*ss; |
454 | 454 | unsigned int Model::get_auto_extruder_id(unsigned int max_extruders) |
455 | 455 | { |
456 | 456 | unsigned int id = s_auto_extruder_id; |
457 | ||
458 | if (++s_auto_extruder_id > max_extruders) | |
457 | if (id > max_extruders) { | |
458 | // The current counter is invalid, likely due to switching the printer profiles | |
459 | // to a profile with a lower number of extruders. | |
459 | 460 | reset_auto_extruder_id(); |
460 | ||
461 | id = s_auto_extruder_id; | |
462 | } else if (++s_auto_extruder_id > max_extruders) { | |
463 | reset_auto_extruder_id(); | |
464 | } | |
461 | 465 | return id; |
462 | 466 | } |
463 | 467 | |
608 | 612 | if (! m_bounding_box_valid) { |
609 | 613 | BoundingBoxf3 raw_bbox; |
610 | 614 | for (const ModelVolume *v : this->volumes) |
611 | if (! v->modifier) | |
615 | if (v->is_model_part()) | |
616 | // mesh.bounding_box() returns a cached value. | |
612 | 617 | raw_bbox.merge(v->mesh.bounding_box()); |
613 | 618 | BoundingBoxf3 bb; |
614 | 619 | for (const ModelInstance *i : this->instances) |
639 | 644 | { |
640 | 645 | TriangleMesh mesh; |
641 | 646 | for (const ModelVolume *v : this->volumes) |
642 | if (! v->modifier) | |
647 | if (v->is_model_part()) | |
643 | 648 | mesh.merge(v->mesh); |
644 | 649 | return mesh; |
645 | 650 | } |
650 | 655 | { |
651 | 656 | BoundingBoxf3 bb; |
652 | 657 | for (const ModelVolume *v : this->volumes) |
653 | if (! v->modifier) { | |
658 | if (v->is_model_part()) { | |
654 | 659 | if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances"); |
655 | 660 | bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true)); |
656 | 661 | } |
662 | 667 | { |
663 | 668 | BoundingBoxf3 bb; |
664 | 669 | for (ModelVolume *v : this->volumes) |
665 | if (! v->modifier) | |
670 | if (v->is_model_part()) | |
666 | 671 | bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate)); |
667 | 672 | return bb; |
668 | 673 | } |
673 | 678 | // center this object around the origin |
674 | 679 | BoundingBoxf3 bb; |
675 | 680 | for (ModelVolume *v : this->volumes) |
676 | if (! v->modifier) | |
681 | if (v->is_model_part()) | |
677 | 682 | bb.merge(v->mesh.bounding_box()); |
678 | 683 | |
679 | 684 | // first align to origin on XYZ |
777 | 782 | { |
778 | 783 | size_t num = 0; |
779 | 784 | for (const ModelVolume *v : this->volumes) |
780 | if (! v->modifier) | |
785 | if (v->is_model_part()) | |
781 | 786 | num += v->mesh.stl.stats.number_of_facets; |
782 | 787 | return num; |
783 | 788 | } |
785 | 790 | bool ModelObject::needed_repair() const |
786 | 791 | { |
787 | 792 | for (const ModelVolume *v : this->volumes) |
788 | if (! v->modifier && v->mesh.needed_repair()) | |
793 | if (v->is_model_part() && v->mesh.needed_repair()) | |
789 | 794 | return true; |
790 | 795 | return false; |
791 | 796 | } |
801 | 806 | lower->input_file = ""; |
802 | 807 | |
803 | 808 | for (ModelVolume *volume : this->volumes) { |
804 | if (volume->modifier) { | |
809 | if (! volume->is_model_part()) { | |
805 | 810 | // don't cut modifiers |
806 | 811 | upper->add_volume(*volume); |
807 | 812 | lower->add_volume(*volume); |
853 | 858 | ModelVolume* new_volume = new_object->add_volume(*mesh); |
854 | 859 | new_volume->name = volume->name; |
855 | 860 | new_volume->config = volume->config; |
856 | new_volume->modifier = volume->modifier; | |
861 | new_volume->set_type(volume->type()); | |
857 | 862 | new_volume->material_id(volume->material_id()); |
858 | 863 | |
859 | 864 | new_objects->push_back(new_object); |
867 | 872 | { |
868 | 873 | for (const ModelVolume* vol : this->volumes) |
869 | 874 | { |
870 | if (!vol->modifier) | |
875 | if (vol->is_model_part()) | |
871 | 876 | { |
872 | 877 | for (ModelInstance* inst : this->instances) |
873 | 878 | { |
971 | 976 | return m_convex_hull; |
972 | 977 | } |
973 | 978 | |
979 | TriangleMesh& ModelVolume::get_convex_hull() | |
980 | { | |
981 | return m_convex_hull; | |
982 | } | |
983 | ||
984 | ModelVolume::Type ModelVolume::type_from_string(const std::string &s) | |
985 | { | |
986 | // Legacy support | |
987 | if (s == "0") | |
988 | return MODEL_PART; | |
989 | if (s == "1") | |
990 | return PARAMETER_MODIFIER; | |
991 | // New type (supporting the support enforcers & blockers) | |
992 | if (s == "ModelPart") | |
993 | return MODEL_PART; | |
994 | if (s == "ParameterModifier") | |
995 | return PARAMETER_MODIFIER; | |
996 | if (s == "SupportEnforcer") | |
997 | return SUPPORT_ENFORCER; | |
998 | if (s == "SupportBlocker") | |
999 | return SUPPORT_BLOCKER; | |
1000 | } | |
1001 | ||
1002 | std::string ModelVolume::type_to_string(const Type t) | |
1003 | { | |
1004 | switch (t) { | |
1005 | case MODEL_PART: return "ModelPart"; | |
1006 | case PARAMETER_MODIFIER: return "ParameterModifier"; | |
1007 | case SUPPORT_ENFORCER: return "SupportEnforcer"; | |
1008 | case SUPPORT_BLOCKER: return "SupportBlocker"; | |
1009 | default: | |
1010 | assert(false); | |
1011 | return "ModelPart"; | |
1012 | } | |
1013 | } | |
1014 | ||
974 | 1015 | // Split this volume, append the result to the object owning this volume. |
975 | 1016 | // Return the number of volumes created from this one. |
976 | 1017 | // This is useful to assign different materials to different volumes of an object. |
164 | 164 | // Configuration parameters specific to an object model geometry or a modifier volume, |
165 | 165 | // overriding the global Slic3r settings and the ModelObject settings. |
166 | 166 | DynamicPrintConfig config; |
167 | // Is it an object to be printed, or a modifier volume? | |
168 | bool modifier; | |
169 | ||
167 | ||
168 | enum Type { | |
169 | MODEL_TYPE_INVALID = -1, | |
170 | MODEL_PART = 0, | |
171 | PARAMETER_MODIFIER, | |
172 | SUPPORT_ENFORCER, | |
173 | SUPPORT_BLOCKER, | |
174 | }; | |
175 | ||
170 | 176 | // A parent object owning this modifier volume. |
171 | ModelObject* get_object() const { return this->object; }; | |
177 | ModelObject* get_object() const { return this->object; }; | |
178 | Type type() const { return m_type; } | |
179 | void set_type(const Type t) { m_type = t; } | |
180 | bool is_model_part() const { return m_type == MODEL_PART; } | |
181 | bool is_modifier() const { return m_type == PARAMETER_MODIFIER; } | |
182 | bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; } | |
183 | bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; } | |
172 | 184 | t_model_material_id material_id() const { return this->_material_id; } |
173 | void material_id(t_model_material_id material_id); | |
174 | ModelMaterial* material() const; | |
175 | void set_material(t_model_material_id material_id, const ModelMaterial &material); | |
185 | void material_id(t_model_material_id material_id); | |
186 | ModelMaterial* material() const; | |
187 | void set_material(t_model_material_id material_id, const ModelMaterial &material); | |
176 | 188 | // Split this volume, append the result to the object owning this volume. |
177 | 189 | // Return the number of volumes created from this one. |
178 | 190 | // This is useful to assign different materials to different volumes of an object. |
182 | 194 | |
183 | 195 | void calculate_convex_hull(); |
184 | 196 | const TriangleMesh& get_convex_hull() const; |
197 | TriangleMesh& get_convex_hull(); | |
198 | ||
199 | // Helpers for loading / storing into AMF / 3MF files. | |
200 | static Type type_from_string(const std::string &s); | |
201 | static std::string type_to_string(const Type t); | |
185 | 202 | |
186 | 203 | private: |
187 | 204 | // Parent object owning this ModelVolume. |
188 | ModelObject* object; | |
189 | t_model_material_id _material_id; | |
190 | ||
191 | ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) | |
205 | ModelObject* object; | |
206 | // Is it an object to be printed, or a modifier volume? | |
207 | Type m_type; | |
208 | t_model_material_id _material_id; | |
209 | ||
210 | ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object) | |
192 | 211 | { |
193 | 212 | if (mesh.stl.stats.number_of_facets > 1) |
194 | 213 | calculate_convex_hull(); |
195 | 214 | } |
196 | ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {} | |
215 | 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) {} | |
197 | 216 | ModelVolume(ModelObject *object, const ModelVolume &other) : |
198 | name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object) | |
217 | name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object) | |
199 | 218 | { |
200 | 219 | this->material_id(other.material_id()); |
201 | 220 | } |
202 | 221 | ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : |
203 | name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object) | |
222 | name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object) | |
204 | 223 | { |
205 | 224 | this->material_id(other.material_id()); |
206 | 225 | if (mesh.stl.stats.number_of_facets > 1) |
98 | 98 | |
99 | 99 | using SpatElement = std::pair<Box, unsigned>; |
100 | 100 | using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; |
101 | using ItemGroup = std::vector<std::reference_wrapper<Item>>; | |
102 | template<class TBin> | |
103 | using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>; | |
104 | ||
105 | const double BIG_ITEM_TRESHOLD = 0.02; | |
106 | ||
107 | Box boundingBox(const Box& pilebb, const Box& ibb ) { | |
108 | auto& pminc = pilebb.minCorner(); | |
109 | auto& pmaxc = pilebb.maxCorner(); | |
110 | auto& iminc = ibb.minCorner(); | |
111 | auto& imaxc = ibb.maxCorner(); | |
112 | PointImpl minc, maxc; | |
113 | ||
114 | setX(minc, std::min(getX(pminc), getX(iminc))); | |
115 | setY(minc, std::min(getY(pminc), getY(iminc))); | |
116 | ||
117 | setX(maxc, std::max(getX(pmaxc), getX(imaxc))); | |
118 | setY(maxc, std::max(getY(pmaxc), getY(imaxc))); | |
119 | return Box(minc, maxc); | |
120 | } | |
101 | 121 | |
102 | 122 | std::tuple<double /*score*/, Box /*farthest point from bin center*/> |
103 | 123 | objfunc(const PointImpl& bincenter, |
104 | double /*bin_area*/, | |
105 | ShapeLike::Shapes<PolygonImpl>& pile, // The currently arranged pile | |
106 | double /*pile_area*/, | |
124 | const shapelike::Shapes<PolygonImpl>& merged_pile, | |
125 | const Box& pilebb, | |
126 | const ItemGroup& items, | |
107 | 127 | const Item &item, |
128 | double bin_area, | |
108 | 129 | double norm, // A norming factor for physical dimensions |
109 | std::vector<double>& areacache, // pile item areas will be cached | |
110 | 130 | // a spatial index to quickly get neighbors of the candidate item |
111 | SpatIndex& spatindex | |
131 | const SpatIndex& spatindex, | |
132 | const SpatIndex& smalls_spatindex, | |
133 | const ItemGroup& remaining | |
112 | 134 | ) |
113 | 135 | { |
114 | using pl = PointLike; | |
115 | using sl = ShapeLike; | |
116 | ||
117 | static const double BIG_ITEM_TRESHOLD = 0.2; | |
136 | using Coord = TCoord<PointImpl>; | |
137 | ||
118 | 138 | static const double ROUNDNESS_RATIO = 0.5; |
119 | 139 | static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; |
120 | 140 | |
121 | 141 | // We will treat big items (compared to the print bed) differently |
122 | auto normarea = [norm](double area) { return std::sqrt(area)/norm; }; | |
123 | ||
124 | // If a new bin has been created: | |
125 | if(pile.size() < areacache.size()) { | |
126 | areacache.clear(); | |
127 | spatindex.clear(); | |
128 | } | |
129 | ||
130 | // We must fill the caches: | |
131 | int idx = 0; | |
132 | for(auto& p : pile) { | |
133 | if(idx == areacache.size()) { | |
134 | areacache.emplace_back(sl::area(p)); | |
135 | if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) | |
136 | spatindex.insert({sl::boundingBox(p), idx}); | |
137 | } | |
138 | ||
139 | idx++; | |
140 | } | |
142 | auto isBig = [bin_area](double a) { | |
143 | return a/bin_area > BIG_ITEM_TRESHOLD ; | |
144 | }; | |
141 | 145 | |
142 | 146 | // Candidate item bounding box |
143 | auto ibb = item.boundingBox(); | |
147 | auto ibb = sl::boundingBox(item.transformedShape()); | |
144 | 148 | |
145 | 149 | // Calculate the full bounding box of the pile with the candidate item |
146 | pile.emplace_back(item.transformedShape()); | |
147 | auto fullbb = ShapeLike::boundingBox(pile); | |
148 | pile.pop_back(); | |
150 | auto fullbb = boundingBox(pilebb, ibb); | |
149 | 151 | |
150 | 152 | // The bounding box of the big items (they will accumulate in the center |
151 | 153 | // of the pile |
156 | 158 | boost::geometry::convert(boostbb, bigbb); |
157 | 159 | } |
158 | 160 | |
159 | // The size indicator of the candidate item. This is not the area, | |
160 | // but almost... | |
161 | double item_normarea = normarea(item.area()); | |
162 | ||
163 | 161 | // Will hold the resulting score |
164 | 162 | double score = 0; |
165 | 163 | |
166 | if(item_normarea > BIG_ITEM_TRESHOLD) { | |
164 | if(isBig(item.area()) || spatindex.empty()) { | |
167 | 165 | // This branch is for the bigger items.. |
168 | // Here we will use the closest point of the item bounding box to | |
169 | // the already arranged pile. So not the bb center nor the a choosen | |
170 | // corner but whichever is the closest to the center. This will | |
171 | // prevent some unwanted strange arrangements. | |
172 | 166 | |
173 | 167 | auto minc = ibb.minCorner(); // bottom left corner |
174 | 168 | auto maxc = ibb.maxCorner(); // top right corner |
189 | 183 | |
190 | 184 | // The smalles distance from the arranged pile center: |
191 | 185 | auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; |
186 | auto bindist = pl::distance(ibb.center(), bincenter) / norm; | |
187 | dist = 0.8*dist + 0.2*bindist; | |
192 | 188 | |
193 | 189 | // Density is the pack density: how big is the arranged pile |
194 | auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | |
195 | ||
196 | // Prepare a variable for the alignment score. | |
197 | // This will indicate: how well is the candidate item aligned with | |
198 | // its neighbors. We will check the aligment with all neighbors and | |
199 | // return the score for the best alignment. So it is enough for the | |
200 | // candidate to be aligned with only one item. | |
201 | auto alignment_score = std::numeric_limits<double>::max(); | |
202 | ||
203 | auto& trsh = item.transformedShape(); | |
204 | ||
205 | auto querybb = item.boundingBox(); | |
206 | ||
207 | // Query the spatial index for the neigbours | |
208 | std::vector<SpatElement> result; | |
209 | spatindex.query(bgi::intersects(querybb), std::back_inserter(result)); | |
210 | ||
211 | for(auto& e : result) { // now get the score for the best alignment | |
212 | auto idx = e.second; | |
213 | auto& p = pile[idx]; | |
214 | auto parea = areacache[idx]; | |
215 | auto bb = sl::boundingBox(sl::Shapes<PolygonImpl>{p, trsh}); | |
216 | auto bbarea = bb.area(); | |
217 | auto ascore = 1.0 - (item.area() + parea)/bbarea; | |
218 | ||
219 | if(ascore < alignment_score) alignment_score = ascore; | |
190 | double density = 0; | |
191 | ||
192 | if(remaining.empty()) { | |
193 | ||
194 | auto mp = merged_pile; | |
195 | mp.emplace_back(item.transformedShape()); | |
196 | auto chull = sl::convexHull(mp); | |
197 | ||
198 | placers::EdgeCache<PolygonImpl> ec(chull); | |
199 | ||
200 | double circ = ec.circumference() / norm; | |
201 | double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; | |
202 | score = 0.5*circ + 0.5*bcirc; | |
203 | ||
204 | } else { | |
205 | // Prepare a variable for the alignment score. | |
206 | // This will indicate: how well is the candidate item aligned with | |
207 | // its neighbors. We will check the alignment with all neighbors and | |
208 | // return the score for the best alignment. So it is enough for the | |
209 | // candidate to be aligned with only one item. | |
210 | auto alignment_score = 1.0; | |
211 | ||
212 | density = std::sqrt((fullbb.width() / norm )* | |
213 | (fullbb.height() / norm)); | |
214 | auto querybb = item.boundingBox(); | |
215 | ||
216 | // Query the spatial index for the neighbors | |
217 | std::vector<SpatElement> result; | |
218 | result.reserve(spatindex.size()); | |
219 | if(isBig(item.area())) { | |
220 | spatindex.query(bgi::intersects(querybb), | |
221 | std::back_inserter(result)); | |
222 | } else { | |
223 | smalls_spatindex.query(bgi::intersects(querybb), | |
224 | std::back_inserter(result)); | |
225 | } | |
226 | ||
227 | for(auto& e : result) { // now get the score for the best alignment | |
228 | auto idx = e.second; | |
229 | Item& p = items[idx]; | |
230 | auto parea = p.area(); | |
231 | if(std::abs(1.0 - parea/item.area()) < 1e-6) { | |
232 | auto bb = boundingBox(p.boundingBox(), ibb); | |
233 | auto bbarea = bb.area(); | |
234 | auto ascore = 1.0 - (item.area() + parea)/bbarea; | |
235 | ||
236 | if(ascore < alignment_score) alignment_score = ascore; | |
237 | } | |
238 | } | |
239 | ||
240 | // The final mix of the score is the balance between the distance | |
241 | // from the full pile center, the pack density and the | |
242 | // alignment with the neighbors | |
243 | if(result.empty()) | |
244 | score = 0.5 * dist + 0.5 * density; | |
245 | else | |
246 | score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; | |
220 | 247 | } |
221 | ||
222 | // The final mix of the score is the balance between the distance | |
223 | // from the full pile center, the pack density and the | |
224 | // alignment with the neigbours | |
225 | auto C = 0.33; | |
226 | score = C * dist + C * density + C * alignment_score; | |
227 | ||
228 | } else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) { | |
229 | // If there are no big items, only small, we should consider the | |
230 | // density here as well to not get silly results | |
231 | auto bindist = pl::distance(ibb.center(), bincenter) / norm; | |
232 | auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | |
233 | score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density; | |
234 | 248 | } else { |
235 | 249 | // Here there are the small items that should be placed around the |
236 | 250 | // already processed bigger items. |
258 | 272 | |
259 | 273 | // The accuracy of optimization. |
260 | 274 | // Goes from 0.0 to 1.0 and scales performance as well |
261 | pcfg.accuracy = 0.6f; | |
275 | pcfg.accuracy = 0.65f; | |
276 | ||
277 | pcfg.parallel = true; | |
262 | 278 | } |
263 | 279 | |
264 | 280 | template<class TBin> |
267 | 283 | template<class TBin> |
268 | 284 | class _ArrBase { |
269 | 285 | protected: |
270 | using Placer = strategies::_NofitPolyPlacer<PolygonImpl, TBin>; | |
286 | ||
287 | using Placer = TPacker<TBin>; | |
271 | 288 | using Selector = FirstFitSelection; |
272 | using Packer = Arranger<Placer, Selector>; | |
289 | using Packer = Nester<Placer, Selector>; | |
273 | 290 | using PConfig = typename Packer::PlacementConfig; |
274 | 291 | using Distance = TCoord<PointImpl>; |
275 | using Pile = ShapeLike::Shapes<PolygonImpl>; | |
292 | using Pile = sl::Shapes<PolygonImpl>; | |
276 | 293 | |
277 | 294 | Packer pck_; |
278 | 295 | PConfig pconf_; // Placement configuration |
279 | 296 | double bin_area_; |
280 | std::vector<double> areacache_; | |
281 | 297 | SpatIndex rtree_; |
298 | SpatIndex smallsrtree_; | |
299 | double norm_; | |
300 | Pile merged_pile_; | |
301 | Box pilebb_; | |
302 | ItemGroup remaining_; | |
303 | ItemGroup items_; | |
282 | 304 | public: |
283 | 305 | |
284 | 306 | _ArrBase(const TBin& bin, Distance dist, |
285 | 307 | std::function<void(unsigned)> progressind): |
286 | pck_(bin, dist), bin_area_(ShapeLike::area<PolygonImpl>(bin)) | |
308 | pck_(bin, dist), bin_area_(sl::area(bin)), | |
309 | norm_(std::sqrt(sl::area(bin))) | |
287 | 310 | { |
288 | 311 | fillConfig(pconf_); |
312 | ||
313 | pconf_.before_packing = | |
314 | [this](const Pile& merged_pile, // merged pile | |
315 | const ItemGroup& items, // packed items | |
316 | const ItemGroup& remaining) // future items to be packed | |
317 | { | |
318 | items_ = items; | |
319 | merged_pile_ = merged_pile; | |
320 | remaining_ = remaining; | |
321 | ||
322 | pilebb_ = sl::boundingBox(merged_pile); | |
323 | ||
324 | rtree_.clear(); | |
325 | smallsrtree_.clear(); | |
326 | ||
327 | // We will treat big items (compared to the print bed) differently | |
328 | auto isBig = [this](double a) { | |
329 | return a/bin_area_ > BIG_ITEM_TRESHOLD ; | |
330 | }; | |
331 | ||
332 | for(unsigned idx = 0; idx < items.size(); ++idx) { | |
333 | Item& itm = items[idx]; | |
334 | if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx}); | |
335 | smallsrtree_.insert({itm.boundingBox(), idx}); | |
336 | } | |
337 | }; | |
338 | ||
289 | 339 | pck_.progressIndicator(progressind); |
290 | 340 | } |
291 | 341 | |
292 | 342 | template<class...Args> inline IndexedPackGroup operator()(Args&&...args) { |
293 | areacache_.clear(); | |
294 | return pck_.arrangeIndexed(std::forward<Args>(args)...); | |
343 | rtree_.clear(); | |
344 | return pck_.executeIndexed(std::forward<Args>(args)...); | |
295 | 345 | } |
296 | 346 | }; |
297 | 347 | |
303 | 353 | std::function<void(unsigned)> progressind): |
304 | 354 | _ArrBase<Box>(bin, dist, progressind) |
305 | 355 | { |
306 | pconf_.object_function = [this, bin] ( | |
307 | Pile& pile, | |
308 | const Item &item, | |
309 | double pile_area, | |
310 | double norm, | |
311 | double /*penality*/) { | |
312 | ||
313 | auto result = objfunc(bin.center(), bin_area_, pile, | |
314 | pile_area, item, norm, areacache_, rtree_); | |
356 | ||
357 | pconf_.object_function = [this, bin] (const Item &item) { | |
358 | ||
359 | auto result = objfunc(bin.center(), | |
360 | merged_pile_, | |
361 | pilebb_, | |
362 | items_, | |
363 | item, | |
364 | bin_area_, | |
365 | norm_, | |
366 | rtree_, | |
367 | smallsrtree_, | |
368 | remaining_); | |
369 | ||
315 | 370 | double score = std::get<0>(result); |
316 | 371 | auto& fullbb = std::get<1>(result); |
317 | 372 | |
318 | auto wdiff = fullbb.width() - bin.width(); | |
319 | auto hdiff = fullbb.height() - bin.height(); | |
320 | if(wdiff > 0) score += std::pow(wdiff, 2) / norm; | |
321 | if(hdiff > 0) score += std::pow(hdiff, 2) / norm; | |
373 | double miss = Placer::overfit(fullbb, bin); | |
374 | miss = miss > 0? miss : 0; | |
375 | score += miss*miss; | |
376 | ||
377 | return score; | |
378 | }; | |
379 | ||
380 | pck_.configure(pconf_); | |
381 | } | |
382 | }; | |
383 | ||
384 | using lnCircle = libnest2d::_Circle<libnest2d::PointImpl>; | |
385 | ||
386 | template<> | |
387 | class AutoArranger<lnCircle>: public _ArrBase<lnCircle> { | |
388 | public: | |
389 | ||
390 | AutoArranger(const lnCircle& bin, Distance dist, | |
391 | std::function<void(unsigned)> progressind): | |
392 | _ArrBase<lnCircle>(bin, dist, progressind) { | |
393 | ||
394 | pconf_.object_function = [this, &bin] (const Item &item) { | |
395 | ||
396 | auto result = objfunc(bin.center(), | |
397 | merged_pile_, | |
398 | pilebb_, | |
399 | items_, | |
400 | item, | |
401 | bin_area_, | |
402 | norm_, | |
403 | rtree_, | |
404 | smallsrtree_, | |
405 | remaining_); | |
406 | ||
407 | double score = std::get<0>(result); | |
408 | ||
409 | auto isBig = [this](const Item& itm) { | |
410 | return itm.area()/bin_area_ > BIG_ITEM_TRESHOLD ; | |
411 | }; | |
412 | ||
413 | if(isBig(item)) { | |
414 | auto mp = merged_pile_; | |
415 | mp.push_back(item.transformedShape()); | |
416 | auto chull = sl::convexHull(mp); | |
417 | double miss = Placer::overfit(chull, bin); | |
418 | if(miss < 0) miss = 0; | |
419 | score += miss*miss; | |
420 | } | |
322 | 421 | |
323 | 422 | return score; |
324 | 423 | }; |
334 | 433 | std::function<void(unsigned)> progressind): |
335 | 434 | _ArrBase<PolygonImpl>(bin, dist, progressind) |
336 | 435 | { |
337 | pconf_.object_function = [this, &bin] ( | |
338 | Pile& pile, | |
339 | const Item &item, | |
340 | double pile_area, | |
341 | double norm, | |
342 | double /*penality*/) { | |
343 | ||
344 | auto binbb = ShapeLike::boundingBox(bin); | |
345 | auto result = objfunc(binbb.center(), bin_area_, pile, | |
346 | pile_area, item, norm, areacache_, rtree_); | |
436 | pconf_.object_function = [this, &bin] (const Item &item) { | |
437 | ||
438 | auto binbb = sl::boundingBox(bin); | |
439 | auto result = objfunc(binbb.center(), | |
440 | merged_pile_, | |
441 | pilebb_, | |
442 | items_, | |
443 | item, | |
444 | bin_area_, | |
445 | norm_, | |
446 | rtree_, | |
447 | smallsrtree_, | |
448 | remaining_); | |
347 | 449 | double score = std::get<0>(result); |
348 | ||
349 | pile.emplace_back(item.transformedShape()); | |
350 | auto chull = ShapeLike::convexHull(pile); | |
351 | pile.pop_back(); | |
352 | ||
353 | // If it does not fit into the print bed we will beat it with a | |
354 | // large penality. If we would not do this, there would be only one | |
355 | // big pile that doesn't care whether it fits onto the print bed. | |
356 | if(!Placer::wouldFit(chull, bin)) score += norm; | |
357 | 450 | |
358 | 451 | return score; |
359 | 452 | }; |
369 | 462 | AutoArranger(Distance dist, std::function<void(unsigned)> progressind): |
370 | 463 | _ArrBase<Box>(Box(0, 0), dist, progressind) |
371 | 464 | { |
372 | this->pconf_.object_function = [this] ( | |
373 | Pile& pile, | |
374 | const Item &item, | |
375 | double pile_area, | |
376 | double norm, | |
377 | double /*penality*/) { | |
378 | ||
379 | auto result = objfunc({0, 0}, 0, pile, pile_area, | |
380 | item, norm, areacache_, rtree_); | |
465 | this->pconf_.object_function = [this] (const Item &item) { | |
466 | ||
467 | auto result = objfunc({0, 0}, | |
468 | merged_pile_, | |
469 | pilebb_, | |
470 | items_, | |
471 | item, | |
472 | 0, | |
473 | norm_, | |
474 | rtree_, | |
475 | smallsrtree_, | |
476 | remaining_); | |
381 | 477 | return std::get<0>(result); |
382 | 478 | }; |
383 | 479 | |
439 | 535 | return ret; |
440 | 536 | } |
441 | 537 | |
442 | enum BedShapeHint { | |
538 | class Circle { | |
539 | Point center_; | |
540 | double radius_; | |
541 | public: | |
542 | ||
543 | inline Circle(): center_(0, 0), radius_(std::nan("")) {} | |
544 | inline Circle(const Point& c, double r): center_(c), radius_(r) {} | |
545 | ||
546 | inline double radius() const { return radius_; } | |
547 | inline const Point& center() const { return center_; } | |
548 | inline operator bool() { return !std::isnan(radius_); } | |
549 | }; | |
550 | ||
551 | enum class BedShapeType { | |
443 | 552 | BOX, |
444 | 553 | CIRCLE, |
445 | 554 | IRREGULAR, |
446 | 555 | WHO_KNOWS |
447 | 556 | }; |
448 | 557 | |
449 | BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) { | |
558 | struct BedShapeHint { | |
559 | BedShapeType type; | |
560 | /*union*/ struct { // I know but who cares... | |
561 | Circle circ; | |
562 | BoundingBox box; | |
563 | Polyline polygon; | |
564 | } shape; | |
565 | }; | |
566 | ||
567 | BedShapeHint bedShape(const Polyline& bed) { | |
568 | static const double E = 10/SCALING_FACTOR; | |
569 | ||
570 | BedShapeHint ret; | |
571 | ||
572 | auto width = [](const BoundingBox& box) { | |
573 | return box.max.x - box.min.x; | |
574 | }; | |
575 | ||
576 | auto height = [](const BoundingBox& box) { | |
577 | return box.max.y - box.min.y; | |
578 | }; | |
579 | ||
580 | auto area = [&width, &height](const BoundingBox& box) { | |
581 | double w = width(box); | |
582 | double h = height(box); | |
583 | return w*h; | |
584 | }; | |
585 | ||
586 | auto poly_area = [](Polyline p) { | |
587 | Polygon pp; pp.points.reserve(p.points.size() + 1); | |
588 | pp.points = std::move(p.points); | |
589 | pp.points.emplace_back(pp.points.front()); | |
590 | return std::abs(pp.area()); | |
591 | }; | |
592 | ||
593 | ||
594 | auto bb = bed.bounding_box(); | |
595 | ||
596 | auto isCircle = [bb](const Polyline& polygon) { | |
597 | auto center = bb.center(); | |
598 | std::vector<double> vertex_distances; | |
599 | double avg_dist = 0; | |
600 | for (auto pt: polygon.points) | |
601 | { | |
602 | double distance = center.distance_to(pt); | |
603 | vertex_distances.push_back(distance); | |
604 | avg_dist += distance; | |
605 | } | |
606 | ||
607 | avg_dist /= vertex_distances.size(); | |
608 | ||
609 | Circle ret(center, avg_dist); | |
610 | for (auto el: vertex_distances) | |
611 | { | |
612 | if (abs(el - avg_dist) > 10 * SCALED_EPSILON) | |
613 | ret = Circle(); | |
614 | break; | |
615 | } | |
616 | ||
617 | return ret; | |
618 | }; | |
619 | ||
620 | auto parea = poly_area(bed); | |
621 | ||
622 | if( (1.0 - parea/area(bb)) < 1e-3 ) { | |
623 | ret.type = BedShapeType::BOX; | |
624 | ret.shape.box = bb; | |
625 | } | |
626 | else if(auto c = isCircle(bed)) { | |
627 | ret.type = BedShapeType::CIRCLE; | |
628 | ret.shape.circ = c; | |
629 | } else { | |
630 | ret.type = BedShapeType::IRREGULAR; | |
631 | ret.shape.polygon = bed; | |
632 | } | |
633 | ||
450 | 634 | // Determine the bed shape by hand |
451 | return BOX; | |
635 | return ret; | |
452 | 636 | } |
453 | 637 | |
454 | 638 | void applyResult( |
524 | 708 | }); |
525 | 709 | |
526 | 710 | IndexedPackGroup result; |
527 | BoundingBox bbb(bed.points); | |
711 | ||
712 | if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); | |
713 | ||
714 | BoundingBox bbb(bed); | |
528 | 715 | |
529 | 716 | auto binbb = Box({ |
530 | 717 | static_cast<libnest2d::Coord>(bbb.min.x), |
535 | 722 | static_cast<libnest2d::Coord>(bbb.max.y) |
536 | 723 | }); |
537 | 724 | |
538 | switch(bedhint) { | |
539 | case BOX: { | |
725 | switch(bedhint.type) { | |
726 | case BedShapeType::BOX: { | |
540 | 727 | |
541 | 728 | // Create the arranger for the box shaped bed |
542 | 729 | AutoArranger<Box> arrange(binbb, min_obj_distance, progressind); |
546 | 733 | result = arrange(shapes.begin(), shapes.end()); |
547 | 734 | break; |
548 | 735 | } |
549 | case CIRCLE: | |
736 | case BedShapeType::CIRCLE: { | |
737 | ||
738 | auto c = bedhint.shape.circ; | |
739 | auto cc = lnCircle({c.center().x, c.center().y} , c.radius()); | |
740 | ||
741 | AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind); | |
742 | result = arrange(shapes.begin(), shapes.end()); | |
550 | 743 | break; |
551 | case IRREGULAR: | |
552 | case WHO_KNOWS: { | |
744 | } | |
745 | case BedShapeType::IRREGULAR: | |
746 | case BedShapeType::WHO_KNOWS: { | |
747 | ||
553 | 748 | using P = libnest2d::PolygonImpl; |
554 | 749 | |
555 | 750 | auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); |
556 | P irrbed = ShapeLike::create<PolygonImpl>(std::move(ctour)); | |
557 | ||
558 | // std::cout << ShapeLike::toString(irrbed) << std::endl; | |
751 | P irrbed = sl::create<PolygonImpl>(std::move(ctour)); | |
559 | 752 | |
560 | 753 | AutoArranger<P> arrange(irrbed, min_obj_distance, progressind); |
561 | 754 | |
565 | 758 | break; |
566 | 759 | } |
567 | 760 | }; |
761 | ||
762 | if(result.empty()) return false; | |
568 | 763 | |
569 | 764 | if(first_bin_only) { |
570 | 765 | applyResult(result.front(), 0, shapemap); |
33 | 33 | Point first_point() const; |
34 | 34 | virtual Point last_point() const = 0; |
35 | 35 | virtual Lines lines() const = 0; |
36 | size_t size() const { return points.size(); } | |
37 | bool empty() const { return points.empty(); } | |
36 | 38 | double length() const; |
37 | bool is_valid() const { return this->points.size() >= 2; } | |
39 | bool is_valid() const { return this->points.size() >= 2; } | |
38 | 40 | |
39 | 41 | int find_point(const Point &point) const; |
40 | 42 | bool has_boundary_point(const Point &point) const; |
102 | 104 | extern BoundingBox get_extents_rotated(const std::vector<Point> &points, double angle); |
103 | 105 | extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle); |
104 | 106 | |
107 | inline double length(const Points &pts) { | |
108 | double total = 0; | |
109 | if (! pts.empty()) { | |
110 | auto it = pts.begin(); | |
111 | for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev) | |
112 | total += it->distance_to(*it_prev); | |
113 | } | |
114 | return total; | |
115 | } | |
116 | ||
117 | inline double area(const Points &polygon) { | |
118 | double area = 0.; | |
119 | for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++) | |
120 | area += double(polygon[j].x + polygon[i].x) * double(polygon[i].y - polygon[j].y); | |
121 | return area; | |
122 | } | |
123 | ||
105 | 124 | } // namespace Slic3r |
106 | 125 | |
107 | 126 | #endif |
149 | 149 | m_map.emplace(std::make_pair(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2), std::move(value))); |
150 | 150 | } |
151 | 151 | |
152 | // Erase a data point equal to value. (ValueType has to declare the operator==). | |
153 | // Returns true if the data point equal to value was found and removed. | |
154 | bool erase(const ValueType &value) { | |
155 | const Point *pt = m_point_accessor(value); | |
156 | if (pt != nullptr) { | |
157 | // Range of fragment starts around grid_corner, close to pt. | |
158 | auto range = m_map.equal_range(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2)); | |
159 | // Remove the first item. | |
160 | for (auto it = range.first; it != range.second; ++ it) { | |
161 | if (it->second == value) { | |
162 | m_map.erase(it); | |
163 | return true; | |
164 | } | |
165 | } | |
166 | } | |
167 | return false; | |
168 | } | |
169 | ||
152 | 170 | // Return a pair of <ValueType*, distance_squared> |
153 | 171 | std::pair<const ValueType*, double> find(const Point &pt) { |
154 | 172 | // Iterate over 4 closest grid cells around pt, |
176 | 194 | } |
177 | 195 | } |
178 | 196 | } |
179 | return (value_min != nullptr && dist_min < coordf_t(m_search_radius * m_search_radius)) ? | |
197 | return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? | |
180 | 198 | std::make_pair(value_min, dist_min) : |
181 | 199 | std::make_pair(nullptr, std::numeric_limits<double>::max()); |
182 | 200 | } |
102 | 102 | p.rotate(cos_angle, sin_angle); |
103 | 103 | } |
104 | 104 | |
105 | inline void polygons_reverse(Polygons &polys) | |
106 | { | |
107 | for (Polygon &p : polys) | |
108 | p.reverse(); | |
109 | } | |
110 | ||
105 | 111 | inline Points to_points(const Polygon &poly) |
106 | 112 | { |
107 | 113 | return poly.points; |
192 | 192 | } |
193 | 193 | } |
194 | 194 | |
195 | bool | |
196 | Polyline::is_straight() const | |
197 | { | |
198 | /* Check that each segment's direction is equal to the line connecting | |
199 | first point and last point. (Checking each line against the previous | |
200 | one would cause the error to accumulate.) */ | |
195 | bool Polyline::is_straight() const | |
196 | { | |
197 | // Check that each segment's direction is equal to the line connecting | |
198 | // first point and last point. (Checking each line against the previous | |
199 | // one would cause the error to accumulate.) | |
201 | 200 | double dir = Line(this->first_point(), this->last_point()).direction(); |
202 | ||
203 | Lines lines = this->lines(); | |
204 | for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { | |
205 | if (!line->parallel_to(dir)) return false; | |
206 | } | |
201 | for (const auto &line: this->lines()) | |
202 | if (! line.parallel_to(dir)) | |
203 | return false; | |
207 | 204 | return true; |
208 | 205 | } |
209 | 206 | |
210 | std::string | |
211 | Polyline::wkt() const | |
207 | std::string Polyline::wkt() const | |
212 | 208 | { |
213 | 209 | std::ostringstream wkt; |
214 | 210 | wkt << "LINESTRING(("; |
18 | 18 | Polyline() {}; |
19 | 19 | Polyline(const Polyline &other) : MultiPoint(other.points) {} |
20 | 20 | Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} |
21 | explicit Polyline(const Points &points) : MultiPoint(points) {} | |
22 | explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {} | |
21 | 23 | Polyline& operator=(const Polyline &other) { points = other.points; return *this; } |
22 | 24 | Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; } |
23 | 25 | static Polyline new_scale(std::vector<Pointf> points) { |
78 | 80 | |
79 | 81 | inline double total_length(const Polylines &polylines) { |
80 | 82 | double total = 0; |
81 | for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) | |
82 | total += it->length(); | |
83 | for (const Polyline &pl : polylines) | |
84 | total += pl.length(); | |
83 | 85 | return total; |
84 | 86 | } |
85 | 87 |
361 | 361 | // Invalidate all print steps. |
362 | 362 | this->invalidate_all_steps(); |
363 | 363 | |
364 | for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { | |
364 | size_t volume_id = 0; | |
365 | for (const ModelVolume *volume : model_object->volumes) { | |
366 | if (! volume->is_model_part() && ! volume->is_modifier()) | |
367 | continue; | |
365 | 368 | // Get the config applied to this volume. |
366 | PrintRegionConfig config = this->_region_config_from_model_volume(*model_object->volumes[volume_id]); | |
369 | PrintRegionConfig config = this->_region_config_from_model_volume(*volume); | |
367 | 370 | // Find an existing print region with the same config. |
368 | 371 | size_t region_id = size_t(-1); |
369 | 372 | for (size_t i = 0; i < this->regions.size(); ++ i) |
378 | 381 | } |
379 | 382 | // Assign volume to a region. |
380 | 383 | object->add_region_volume(region_id, volume_id); |
384 | ++ volume_id; | |
381 | 385 | } |
382 | 386 | |
383 | 387 | // Apply config to print object. |
852 | 856 | for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { |
853 | 857 | ModelVolume *volume = model_object->volumes[volume_id]; |
854 | 858 | //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. |
855 | if (! volume->material_id().empty() && ! volume->config.has("extruder")) | |
859 | if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder")) | |
856 | 860 | volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1); |
857 | 861 | } |
858 | 862 | } |
1192 | 1196 | } |
1193 | 1197 | m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>( |
1194 | 1198 | wipe_tower.tool_change((unsigned int)-1, false)); |
1199 | ||
1200 | m_wipe_tower_used_filament = wipe_tower.get_used_filament(); | |
1201 | m_wipe_tower_number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); | |
1195 | 1202 | } |
1196 | 1203 | |
1197 | 1204 | std::string Print::output_filename() |
79 | 79 | |
80 | 80 | Print* print() { return this->_print; } |
81 | 81 | Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; |
82 | // Average diameter of nozzles participating on extruding this region. | |
82 | 83 | coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; |
84 | // Average diameter of nozzles participating on extruding this region. | |
85 | coordf_t bridging_height_avg(const PrintConfig &print_config) const; | |
83 | 86 | |
84 | 87 | private: |
85 | 88 | Print* _print; |
210 | 213 | |
211 | 214 | bool is_printable() const { return !this->_shifted_copies.empty(); } |
212 | 215 | |
216 | // Helpers to slice support enforcer / blocker meshes by the support generator. | |
217 | std::vector<ExPolygons> slice_support_enforcers() const; | |
218 | std::vector<ExPolygons> slice_support_blockers() const; | |
219 | ||
213 | 220 | private: |
214 | 221 | Print* _print; |
215 | 222 | ModelObject* _model_object; |
221 | 228 | ~PrintObject() {} |
222 | 229 | |
223 | 230 | std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier); |
231 | std::vector<ExPolygons> _slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const; | |
224 | 232 | }; |
225 | 233 | |
226 | 234 | typedef std::vector<PrintObject*> PrintObjectPtrs; |
239 | 247 | // TODO: status_cb |
240 | 248 | std::string estimated_normal_print_time; |
241 | 249 | std::string estimated_silent_print_time; |
242 | double total_used_filament, total_extruded_volume, total_cost, total_weight; | |
250 | double total_used_filament, total_extruded_volume, total_cost, total_weight, total_wipe_tower_cost, total_wipe_tower_filament; | |
243 | 251 | std::map<size_t, float> filament_stats; |
244 | 252 | PrintState<PrintStep, psCount> state; |
245 | 253 | |
308 | 316 | std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_priming; |
309 | 317 | std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes; |
310 | 318 | std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_final_purge; |
319 | std::vector<float> m_wipe_tower_used_filament; | |
320 | int m_wipe_tower_number_of_toolchanges = -1; | |
311 | 321 | |
312 | 322 | std::string output_filename(); |
313 | 323 | std::string output_filepath(const std::string &path); |
0 | 0 | #include "PrintConfig.hpp" |
1 | 1 | #include "I18N.hpp" |
2 | 2 | |
3 | #include <algorithm> | |
3 | 4 | #include <set> |
4 | 5 | #include <boost/algorithm/string/replace.hpp> |
5 | 6 | #include <boost/algorithm/string/case_conv.hpp> |
122 | 123 | def->tooltip = L("Speed for printing bridges."); |
123 | 124 | def->sidetext = L("mm/s"); |
124 | 125 | def->cli = "bridge-speed=f"; |
125 | def->aliases.push_back("bridge_feed_rate"); | |
126 | def->aliases = { "bridge_feed_rate" }; | |
126 | 127 | def->min = 0; |
127 | 128 | def->default_value = new ConfigOptionFloat(60); |
128 | 129 | |
235 | 236 | def->tooltip = L("Distance used for the auto-arrange feature of the plater."); |
236 | 237 | def->sidetext = L("mm"); |
237 | 238 | def->cli = "duplicate-distance=f"; |
238 | def->aliases.push_back("multiply_distance"); | |
239 | def->aliases = { "multiply_distance" }; | |
239 | 240 | def->min = 0; |
240 | 241 | def->default_value = new ConfigOptionFloat(6); |
241 | 242 | |
296 | 297 | def->enum_labels.push_back(L("Archimedean Chords")); |
297 | 298 | def->enum_labels.push_back(L("Octagram Spiral")); |
298 | 299 | // solid_fill_pattern is an obsolete equivalent to external_fill_pattern. |
299 | def->aliases.push_back("solid_fill_pattern"); | |
300 | def->aliases = { "solid_fill_pattern" }; | |
300 | 301 | def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear); |
301 | 302 | |
302 | 303 | def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); |
884 | 885 | def->tooltip = L("Speed for printing the internal fill. Set to zero for auto."); |
885 | 886 | def->sidetext = L("mm/s"); |
886 | 887 | def->cli = "infill-speed=f"; |
887 | def->aliases.push_back("print_feed_rate"); | |
888 | def->aliases.push_back("infill_feed_rate"); | |
888 | def->aliases = { "print_feed_rate", "infill_feed_rate" }; | |
889 | 889 | def->min = 0; |
890 | 890 | def->default_value = new ConfigOptionFloat(80); |
891 | 891 | |
1250 | 1250 | def->category = L("Extruders"); |
1251 | 1251 | def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1."); |
1252 | 1252 | def->cli = "perimeter-extruder=i"; |
1253 | def->aliases.push_back("perimeters_extruder"); | |
1253 | def->aliases = { "perimeters_extruder" }; | |
1254 | 1254 | def->min = 1; |
1255 | 1255 | def->default_value = new ConfigOptionInt(1); |
1256 | 1256 | |
1263 | 1263 | "If expressed as percentage (for example 200%) it will be computed over layer height."); |
1264 | 1264 | def->sidetext = L("mm or % (leave 0 for default)"); |
1265 | 1265 | def->cli = "perimeter-extrusion-width=s"; |
1266 | def->aliases.push_back("perimeters_extrusion_width"); | |
1266 | def->aliases = { "perimeters_extrusion_width" }; | |
1267 | 1267 | def->default_value = new ConfigOptionFloatOrPercent(0, false); |
1268 | 1268 | |
1269 | 1269 | def = this->add("perimeter_speed", coFloat); |
1272 | 1272 | def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto."); |
1273 | 1273 | def->sidetext = L("mm/s"); |
1274 | 1274 | def->cli = "perimeter-speed=f"; |
1275 | def->aliases.push_back("perimeter_feed_rate"); | |
1275 | def->aliases = { "perimeter_feed_rate" }; | |
1276 | 1276 | def->min = 0; |
1277 | 1277 | def->default_value = new ConfigOptionFloat(60); |
1278 | 1278 | |
1285 | 1285 | "if the Extra Perimeters option is enabled."); |
1286 | 1286 | def->sidetext = L("(minimum)"); |
1287 | 1287 | def->cli = "perimeters=i"; |
1288 | def->aliases.push_back("perimeter_offsets"); | |
1288 | def->aliases = { "perimeter_offsets" }; | |
1289 | 1289 | def->min = 0; |
1290 | 1290 | def->default_value = new ConfigOptionInt(3); |
1291 | 1291 | |
1613 | 1613 | def->sidetext = L("mm/s or %"); |
1614 | 1614 | def->cli = "solid-infill-speed=s"; |
1615 | 1615 | def->ratio_over = "infill_speed"; |
1616 | def->aliases.push_back("solid_infill_feed_rate"); | |
1616 | def->aliases = { "solid_infill_feed_rate" }; | |
1617 | 1617 | def->min = 0; |
1618 | 1618 | def->default_value = new ConfigOptionFloatOrPercent(20, false); |
1619 | 1619 | |
1695 | 1695 | def->cli = "support-material!"; |
1696 | 1696 | def->default_value = new ConfigOptionBool(false); |
1697 | 1697 | |
1698 | def = this->add("support_material_auto", coBool); | |
1699 | def->label = L("Auto generated supports"); | |
1700 | def->category = L("Support material"); | |
1701 | def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\ | |
1702 | " If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only."); | |
1703 | def->cli = "support-material-auto!"; | |
1704 | def->default_value = new ConfigOptionBool(true); | |
1705 | ||
1698 | 1706 | def = this->add("support_material_xy_spacing", coFloatOrPercent); |
1699 | 1707 | def->label = L("XY separation between an object and its support"); |
1700 | 1708 | def->category = L("Support material"); |
1733 | 1741 | "for the first object layer."); |
1734 | 1742 | def->sidetext = L("mm"); |
1735 | 1743 | def->cli = "support-material-contact-distance=f"; |
1736 | def->min = 0; | |
1744 | // def->min = 0; | |
1737 | 1745 | def->enum_values.push_back("0"); |
1738 | 1746 | def->enum_values.push_back("0.2"); |
1739 | 1747 | def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str()); |
1959 | 1967 | def->tooltip = L("Speed for travel moves (jumps between distant extrusion points)."); |
1960 | 1968 | def->sidetext = L("mm/s"); |
1961 | 1969 | def->cli = "travel-speed=f"; |
1962 | def->aliases.push_back("travel_feed_rate"); | |
1970 | def->aliases = { "travel_feed_rate" }; | |
1963 | 1971 | def->min = 1; |
1964 | 1972 | def->default_value = new ConfigOptionFloat(130); |
1965 | 1973 | |
2233 | 2241 | return fpc.validate(); |
2234 | 2242 | } |
2235 | 2243 | |
2244 | size_t DynamicPrintConfig::remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message) | |
2245 | { | |
2246 | size_t n_removed_keys = 0; | |
2247 | for (const std::string &key : this->keys()) { | |
2248 | if (! default_config.has(key)) { | |
2249 | if (removed_keys_message.empty()) | |
2250 | removed_keys_message = key; | |
2251 | else { | |
2252 | removed_keys_message += ", "; | |
2253 | removed_keys_message += key; | |
2254 | } | |
2255 | this->erase(key); | |
2256 | ++ n_removed_keys; | |
2257 | } | |
2258 | } | |
2259 | return n_removed_keys; | |
2260 | } | |
2261 | ||
2236 | 2262 | double PrintConfig::min_object_distance() const |
2237 | 2263 | { |
2238 | 2264 | return PrintConfig::min_object_distance(static_cast<const ConfigBase*>(this)); |
166 | 166 | |
167 | 167 | // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. |
168 | 168 | std::string validate(); |
169 | ||
170 | // Remove all keys not in "valid_keys", return number of removed keys and add the list of keys to "removed_keys_message. | |
171 | // valid_keys has to be sorted lexicographically. | |
172 | size_t remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message); | |
169 | 173 | |
170 | 174 | // Verify whether the opt_key has not been obsoleted or renamed. |
171 | 175 | // Both opt_key and value may be modified by handle_legacy(). |
322 | 326 | ConfigOptionFloatOrPercent extrusion_width; |
323 | 327 | ConfigOptionFloatOrPercent first_layer_height; |
324 | 328 | ConfigOptionBool infill_only_where_needed; |
329 | // Force the generation of solid shells between adjacent materials/volumes. | |
325 | 330 | ConfigOptionBool interface_shells; |
326 | 331 | ConfigOptionFloat layer_height; |
327 | 332 | ConfigOptionInt raft_layers; |
329 | 334 | // ConfigOptionFloat seam_preferred_direction; |
330 | 335 | // ConfigOptionFloat seam_preferred_direction_jitter; |
331 | 336 | ConfigOptionBool support_material; |
337 | // Automatic supports (generated based on support_material_threshold). | |
338 | ConfigOptionBool support_material_auto; | |
339 | // Direction of the support pattern (in XY plane). | |
332 | 340 | ConfigOptionFloat support_material_angle; |
333 | 341 | ConfigOptionBool support_material_buildplate_only; |
334 | 342 | ConfigOptionFloat support_material_contact_distance; |
338 | 346 | ConfigOptionBool support_material_interface_contact_loops; |
339 | 347 | ConfigOptionInt support_material_interface_extruder; |
340 | 348 | ConfigOptionInt support_material_interface_layers; |
349 | // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. | |
341 | 350 | ConfigOptionFloat support_material_interface_spacing; |
342 | 351 | ConfigOptionFloatOrPercent support_material_interface_speed; |
343 | 352 | ConfigOptionEnum<SupportMaterialPattern> support_material_pattern; |
353 | // Spacing between support material lines (the hatching distance). | |
344 | 354 | ConfigOptionFloat support_material_spacing; |
345 | 355 | ConfigOptionFloat support_material_speed; |
346 | 356 | ConfigOptionBool support_material_synchronize_layers; |
357 | // Overhang angle threshold. | |
347 | 358 | ConfigOptionInt support_material_threshold; |
348 | 359 | ConfigOptionBool support_material_with_sheath; |
349 | 360 | ConfigOptionFloatOrPercent support_material_xy_spacing; |
366 | 377 | // OPT_PTR(seam_preferred_direction); |
367 | 378 | // OPT_PTR(seam_preferred_direction_jitter); |
368 | 379 | OPT_PTR(support_material); |
380 | OPT_PTR(support_material_auto); | |
369 | 381 | OPT_PTR(support_material_angle); |
370 | 382 | OPT_PTR(support_material_buildplate_only); |
371 | 383 | OPT_PTR(support_material_contact_distance); |
413 | 425 | ConfigOptionInt infill_every_layers; |
414 | 426 | ConfigOptionFloatOrPercent infill_overlap; |
415 | 427 | ConfigOptionFloat infill_speed; |
428 | // Detect bridging perimeters | |
416 | 429 | ConfigOptionBool overhangs; |
417 | 430 | ConfigOptionInt perimeter_extruder; |
418 | 431 | ConfigOptionFloatOrPercent perimeter_extrusion_width; |
419 | 432 | ConfigOptionFloat perimeter_speed; |
433 | // Total number of perimeters. | |
420 | 434 | ConfigOptionInt perimeters; |
421 | 435 | ConfigOptionFloatOrPercent small_perimeter_speed; |
422 | 436 | ConfigOptionFloat solid_infill_below_area; |
424 | 438 | ConfigOptionFloatOrPercent solid_infill_extrusion_width; |
425 | 439 | ConfigOptionInt solid_infill_every_layers; |
426 | 440 | ConfigOptionFloatOrPercent solid_infill_speed; |
441 | // Detect thin walls. | |
427 | 442 | ConfigOptionBool thin_walls; |
428 | 443 | ConfigOptionFloatOrPercent top_infill_extrusion_width; |
429 | 444 | ConfigOptionInt top_solid_layers; |
171 | 171 | steps.emplace_back(posSlice); |
172 | 172 | } else if ( |
173 | 173 | opt_key == "support_material" |
174 | || opt_key == "support_material_auto" | |
174 | 175 | || opt_key == "support_material_angle" |
175 | 176 | || opt_key == "support_material_buildplate_only" |
176 | 177 | || opt_key == "support_material_enforce_layers" |
913 | 914 | #if 1 |
914 | 915 | // Intentionally inflate a bit more than how much the region has been shrunk, |
915 | 916 | // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). |
916 | shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); | |
917 | shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); | |
917 | 918 | if (shell.empty()) |
918 | 919 | continue; |
919 | 920 | #else |
1319 | 1320 | |
1320 | 1321 | std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier) |
1321 | 1322 | { |
1323 | std::vector<const ModelVolume*> volumes; | |
1324 | if (region_id < this->region_volumes.size()) { | |
1325 | for (int volume_id : this->region_volumes[region_id]) { | |
1326 | const ModelVolume *volume = this->model_object()->volumes[volume_id]; | |
1327 | if (modifier ? volume->is_modifier() : volume->is_model_part()) | |
1328 | volumes.emplace_back(volume); | |
1329 | } | |
1330 | } | |
1331 | return this->_slice_volumes(z, volumes); | |
1332 | } | |
1333 | ||
1334 | std::vector<ExPolygons> PrintObject::slice_support_enforcers() const | |
1335 | { | |
1336 | std::vector<const ModelVolume*> volumes; | |
1337 | for (const ModelVolume *volume : this->model_object()->volumes) | |
1338 | if (volume->is_support_enforcer()) | |
1339 | volumes.emplace_back(volume); | |
1340 | std::vector<float> zs; | |
1341 | zs.reserve(this->layers.size()); | |
1342 | for (const Layer *l : this->layers) | |
1343 | zs.emplace_back(l->slice_z); | |
1344 | return this->_slice_volumes(zs, volumes); | |
1345 | } | |
1346 | ||
1347 | std::vector<ExPolygons> PrintObject::slice_support_blockers() const | |
1348 | { | |
1349 | std::vector<const ModelVolume*> volumes; | |
1350 | for (const ModelVolume *volume : this->model_object()->volumes) | |
1351 | if (volume->is_support_blocker()) | |
1352 | volumes.emplace_back(volume); | |
1353 | std::vector<float> zs; | |
1354 | zs.reserve(this->layers.size()); | |
1355 | for (const Layer *l : this->layers) | |
1356 | zs.emplace_back(l->slice_z); | |
1357 | return this->_slice_volumes(zs, volumes); | |
1358 | } | |
1359 | ||
1360 | std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const | |
1361 | { | |
1322 | 1362 | std::vector<ExPolygons> layers; |
1323 | if (region_id < this->region_volumes.size()) { | |
1324 | std::vector<int> &volumes = this->region_volumes[region_id]; | |
1325 | if (! volumes.empty()) { | |
1326 | // Compose mesh. | |
1327 | //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. | |
1328 | TriangleMesh mesh; | |
1329 | for (int volume_id : volumes) { | |
1330 | ModelVolume *volume = this->model_object()->volumes[volume_id]; | |
1331 | if (volume->modifier == modifier) | |
1332 | mesh.merge(volume->mesh); | |
1333 | } | |
1334 | if (mesh.stl.stats.number_of_facets > 0) { | |
1335 | // transform mesh | |
1336 | // we ignore the per-instance transformations currently and only | |
1337 | // consider the first one | |
1338 | this->model_object()->instances.front()->transform_mesh(&mesh, true); | |
1339 | // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift | |
1340 | mesh.translate(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z)); | |
1341 | // perform actual slicing | |
1342 | TriangleMeshSlicer mslicer(&mesh); | |
1343 | mslicer.slice(z, &layers); | |
1344 | } | |
1363 | if (! volumes.empty()) { | |
1364 | // Compose mesh. | |
1365 | //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. | |
1366 | TriangleMesh mesh; | |
1367 | for (const ModelVolume *v : volumes) | |
1368 | mesh.merge(v->mesh); | |
1369 | if (mesh.stl.stats.number_of_facets > 0) { | |
1370 | // transform mesh | |
1371 | // we ignore the per-instance transformations currently and only | |
1372 | // consider the first one | |
1373 | this->model_object()->instances.front()->transform_mesh(&mesh, true); | |
1374 | // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift | |
1375 | mesh.translate(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z)); | |
1376 | // perform actual slicing | |
1377 | TriangleMeshSlicer mslicer(&mesh); | |
1378 | mslicer.slice(z, &layers); | |
1345 | 1379 | } |
1346 | 1380 | } |
1347 | 1381 | return layers; |
56 | 56 | print_config.nozzle_diameter.get_at(this->config.solid_infill_extruder.value - 1)) / 3.; |
57 | 57 | } |
58 | 58 | |
59 | coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const | |
60 | { | |
61 | return this->nozzle_dmr_avg(print_config) * sqrt(this->config.bridge_flow_ratio.value); | |
59 | 62 | } |
63 | ||
64 | } |
223 | 223 | // 1) Initialize the SlicingAdaptive class with the object meshes. |
224 | 224 | SlicingAdaptive as; |
225 | 225 | as.set_slicing_parameters(slicing_params); |
226 | for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it) | |
227 | if (! (*it)->modifier) | |
228 | as.add_mesh(&(*it)->mesh); | |
226 | for (const ModelVolume *volume : volumes) | |
227 | if (volume->is_model_part()) | |
228 | as.add_mesh(&volume->mesh); | |
229 | 229 | as.prepare(); |
230 | 230 | |
231 | 231 | // 2) Generate layers using the algorithm of @platsch |
247 | 247 | #ifdef SLIC3R_DEBUG |
248 | 248 | static int iRun = 0; |
249 | 249 | iRun ++; |
250 | for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) | |
250 | for (const MyLayer *layer : top_contacts) | |
251 | 251 | Slic3r::SVG::export_expolygons( |
252 | debug_out_path("support-top-contacts-%d-%lf.svg", iRun, (*it)->print_z), | |
253 | union_ex((*it)->polygons, false)); | |
252 | debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z), | |
253 | union_ex(layer->polygons, false)); | |
254 | 254 | #endif /* SLIC3R_DEBUG */ |
255 | 255 | |
256 | 256 | BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts"; |
281 | 281 | MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( |
282 | 282 | object, bottom_contacts, top_contacts, layer_storage); |
283 | 283 | |
284 | this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); | |
284 | // this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); | |
285 | this->trim_support_layers_by_object(object, top_contacts, | |
286 | m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, | |
287 | m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); | |
288 | ||
289 | #ifdef SLIC3R_DEBUG | |
290 | for (const MyLayer *layer : top_contacts) | |
291 | Slic3r::SVG::export_expolygons( | |
292 | debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z), | |
293 | union_ex(layer->polygons, false)); | |
294 | #endif | |
285 | 295 | |
286 | 296 | BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; |
287 | 297 | |
419 | 429 | { |
420 | 430 | // 1) Count the new polygons first. |
421 | 431 | size_t n_polygons_new = 0; |
422 | for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { | |
423 | const LayerRegion ®ion = *(*it_region); | |
424 | const SurfaceCollection &slices = region.slices; | |
425 | for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { | |
426 | const Surface &surface = *it; | |
432 | for (const LayerRegion *region : layer.regions) | |
433 | for (const Surface &surface : region->slices.surfaces) | |
427 | 434 | if (surface.surface_type == surface_type) |
428 | 435 | n_polygons_new += surface.expolygon.holes.size() + 1; |
429 | } | |
430 | } | |
431 | ||
432 | 436 | // 2) Collect the new polygons. |
433 | 437 | Polygons out; |
434 | 438 | out.reserve(n_polygons_new); |
435 | for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { | |
436 | const LayerRegion ®ion = *(*it_region); | |
437 | const SurfaceCollection &slices = region.slices; | |
438 | for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { | |
439 | const Surface &surface = *it; | |
439 | for (const LayerRegion *region : layer.regions) | |
440 | for (const Surface &surface : region->slices.surfaces) | |
440 | 441 | if (surface.surface_type == surface_type) |
441 | 442 | polygons_append(out, surface.expolygon); |
442 | } | |
443 | } | |
444 | ||
445 | 443 | return out; |
446 | 444 | } |
447 | 445 | |
451 | 449 | { |
452 | 450 | Polygons out; |
453 | 451 | out.reserve(out.size() + layer.slices.expolygons.size()); |
454 | for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it) | |
455 | out.push_back(it->contour); | |
452 | for (const ExPolygon &expoly : layer.slices.expolygons) | |
453 | out.emplace_back(expoly.contour); | |
456 | 454 | return out; |
457 | 455 | } |
458 | 456 | |
459 | 457 | class SupportGridPattern |
460 | 458 | { |
461 | 459 | public: |
460 | // Achtung! The support_polygons need to be trimmed by trimming_polygons, otherwise | |
461 | // the selection by island_samples (see the island_samples() method) will not work! | |
462 | 462 | SupportGridPattern( |
463 | // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) | |
463 | 464 | const Polygons &support_polygons, |
464 | const Polygons &trimming_polygons, | |
465 | // Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons. | |
466 | const Polygons &trimming_polygons, | |
467 | // Grid spacing, given by "support_material_spacing" + m_support_material_flow.spacing() | |
465 | 468 | coordf_t support_spacing, |
466 | 469 | coordf_t support_angle) : |
467 | 470 | m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons), |
483 | 486 | bbox.align_to_grid(grid_resolution); |
484 | 487 | m_grid.set_bbox(bbox); |
485 | 488 | m_grid.create(*m_support_polygons, grid_resolution); |
489 | #if 0 | |
490 | if (m_grid.has_intersecting_edges()) { | |
491 | // EdgeGrid fails to produce valid signed distance function for self-intersecting polygons. | |
492 | m_support_polygons_rotated = simplify_polygons(*m_support_polygons); | |
493 | m_support_polygons = &m_support_polygons_rotated; | |
494 | m_grid.set_bbox(bbox); | |
495 | m_grid.create(*m_support_polygons, grid_resolution); | |
496 | // assert(! m_grid.has_intersecting_edges()); | |
497 | printf("SupportGridPattern: fixing polygons with intersection %s\n", | |
498 | m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED"); | |
499 | } | |
500 | #endif | |
486 | 501 | m_grid.calculate_sdf(); |
487 | // Extract a bounding contour from the grid, trim by the object. | |
502 | // Sample a single point per input support polygon, keep it as a reference to maintain corresponding | |
503 | // polygons if ever these polygons get split into parts by the trimming polygons. | |
488 | 504 | m_island_samples = island_samples(*m_support_polygons); |
489 | 505 | } |
490 | 506 | |
492 | 508 | // and trim the extracted polygons by trimming_polygons. |
493 | 509 | // Trimming by the trimming_polygons may split the extracted polygons into pieces. |
494 | 510 | // Remove all the pieces, which do not contain any of the island_samples. |
495 | Polygons extract_support(const coord_t offset_in_grid) | |
511 | Polygons extract_support(const coord_t offset_in_grid, bool fill_holes) | |
496 | 512 | { |
497 | 513 | // Generate islands, so each island may be tested for overlap with m_island_samples. |
498 | ExPolygons islands = diff_ex( | |
499 | m_grid.contours_simplified(offset_in_grid), | |
500 | *m_trimming_polygons, false); | |
514 | assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); | |
515 | #ifdef SLIC3R_DEBUG | |
516 | Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); | |
517 | ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false); | |
518 | #else | |
519 | ExPolygons islands = diff_ex(m_grid.contours_simplified(offset_in_grid, fill_holes), *m_trimming_polygons, false); | |
520 | #endif | |
501 | 521 | |
502 | 522 | // Extract polygons, which contain some of the m_island_samples. |
503 | 523 | Polygons out; |
504 | std::vector<std::pair<Point,bool>> samples_inside; | |
505 | ||
506 | 524 | for (ExPolygon &island : islands) { |
507 | 525 | BoundingBox bbox = get_extents(island.contour); |
526 | // Samples are sorted lexicographically. | |
508 | 527 | auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), bbox.min - Point(1, 1)); |
509 | 528 | auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), bbox.max + Point(1, 1)); |
510 | samples_inside.clear(); | |
529 | std::vector<std::pair<Point,bool>> samples_inside; | |
511 | 530 | for (auto it = it_lower; it != it_upper; ++ it) |
512 | 531 | if (bbox.contains(*it)) |
513 | 532 | samples_inside.push_back(std::make_pair(*it, false)); |
548 | 567 | bbox.merge(get_extents(islands)); |
549 | 568 | if (!out.empty()) |
550 | 569 | bbox.merge(get_extents(out)); |
570 | if (!support_polygons_simplified.empty()) | |
571 | bbox.merge(get_extents(support_polygons_simplified)); | |
551 | 572 | SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox); |
573 | svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); | |
552 | 574 | svg.draw(islands, "red", 0.5f); |
553 | 575 | svg.draw(union_ex(out), "green", 0.5f); |
554 | 576 | svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); |
565 | 587 | return out; |
566 | 588 | } |
567 | 589 | |
590 | #ifdef SLIC3R_DEBUG | |
591 | void serialize(const std::string &path) | |
592 | { | |
593 | FILE *file = ::fopen(path.c_str(), "wb"); | |
594 | ::fwrite(&m_support_spacing, 8, 1, file); | |
595 | ::fwrite(&m_support_angle, 8, 1, file); | |
596 | uint32_t n_polygons = m_support_polygons->size(); | |
597 | ::fwrite(&n_polygons, 4, 1, file); | |
598 | for (uint32_t i = 0; i < n_polygons; ++ i) { | |
599 | const Polygon &poly = (*m_support_polygons)[i]; | |
600 | uint32_t n_points = poly.size(); | |
601 | ::fwrite(&n_points, 4, 1, file); | |
602 | for (uint32_t j = 0; j < n_points; ++ j) { | |
603 | const Point &pt = poly.points[j]; | |
604 | ::fwrite(&pt.x, sizeof(coord_t), 1, file); | |
605 | ::fwrite(&pt.y, sizeof(coord_t), 1, file); | |
606 | } | |
607 | } | |
608 | n_polygons = m_trimming_polygons->size(); | |
609 | ::fwrite(&n_polygons, 4, 1, file); | |
610 | for (uint32_t i = 0; i < n_polygons; ++ i) { | |
611 | const Polygon &poly = (*m_trimming_polygons)[i]; | |
612 | uint32_t n_points = poly.size(); | |
613 | ::fwrite(&n_points, 4, 1, file); | |
614 | for (uint32_t j = 0; j < n_points; ++ j) { | |
615 | const Point &pt = poly.points[j]; | |
616 | ::fwrite(&pt.x, sizeof(coord_t), 1, file); | |
617 | ::fwrite(&pt.y, sizeof(coord_t), 1, file); | |
618 | } | |
619 | } | |
620 | ::fclose(file); | |
621 | } | |
622 | ||
623 | static SupportGridPattern deserialize(const std::string &path, int which = -1) | |
624 | { | |
625 | SupportGridPattern out; | |
626 | out.deserialize_(path, which); | |
627 | return out; | |
628 | } | |
629 | ||
630 | // Deserialization constructor | |
631 | bool deserialize_(const std::string &path, int which = -1) | |
632 | { | |
633 | FILE *file = ::fopen(path.c_str(), "rb"); | |
634 | if (file == nullptr) | |
635 | return false; | |
636 | ||
637 | m_support_polygons = &m_support_polygons_deserialized; | |
638 | m_trimming_polygons = &m_trimming_polygons_deserialized; | |
639 | ||
640 | ::fread(&m_support_spacing, 8, 1, file); | |
641 | ::fread(&m_support_angle, 8, 1, file); | |
642 | //FIXME | |
643 | //m_support_spacing *= 0.01 / 2; | |
644 | uint32_t n_polygons; | |
645 | ::fread(&n_polygons, 4, 1, file); | |
646 | m_support_polygons_deserialized.reserve(n_polygons); | |
647 | int32_t scale = 1; | |
648 | for (uint32_t i = 0; i < n_polygons; ++ i) { | |
649 | Polygon poly; | |
650 | uint32_t n_points; | |
651 | ::fread(&n_points, 4, 1, file); | |
652 | poly.points.reserve(n_points); | |
653 | for (uint32_t j = 0; j < n_points; ++ j) { | |
654 | coord_t x, y; | |
655 | ::fread(&x, sizeof(coord_t), 1, file); | |
656 | ::fread(&y, sizeof(coord_t), 1, file); | |
657 | poly.points.emplace_back(Point(x * scale, y * scale)); | |
658 | } | |
659 | if (which == -1 || which == i) | |
660 | m_support_polygons_deserialized.emplace_back(std::move(poly)); | |
661 | printf("Polygon %d, area: %lf\n", i, area(poly.points)); | |
662 | } | |
663 | ::fread(&n_polygons, 4, 1, file); | |
664 | m_trimming_polygons_deserialized.reserve(n_polygons); | |
665 | for (uint32_t i = 0; i < n_polygons; ++ i) { | |
666 | Polygon poly; | |
667 | uint32_t n_points; | |
668 | ::fread(&n_points, 4, 1, file); | |
669 | poly.points.reserve(n_points); | |
670 | for (uint32_t j = 0; j < n_points; ++ j) { | |
671 | coord_t x, y; | |
672 | ::fread(&x, sizeof(coord_t), 1, file); | |
673 | ::fread(&y, sizeof(coord_t), 1, file); | |
674 | poly.points.emplace_back(Point(x * scale, y * scale)); | |
675 | } | |
676 | m_trimming_polygons_deserialized.emplace_back(std::move(poly)); | |
677 | } | |
678 | ::fclose(file); | |
679 | ||
680 | m_support_polygons_deserialized = simplify_polygons(m_support_polygons_deserialized, false); | |
681 | //m_support_polygons_deserialized = to_polygons(union_ex(m_support_polygons_deserialized, false)); | |
682 | ||
683 | // Create an EdgeGrid, initialize it with projection, initialize signed distance field. | |
684 | coord_t grid_resolution = coord_t(scale_(m_support_spacing)); | |
685 | BoundingBox bbox = get_extents(*m_support_polygons); | |
686 | bbox.offset(20); | |
687 | bbox.align_to_grid(grid_resolution); | |
688 | m_grid.set_bbox(bbox); | |
689 | m_grid.create(*m_support_polygons, grid_resolution); | |
690 | m_grid.calculate_sdf(); | |
691 | // Sample a single point per input support polygon, keep it as a reference to maintain corresponding | |
692 | // polygons if ever these polygons get split into parts by the trimming polygons. | |
693 | m_island_samples = island_samples(*m_support_polygons); | |
694 | return true; | |
695 | } | |
696 | ||
697 | const Polygons& support_polygons() const { return *m_support_polygons; } | |
698 | const Polygons& trimming_polygons() const { return *m_trimming_polygons; } | |
699 | const EdgeGrid::Grid& grid() const { return m_grid; } | |
700 | ||
701 | #endif /* SLIC3R_DEBUG */ | |
702 | ||
568 | 703 | private: |
704 | SupportGridPattern() {} | |
569 | 705 | SupportGridPattern& operator=(const SupportGridPattern &rhs); |
570 | 706 | |
707 | #if 0 | |
571 | 708 | // Get some internal point of an expolygon, to be used as a representative |
572 | 709 | // sample to test, whether this island is inside another island. |
710 | //FIXME this was quick, but not sufficiently robust. | |
573 | 711 | static Point island_sample(const ExPolygon &expoly) |
574 | 712 | { |
575 | 713 | // Find the lowest point lexicographically. |
590 | 728 | double coef = 20. / sqrt(l2); |
591 | 729 | return Point(p2.x + coef * v.x, p2.y + coef * v.y); |
592 | 730 | } |
593 | ||
731 | #endif | |
732 | ||
733 | // Sample one internal point per expolygon. | |
734 | // FIXME this is quite an overkill to calculate a complete offset just to get a single point, but at least it is robust. | |
594 | 735 | static Points island_samples(const ExPolygons &expolygons) |
595 | 736 | { |
596 | 737 | Points pts; |
628 | 769 | coordf_t m_support_spacing; |
629 | 770 | |
630 | 771 | Slic3r::EdgeGrid::Grid m_grid; |
772 | // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding | |
773 | // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons. | |
631 | 774 | Points m_island_samples; |
775 | ||
776 | #ifdef SLIC3R_DEBUG | |
777 | // support for deserialization of m_support_polygons, m_trimming_polygons | |
778 | Polygons m_support_polygons_deserialized; | |
779 | Polygons m_trimming_polygons_deserialized; | |
780 | #endif /* SLIC3R_DEBUG */ | |
632 | 781 | }; |
782 | ||
783 | namespace SupportMaterialInternal { | |
784 | static inline bool has_bridging_perimeters(const ExtrusionLoop &loop) | |
785 | { | |
786 | for (const ExtrusionPath &ep : loop.paths) | |
787 | if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) | |
788 | return ep.size() >= (ep.is_closed() ? 3 : 2); | |
789 | return false; | |
790 | } | |
791 | static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters) | |
792 | { | |
793 | for (const ExtrusionEntity *ee : perimeters.entities) { | |
794 | if (ee->is_collection()) { | |
795 | for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) { | |
796 | assert(! ee2->is_collection()); | |
797 | if (ee2->is_loop()) | |
798 | if (has_bridging_perimeters(*static_cast<const ExtrusionLoop*>(ee2))) | |
799 | return true; | |
800 | } | |
801 | } else if (ee->is_loop() && has_bridging_perimeters(*static_cast<const ExtrusionLoop*>(ee))) | |
802 | return true; | |
803 | } | |
804 | return false; | |
805 | } | |
806 | static bool has_bridging_fills(const ExtrusionEntityCollection &fills) | |
807 | { | |
808 | for (const ExtrusionEntity *ee : fills.entities) { | |
809 | assert(ee->is_collection()); | |
810 | for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) { | |
811 | assert(! ee2->is_collection()); | |
812 | assert(! ee2->is_loop()); | |
813 | if (ee2->role() == erBridgeInfill) | |
814 | return true; | |
815 | } | |
816 | } | |
817 | return false; | |
818 | } | |
819 | static bool has_bridging_extrusions(const Layer &layer) | |
820 | { | |
821 | for (const LayerRegion *region : layer.regions) { | |
822 | if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters)) | |
823 | return true; | |
824 | if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills)) | |
825 | return true; | |
826 | } | |
827 | return false; | |
828 | } | |
829 | ||
830 | static inline void collect_bridging_perimeter_areas(const ExtrusionLoop &loop, const float expansion_scaled, Polygons &out) | |
831 | { | |
832 | assert(expansion_scaled >= 0.f); | |
833 | for (const ExtrusionPath &ep : loop.paths) | |
834 | if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { | |
835 | float exp = 0.5f * scale_(ep.width) + expansion_scaled; | |
836 | if (ep.is_closed()) { | |
837 | if (ep.size() >= 3) { | |
838 | // This is a complete loop. | |
839 | // Add the outer contour first. | |
840 | Polygon poly; | |
841 | poly.points = ep.polyline.points; | |
842 | poly.points.pop_back(); | |
843 | if (poly.area() < 0) | |
844 | poly.reverse(); | |
845 | polygons_append(out, offset(poly, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
846 | Polygons holes = offset(poly, - exp, SUPPORT_SURFACES_OFFSET_PARAMETERS); | |
847 | polygons_reverse(holes); | |
848 | polygons_append(out, holes); | |
849 | } | |
850 | } else if (ep.size() >= 2) { | |
851 | // Offset the polyline. | |
852 | polygons_append(out, offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
853 | } | |
854 | } | |
855 | } | |
856 | static void collect_bridging_perimeter_areas(const ExtrusionEntityCollection &perimeters, const float expansion_scaled, Polygons &out) | |
857 | { | |
858 | for (const ExtrusionEntity *ee : perimeters.entities) { | |
859 | if (ee->is_collection()) { | |
860 | for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) { | |
861 | assert(! ee2->is_collection()); | |
862 | if (ee2->is_loop()) | |
863 | collect_bridging_perimeter_areas(*static_cast<const ExtrusionLoop*>(ee2), expansion_scaled, out); | |
864 | } | |
865 | } else if (ee->is_loop()) | |
866 | collect_bridging_perimeter_areas(*static_cast<const ExtrusionLoop*>(ee), expansion_scaled, out); | |
867 | } | |
868 | } | |
869 | ||
870 | static void remove_bridges_from_contacts( | |
871 | const PrintConfig &print_config, | |
872 | const Layer &lower_layer, | |
873 | const Polygons &lower_layer_polygons, | |
874 | LayerRegion *layerm, | |
875 | float fw, | |
876 | Polygons &contact_polygons) | |
877 | { | |
878 | // compute the area of bridging perimeters | |
879 | Polygons bridges; | |
880 | { | |
881 | // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. | |
882 | Polygons lower_grown_slices = offset(lower_layer_polygons, | |
883 | //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. | |
884 | 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1))), | |
885 | SUPPORT_SURFACES_OFFSET_PARAMETERS); | |
886 | // Collect perimeters of this layer. | |
887 | //FIXME split_at_first_point() could split a bridge mid-way | |
888 | #if 0 | |
889 | Polylines overhang_perimeters = layerm->perimeters.as_polylines(); | |
890 | // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() | |
891 | for (Polyline &polyline : overhang_perimeters) | |
892 | polyline.points[0].x += 1; | |
893 | // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. | |
894 | overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); | |
895 | #else | |
896 | Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices); | |
897 | #endif | |
898 | ||
899 | // only consider straight overhangs | |
900 | // only consider overhangs having endpoints inside layer's slices | |
901 | // convert bridging polylines into polygons by inflating them with their thickness | |
902 | // since we're dealing with bridges, we can't assume width is larger than spacing, | |
903 | // so we take the largest value and also apply safety offset to be ensure no gaps | |
904 | // are left in between | |
905 | Flow bridge_flow = layerm->flow(frPerimeter, true); | |
906 | float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); | |
907 | for (Polyline &polyline : overhang_perimeters) | |
908 | if (polyline.is_straight()) { | |
909 | // This is a bridge | |
910 | polyline.extend_start(fw); | |
911 | polyline.extend_end(fw); | |
912 | // Is the straight perimeter segment supported at both sides? | |
913 | if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point())) | |
914 | // Offset a polyline into a thick line. | |
915 | polygons_append(bridges, offset(polyline, 0.5f * w + 10.f)); | |
916 | } | |
917 | bridges = union_(bridges); | |
918 | } | |
919 | // remove the entire bridges and only support the unsupported edges | |
920 | //FIXME the brided regions are already collected as layerm->bridged. Use it? | |
921 | for (const Surface &surface : layerm->fill_surfaces.surfaces) | |
922 | if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) | |
923 | polygons_append(bridges, surface.expolygon); | |
924 | //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? | |
925 | // Remove the unsupported ends of the bridges from the bridged areas. | |
926 | //FIXME add supports at regular intervals to support long bridges! | |
927 | bridges = diff(bridges, | |
928 | // Offset unsupported edges into polygons. | |
929 | offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
930 | // Remove bridged areas from the supported areas. | |
931 | contact_polygons = diff(contact_polygons, bridges, true); | |
932 | } | |
933 | } | |
934 | ||
935 | #ifdef SLIC3R_DEBUG | |
936 | static int Test() | |
937 | { | |
938 | // for (int i = 0; i < 30; ++ i) | |
939 | { | |
940 | int i = -1; | |
941 | // SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000-prev.bin", i); | |
942 | // SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000.bin", i); | |
943 | auto grid = SupportGridPattern::deserialize("d:\\temp\\support-top-contacts-final-run1-layer27-z5.650000.bin", i); | |
944 | std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersections = grid.grid().intersecting_edges(); | |
945 | if (! intersections.empty()) | |
946 | printf("Intersections between contours!\n"); | |
947 | Slic3r::export_intersections_to_svg("d:\\temp\\support_polygon_intersections.svg", grid.support_polygons()); | |
948 | Slic3r::SVG::export_expolygons("d:\\temp\\support_polygons.svg", union_ex(grid.support_polygons(), false)); | |
949 | Slic3r::SVG::export_expolygons("d:\\temp\\trimming_polygons.svg", union_ex(grid.trimming_polygons(), false)); | |
950 | Polygons extracted = grid.extract_support(scale_(0.21 / 2), true); | |
951 | Slic3r::SVG::export_expolygons("d:\\temp\\extracted.svg", union_ex(extracted, false)); | |
952 | printf("hu!"); | |
953 | } | |
954 | return 0; | |
955 | } | |
956 | static int run_support_test = Test(); | |
957 | #endif /* SLIC3R_DEBUG */ | |
633 | 958 | |
634 | 959 | // Generate top contact layers supporting overhangs. |
635 | 960 | // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. |
642 | 967 | ++ iRun; |
643 | 968 | #endif /* SLIC3R_DEBUG */ |
644 | 969 | |
970 | // Slice support enforcers / support blockers. | |
971 | std::vector<ExPolygons> enforcers = object.slice_support_enforcers(); | |
972 | std::vector<ExPolygons> blockers = object.slice_support_blockers(); | |
973 | ||
645 | 974 | // Output layers, sorted by top Z. |
646 | 975 | MyLayersPtr contact_out; |
647 | 976 | |
977 | const bool support_auto = m_object_config->support_material_auto.value; | |
648 | 978 | // If user specified a custom angle threshold, convert it to radians. |
649 | 979 | // Zero means automatic overhang detection. |
650 | 980 | const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ? |
679 | 1009 | // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. |
680 | 1010 | // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. |
681 | 1011 | size_t num_layers = this->has_support() ? object.layer_count() : 1; |
682 | contact_out.assign(num_layers, nullptr); | |
1012 | // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, | |
1013 | // and the other for the overhangs extruded with a normal flow. | |
1014 | contact_out.assign(num_layers * 2, nullptr); | |
683 | 1015 | tbb::spin_mutex layer_storage_mutex; |
684 | 1016 | tbb::parallel_for(tbb::blocked_range<size_t>(this->has_raft() ? 0 : 1, num_layers), |
685 | [this, &object, &buildplate_covered, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range<size_t>& range) { | |
1017 | [this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out] | |
1018 | (const tbb::blocked_range<size_t>& range) { | |
686 | 1019 | for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) |
687 | 1020 | { |
688 | 1021 | const Layer &layer = *object.layers[layer_id]; |
693 | 1026 | Polygons contact_polygons; |
694 | 1027 | Polygons slices_margin_cached; |
695 | 1028 | float slices_margin_cached_offset = -1.; |
1029 | Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers[layer_id-1]->slices.expolygons); | |
1030 | // Offset of the lower layer, to trim the support polygons with to calculate dense supports. | |
1031 | float no_interface_offset = 0.f; | |
696 | 1032 | if (layer_id == 0) { |
697 | 1033 | // This is the first object layer, so the object is being printed on a raft and |
698 | 1034 | // we're here just to get the object footprint for the raft. |
707 | 1043 | // Extrusion width accounts for the roundings of the extrudates. |
708 | 1044 | // It is the maximum widh of the extrudate. |
709 | 1045 | float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); |
1046 | no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); | |
710 | 1047 | float lower_layer_offset = |
711 | 1048 | (layer_id < this->m_object_config->support_material_enforce_layers.value) ? |
712 | 1049 | // Enforce a full possible support, ignore the overhang angle. |
719 | 1056 | // Overhang polygons for this layer and region. |
720 | 1057 | Polygons diff_polygons; |
721 | 1058 | Polygons layerm_polygons = to_polygons(layerm->slices); |
722 | Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons); | |
723 | 1059 | if (lower_layer_offset == 0.f) { |
724 | 1060 | // Support everything. |
725 | 1061 | diff_polygons = diff(layerm_polygons, lower_layer_polygons); |
729 | 1065 | diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); |
730 | 1066 | } |
731 | 1067 | } else { |
732 | // Get the regions needing a suport, collapse very tiny spots. | |
733 | //FIXME cache the lower layer offset if this layer has multiple regions. | |
734 | diff_polygons = offset2( | |
735 | diff(layerm_polygons, | |
736 | offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)), | |
737 | -0.1f*fw, +0.1f*fw); | |
738 | if (! buildplate_covered.empty()) { | |
739 | // Don't support overhangs above the top surfaces. | |
740 | // This step is done before the contact surface is calculated by growing the overhang region. | |
741 | diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); | |
1068 | if (support_auto) { | |
1069 | // Get the regions needing a suport, collapse very tiny spots. | |
1070 | //FIXME cache the lower layer offset if this layer has multiple regions. | |
1071 | #if 1 | |
1072 | diff_polygons = offset2( | |
1073 | diff(layerm_polygons, | |
1074 | offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), | |
1075 | //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to | |
1076 | // no support at all for not so steep overhangs. | |
1077 | - 0.1f * fw, 0.1f * fw); | |
1078 | #else | |
1079 | diff_polygons = | |
1080 | diff(layerm_polygons, | |
1081 | offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
1082 | #endif | |
1083 | if (! buildplate_covered.empty()) { | |
1084 | // Don't support overhangs above the top surfaces. | |
1085 | // This step is done before the contact surface is calculated by growing the overhang region. | |
1086 | diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); | |
1087 | } | |
1088 | if (! diff_polygons.empty()) { | |
1089 | // Offset the support regions back to a full overhang, restrict them to the full overhang. | |
1090 | // This is done to increase size of the supporting columns below, as they are calculated by | |
1091 | // propagating these contact surfaces downwards. | |
1092 | diff_polygons = diff( | |
1093 | intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), | |
1094 | lower_layer_polygons); | |
1095 | } | |
742 | 1096 | } |
743 | if (diff_polygons.empty()) | |
744 | continue; | |
745 | // Offset the support regions back to a full overhang, restrict them to the full overhang. | |
746 | diff_polygons = diff( | |
747 | intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), | |
748 | lower_layer_polygons); | |
1097 | if (! enforcers.empty()) { | |
1098 | // Apply the "support enforcers". | |
1099 | //FIXME add the "enforcers" to the sparse support regions only. | |
1100 | const ExPolygons &enforcer = enforcers[layer_id - 1]; | |
1101 | if (! enforcer.empty()) { | |
1102 | // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. | |
1103 | Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(enforcer)), | |
1104 | offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
1105 | if (! new_contacts.empty()) { | |
1106 | if (diff_polygons.empty()) | |
1107 | diff_polygons = std::move(new_contacts); | |
1108 | else | |
1109 | diff_polygons = union_(diff_polygons, new_contacts); | |
1110 | } | |
1111 | } | |
1112 | } | |
1113 | } | |
1114 | // Apply the "support blockers". | |
1115 | if (! diff_polygons.empty() && ! blockers.empty() && ! blockers[layer_id].empty()) { | |
1116 | // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. | |
1117 | diff_polygons = diff(diff_polygons, to_polygons(blockers[layer_id])); | |
749 | 1118 | } |
750 | 1119 | if (diff_polygons.empty()) |
751 | 1120 | continue; |
752 | 1121 | |
753 | #ifdef SLIC3R_DEBUG | |
1122 | #ifdef SLIC3R_DEBUG | |
754 | 1123 | { |
755 | 1124 | ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", |
756 | 1125 | iRun, layer_id, |
761 | 1130 | } |
762 | 1131 | #endif /* SLIC3R_DEBUG */ |
763 | 1132 | |
764 | if (this->m_object_config->dont_support_bridges) { | |
765 | // compute the area of bridging perimeters | |
766 | // Note: this is duplicate code from GCode.pm, we need to refactor | |
767 | if (true) { | |
768 | Polygons bridged_perimeters; | |
769 | { | |
770 | Flow bridge_flow = layerm->flow(frPerimeter, true); | |
771 | coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1); | |
772 | Polygons lower_grown_slices = offset(lower_layer_polygons, 0.5f*float(scale_(nozzle_diameter)), SUPPORT_SURFACES_OFFSET_PARAMETERS); | |
773 | ||
774 | // Collect perimeters of this layer. | |
775 | // TODO: split_at_first_point() could split a bridge mid-way | |
776 | Polylines overhang_perimeters; | |
777 | for (ExtrusionEntity* extrusion_entity : layerm->perimeters.entities) { | |
778 | const ExtrusionEntityCollection *island = dynamic_cast<ExtrusionEntityCollection*>(extrusion_entity); | |
779 | assert(island != NULL); | |
780 | for (size_t i = 0; i < island->entities.size(); ++ i) { | |
781 | ExtrusionEntity *entity = island->entities[i]; | |
782 | ExtrusionLoop *loop = dynamic_cast<Slic3r::ExtrusionLoop*>(entity); | |
783 | overhang_perimeters.push_back(loop ? | |
784 | loop->as_polyline() : | |
785 | dynamic_cast<const Slic3r::ExtrusionPath*>(entity)->polyline); | |
786 | } | |
787 | } | |
788 | ||
789 | // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() | |
790 | for (Polyline &polyline : overhang_perimeters) | |
791 | polyline.points[0].x += 1; | |
792 | // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. | |
793 | overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); | |
794 | ||
795 | // only consider straight overhangs | |
796 | // only consider overhangs having endpoints inside layer's slices | |
797 | // convert bridging polylines into polygons by inflating them with their thickness | |
798 | // since we're dealing with bridges, we can't assume width is larger than spacing, | |
799 | // so we take the largest value and also apply safety offset to be ensure no gaps | |
800 | // are left in between | |
801 | float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); | |
802 | for (Polyline &polyline : overhang_perimeters) | |
803 | if (polyline.is_straight()) { | |
804 | // This is a bridge | |
805 | polyline.extend_start(fw); | |
806 | polyline.extend_end(fw); | |
807 | // Is the straight perimeter segment supported at both sides? | |
808 | if (layer.slices.contains(polyline.first_point()) && layer.slices.contains(polyline.last_point())) | |
809 | // Offset a polyline into a thick line. | |
810 | polygons_append(bridged_perimeters, offset(polyline, 0.5f * w + 10.f)); | |
811 | } | |
812 | bridged_perimeters = union_(bridged_perimeters); | |
813 | } | |
814 | // remove the entire bridges and only support the unsupported edges | |
815 | Polygons bridges; | |
816 | for (const Surface &surface : layerm->fill_surfaces.surfaces) | |
817 | if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) | |
818 | polygons_append(bridges, surface.expolygon); | |
819 | diff_polygons = diff(diff_polygons, bridges, true); | |
820 | polygons_append(bridges, bridged_perimeters); | |
821 | polygons_append(diff_polygons, | |
822 | intersection( | |
823 | // Offset unsupported edges into polygons. | |
824 | offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), | |
825 | bridges)); | |
826 | } else { | |
827 | // just remove bridged areas | |
828 | diff_polygons = diff(diff_polygons, layerm->bridged, true); | |
829 | } | |
830 | } // if (m_objconfig->dont_support_bridges) | |
1133 | if (this->m_object_config->dont_support_bridges) | |
1134 | SupportMaterialInternal::remove_bridges_from_contacts( | |
1135 | *m_print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); | |
831 | 1136 | |
832 | 1137 | if (diff_polygons.empty()) |
833 | 1138 | continue; |
841 | 1146 | union_ex(diff_polygons, false)); |
842 | 1147 | #endif /* SLIC3R_DEBUG */ |
843 | 1148 | |
844 | if (this->has_contact_loops()) | |
1149 | //FIXME the overhang_polygons are used to construct the support towers as well. | |
1150 | //if (this->has_contact_loops()) | |
1151 | // Store the exact contour of the overhang for the contact loops. | |
845 | 1152 | polygons_append(overhang_polygons, diff_polygons); |
846 | 1153 | |
847 | 1154 | // Let's define the required contact area by using a max gap of half the upper |
850 | 1157 | // on the other side of the object (if it's very thin). |
851 | 1158 | { |
852 | 1159 | //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. |
1160 | //FIXME one should trim with the layer span colliding with the support layer, this layer | |
1161 | // may be lower than lower_layer, so the support area needed may need to be actually bigger! | |
1162 | // For the same reason, the non-bridging support area may be smaller than the bridging support area! | |
853 | 1163 | float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy))); |
854 | 1164 | if (slices_margin_cached_offset != slices_margin_offset) { |
855 | 1165 | slices_margin_cached_offset = slices_margin_offset; |
856 | 1166 | slices_margin_cached = (slices_margin_offset == 0.f) ? |
857 | to_polygons(lower_layer.slices.expolygons) : | |
858 | offset(lower_layer.slices.expolygons, slices_margin_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS); | |
1167 | lower_layer_polygons : | |
1168 | offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); | |
859 | 1169 | if (! buildplate_covered.empty()) { |
860 | 1170 | // Trim the inflated contact surfaces by the top surfaces as well. |
861 | 1171 | polygons_append(slices_margin_cached, buildplate_covered[layer_id]); |
878 | 1188 | } // for each layer.region |
879 | 1189 | } // end of Generate overhang/contact_polygons for non-raft layers. |
880 | 1190 | |
881 | // now apply the contact areas to the layer were they need to be made | |
1191 | // Now apply the contact areas to the layer where they need to be made. | |
882 | 1192 | if (! contact_polygons.empty()) { |
883 | // get the average nozzle diameter used on this layer | |
884 | 1193 | MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); |
885 | 1194 | new_layer.idx_object_layer_above = layer_id; |
886 | if (m_slicing_params.soluble_interface) { | |
1195 | MyLayer *bridging_layer = nullptr; | |
1196 | if (layer_id == 0) { | |
1197 | // This is a raft contact layer sitting directly on the print bed. | |
1198 | assert(this->has_raft()); | |
1199 | new_layer.print_z = m_slicing_params.raft_contact_top_z; | |
1200 | new_layer.bottom_z = m_slicing_params.raft_interface_top_z; | |
1201 | new_layer.height = m_slicing_params.contact_raft_layer_height; | |
1202 | } else if (m_slicing_params.soluble_interface) { | |
887 | 1203 | // Align the contact surface height with a layer immediately below the supported layer. |
888 | new_layer.print_z = layer.print_z - layer.height; | |
889 | if (layer_id == 0) { | |
890 | // This is a raft contact layer sitting directly on the print bed. | |
891 | new_layer.height = m_slicing_params.contact_raft_layer_height; | |
892 | new_layer.bottom_z = m_slicing_params.raft_interface_top_z; | |
1204 | // Interface layer will be synchronized with the object. | |
1205 | new_layer.print_z = layer.print_z - layer.height; | |
1206 | new_layer.height = object.layers[layer_id - 1]->height; | |
1207 | new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers[layer_id - 2]->print_z; | |
1208 | } else { | |
1209 | new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance; | |
1210 | new_layer.bottom_z = new_layer.print_z; | |
1211 | new_layer.height = 0.; | |
1212 | // Ignore this contact area if it's too low. | |
1213 | // Don't want to print a layer below the first layer height as it may not stick well. | |
1214 | //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact | |
1215 | // and it may actually make sense to do it with a thinner layer than the first layer height. | |
1216 | if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { | |
1217 | // This contact layer is below the first layer height, therefore not printable. Don't support this surface. | |
1218 | continue; | |
1219 | } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { | |
1220 | // Align the layer with the 1st layer height. | |
1221 | new_layer.print_z = m_slicing_params.first_print_layer_height; | |
1222 | new_layer.bottom_z = 0; | |
1223 | new_layer.height = m_slicing_params.first_print_layer_height; | |
893 | 1224 | } else { |
894 | // Interface layer will be synchronized with the object. | |
895 | assert(layer_id > 0); | |
896 | new_layer.height = object.layers[layer_id - 1]->height; | |
897 | new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers[layer_id - 2]->print_z; | |
1225 | // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and | |
1226 | // its height will be set adaptively later on. | |
898 | 1227 | } |
899 | } else { | |
1228 | ||
900 | 1229 | // Contact layer will be printed with a normal flow, but |
901 | 1230 | // it will support layers printed with a bridging flow. |
902 | //FIXME Probably printing with the bridge flow? How about the unsupported perimeters? Are they printed with the bridging flow? | |
903 | // In the future we may switch to a normal extrusion flow for the supported bridges. | |
904 | // Get the average nozzle diameter used on this layer. | |
905 | coordf_t nozzle_dmr = 0.; | |
906 | for (const LayerRegion *region : layer.regions) | |
907 | nozzle_dmr += region->region()->nozzle_dmr_avg(*m_print_config); | |
908 | nozzle_dmr /= coordf_t(layer.regions.size()); | |
909 | new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance; | |
910 | new_layer.bottom_z = new_layer.print_z; | |
911 | new_layer.height = 0.; | |
912 | if (layer_id == 0) { | |
913 | // This is a raft contact layer sitting directly on the print bed. | |
914 | assert(this->has_raft()); | |
915 | new_layer.bottom_z = m_slicing_params.raft_interface_top_z; | |
916 | new_layer.height = m_slicing_params.contact_raft_layer_height; | |
917 | } else { | |
918 | // Ignore this contact area if it's too low. | |
919 | // Don't want to print a layer below the first layer height as it may not stick well. | |
920 | //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact | |
921 | // and it may actually make sense to do it with a thinner layer than the first layer height. | |
922 | if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { | |
923 | // This contact layer is below the first layer height, therefore not printable. Don't support this surface. | |
924 | continue; | |
925 | } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { | |
926 | // Align the layer with the 1st layer height. | |
927 | new_layer.print_z = m_slicing_params.first_print_layer_height; | |
928 | new_layer.bottom_z = 0; | |
929 | new_layer.height = m_slicing_params.first_print_layer_height; | |
930 | } else { | |
931 | // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and | |
932 | // its height will be set adaptively later on. | |
1231 | if (SupportMaterialInternal::has_bridging_extrusions(layer)) { | |
1232 | coordf_t bridging_height = 0.; | |
1233 | for (const LayerRegion *region : layer.regions) | |
1234 | bridging_height += region->region()->bridging_height_avg(*m_print_config); | |
1235 | bridging_height /= coordf_t(layer.regions.size()); | |
1236 | coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance; | |
1237 | if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) { | |
1238 | // Not below the first layer height means this layer is printable. | |
1239 | if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { | |
1240 | // Align the layer with the 1st layer height. | |
1241 | bridging_print_z = m_slicing_params.first_print_layer_height; | |
1242 | } | |
1243 | if (bridging_print_z < new_layer.print_z - EPSILON) { | |
1244 | // Allocate the new layer. | |
1245 | bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); | |
1246 | bridging_layer->idx_object_layer_above = layer_id; | |
1247 | bridging_layer->print_z = bridging_print_z; | |
1248 | if (bridging_print_z == m_slicing_params.first_print_layer_height) { | |
1249 | bridging_layer->bottom_z = 0; | |
1250 | bridging_layer->height = m_slicing_params.first_print_layer_height; | |
1251 | } else { | |
1252 | // Don't know the height yet. | |
1253 | bridging_layer->bottom_z = bridging_print_z; | |
1254 | bridging_layer->height = 0; | |
1255 | } | |
1256 | } | |
933 | 1257 | } |
934 | 1258 | } |
935 | 1259 | } |
936 | 1260 | |
1261 | // Achtung! The contact_polygons need to be trimmed by slices_margin_cached, otherwise | |
1262 | // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! | |
937 | 1263 | SupportGridPattern support_grid_pattern( |
938 | 1264 | // Support islands, to be stretched into a grid. |
939 | 1265 | contact_polygons, |
940 | 1266 | // Trimming polygons, to trim the stretched support islands. |
941 | 1267 | slices_margin_cached, |
942 | // How much to offset the extracted contour outside of the grid. | |
1268 | // Grid resolution. | |
943 | 1269 | m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), |
944 | 1270 | Geometry::deg2rad(m_object_config->support_material_angle.value)); |
945 | // 1) infill polygons, expand them by half the extrusion width + a tiny bit of extra. | |
946 | new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); | |
947 | // 2) Contact polygons will be projected down. To keep the interface and base layers to grow, return a contour a tiny bit smaller than the grid cells. | |
948 | new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3)); | |
1271 | // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. | |
1272 | new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true)); | |
1273 | // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. | |
1274 | if (layer_id == 0 || m_slicing_params.soluble_interface) { | |
1275 | // if (no_interface_offset == 0.f) { | |
1276 | new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true); | |
1277 | } else { | |
1278 | // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. | |
1279 | Polygons dense_interface_polygons = diff(overhang_polygons, | |
1280 | offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
1281 | if (! dense_interface_polygons.empty()) { | |
1282 | dense_interface_polygons = | |
1283 | // Achtung! The dense_interface_polygons need to be trimmed by slices_margin_cached, otherwise | |
1284 | // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! | |
1285 | diff( | |
1286 | // Regularize the contour. | |
1287 | offset(dense_interface_polygons, no_interface_offset * 0.1f), | |
1288 | slices_margin_cached); | |
1289 | SupportGridPattern support_grid_pattern( | |
1290 | // Support islands, to be stretched into a grid. | |
1291 | dense_interface_polygons, | |
1292 | // Trimming polygons, to trim the stretched support islands. | |
1293 | slices_margin_cached, | |
1294 | // Grid resolution. | |
1295 | m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), | |
1296 | Geometry::deg2rad(m_object_config->support_material_angle.value)); | |
1297 | new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); | |
1298 | #ifdef SLIC3R_DEBUG | |
1299 | { | |
1300 | support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); | |
1301 | ||
1302 | BoundingBox bbox = get_extents(contact_polygons); | |
1303 | bbox.merge(get_extents(new_layer.polygons)); | |
1304 | ::Slic3r::SVG svg(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z)); | |
1305 | svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f); | |
1306 | svg.draw(union_ex(contact_polygons, false), "blue", 0.5f); | |
1307 | svg.draw(union_ex(dense_interface_polygons, false), "green", 0.5f); | |
1308 | svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f); | |
1309 | svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f)); | |
1310 | } | |
1311 | #endif /* SLIC3R_DEBUG */ | |
1312 | } | |
1313 | } | |
1314 | #ifdef SLIC3R_DEBUG | |
1315 | { | |
1316 | BoundingBox bbox = get_extents(contact_polygons); | |
1317 | bbox.merge(get_extents(new_layer.polygons)); | |
1318 | ::Slic3r::SVG svg(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z)); | |
1319 | svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f); | |
1320 | svg.draw(union_ex(contact_polygons, false), "blue", 0.5f); | |
1321 | svg.draw(union_ex(overhang_polygons, false), "green", 0.5f); | |
1322 | svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f); | |
1323 | svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f)); | |
1324 | } | |
1325 | #endif /* SLIC3R_DEBUG */ | |
949 | 1326 | |
950 | 1327 | // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. |
951 | 1328 | // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. |
952 | 1329 | |
953 | 1330 | // Store the overhang polygons. |
954 | 1331 | // The overhang polygons are used in the path generator for planning of the contact loops. |
955 | // if (this->has_contact_loops()) | |
1332 | // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. | |
956 | 1333 | new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons)); |
957 | contact_out[layer_id] = &new_layer; | |
1334 | contact_out[layer_id * 2] = &new_layer; | |
1335 | if (bridging_layer != nullptr) { | |
1336 | bridging_layer->polygons = new_layer.polygons; | |
1337 | bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons); | |
1338 | bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons); | |
1339 | contact_out[layer_id * 2 + 1] = bridging_layer; | |
1340 | } | |
958 | 1341 | } |
959 | 1342 | } |
960 | 1343 | }); |
1344 | ||
961 | 1345 | // Compress contact_out, remove the nullptr items. |
962 | 1346 | remove_nulls(contact_out); |
1347 | // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. | |
1348 | std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; }); | |
1349 | ||
1350 | // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), | |
1351 | // the top contact layer is merged into the bottom contact layer. | |
1352 | { | |
1353 | int i = 0; | |
1354 | int k = 0; | |
1355 | { | |
1356 | // Find the span of layers, which are to be printed at the first layer height. | |
1357 | int j = 0; | |
1358 | for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); | |
1359 | if (j > 0) { | |
1360 | // Merge the contact_out layers (0) to (j - 1) into the contact_out[0]. | |
1361 | MyLayer &dst = *contact_out.front(); | |
1362 | for (int u = 1; u < j; ++ u) { | |
1363 | MyLayer &src = *contact_out[u]; | |
1364 | // The union_() does not support move semantic yet, but maybe one day it will. | |
1365 | dst.polygons = union_(dst.polygons, std::move(src.polygons)); | |
1366 | *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); | |
1367 | *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); | |
1368 | // Source polygon is no more needed, it will not be refrenced. Release its data. | |
1369 | src.reset(); | |
1370 | } | |
1371 | // Snap the first layer to the 1st layer height. | |
1372 | dst.print_z = m_slicing_params.first_print_layer_height; | |
1373 | dst.height = m_slicing_params.first_print_layer_height; | |
1374 | dst.bottom_z = 0; | |
1375 | ++ k; | |
1376 | } | |
1377 | i = j; | |
1378 | } | |
1379 | for (; i < int(contact_out.size()); ++ k) { | |
1380 | // Find the span of layers closer than m_support_layer_height_min. | |
1381 | int j = i + 1; | |
1382 | coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; | |
1383 | for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; | |
1384 | if (i + 1 < j) { | |
1385 | // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. | |
1386 | MyLayer &dst = *contact_out[i]; | |
1387 | for (int u = i + 1; u < j; ++ u) { | |
1388 | MyLayer &src = *contact_out[u]; | |
1389 | // The union_() does not support move semantic yet, but maybe one day it will. | |
1390 | dst.polygons = union_(dst.polygons, std::move(src.polygons)); | |
1391 | *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); | |
1392 | *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); | |
1393 | // Source polygon is no more needed, it will not be refrenced. Release its data. | |
1394 | src.reset(); | |
1395 | } | |
1396 | } | |
1397 | if (k < i) | |
1398 | contact_out[k] = contact_out[i]; | |
1399 | i = j; | |
1400 | } | |
1401 | if (k < contact_out.size()) | |
1402 | contact_out.erase(contact_out.begin() + k, contact_out.end()); | |
1403 | } | |
1404 | ||
963 | 1405 | BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; |
964 | 1406 | |
965 | 1407 | return contact_out; |
995 | 1437 | BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; |
996 | 1438 | const Layer &layer = *object.get_layer(layer_id); |
997 | 1439 | // Collect projections of all contact areas above or at the same level as this top surface. |
998 | for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) { | |
1440 | for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { | |
999 | 1441 | Polygons polygons_new; |
1000 | 1442 | // Contact surfaces are expanded away from the object, trimmed by the object. |
1001 | 1443 | // Use a slight positive offset to overlap the touching regions. |
1003 | 1445 | // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. |
1004 | 1446 | polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON)); |
1005 | 1447 | #else |
1006 | // Consume the contact_polygons. The contact polygons are already expanded into a grid form. | |
1448 | // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller | |
1449 | // than the grid cells. | |
1007 | 1450 | polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons)); |
1008 | 1451 | #endif |
1009 | 1452 | // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. |
1015 | 1458 | continue; |
1016 | 1459 | Polygons projection_raw = union_(projection); |
1017 | 1460 | |
1018 | // Top surfaces of this layer, to be used to stop the surface volume from growing down. | |
1019 | 1461 | tbb::task_group task_group; |
1020 | 1462 | if (! m_object_config->support_material_buildplate_only) |
1463 | // Find the bottom contact layers above the top surfaces of this layer. | |
1021 | 1464 | task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] { |
1022 | 1465 | Polygons top = collect_region_slices_by_type(layer, stTop); |
1023 | 1466 | #ifdef SLIC3R_DEBUG |
1045 | 1488 | // Grow top surfaces so that interface and support generation are generated |
1046 | 1489 | // with some spacing from object - it looks we don't need the actual |
1047 | 1490 | // top shapes so this can be done here |
1491 | //FIXME calculate layer height based on the actual thickness of the layer: | |
1492 | // If the layer is extruded with no bridging flow, support just the normal extrusions. | |
1048 | 1493 | layer_new.height = m_slicing_params.soluble_interface ? |
1049 | 1494 | // Align the interface layer with the object's layer height. |
1050 | 1495 | object.layers[layer_id + 1]->height : |
1051 | 1496 | // Place a bridge flow interface layer over the top surface. |
1497 | //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?) | |
1498 | // According to Jindrich the bottom surfaces work well. | |
1499 | //FIXME test the bridging flow instead? | |
1052 | 1500 | m_support_material_interface_flow.nozzle_diameter; |
1053 | 1501 | layer_new.print_z = m_slicing_params.soluble_interface ? object.layers[layer_id + 1]->print_z : |
1054 | 1502 | layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; |
1055 | 1503 | layer_new.bottom_z = layer.print_z; |
1056 | 1504 | layer_new.idx_object_layer_below = layer_id; |
1057 | 1505 | layer_new.bridging = ! m_slicing_params.soluble_interface; |
1058 | //FIXME how much to inflate the top surface? | |
1506 | //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. | |
1507 | //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. | |
1059 | 1508 | layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); |
1060 | 1509 | if (! m_slicing_params.soluble_interface) { |
1061 | 1510 | // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, |
1062 | 1511 | // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. |
1063 | 1512 | for (size_t top_idx = size_t(std::max<int>(0, contact_idx)); |
1064 | top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min; | |
1513 | top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min + EPSILON; | |
1065 | 1514 | ++ top_idx) { |
1066 | if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min) { | |
1515 | if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min - EPSILON) { | |
1067 | 1516 | // A top layer has been found, which is close to the new bottom layer. |
1068 | 1517 | coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; |
1069 | assert(std::abs(diff) <= this->m_support_layer_height_min); | |
1518 | assert(std::abs(diff) <= this->m_support_layer_height_min + EPSILON); | |
1070 | 1519 | if (diff > 0.) { |
1071 | 1520 | // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. |
1072 | 1521 | assert(diff < layer_new.height + EPSILON); |
1090 | 1539 | union_ex(layer_new.polygons, false)); |
1091 | 1540 | #endif /* SLIC3R_DEBUG */ |
1092 | 1541 | // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. |
1542 | //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? | |
1093 | 1543 | touching = offset(touching, float(SCALED_EPSILON)); |
1094 | 1544 | for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { |
1095 | 1545 | const Layer &layer_above = *object.layers[layer_id_above]; |
1096 | if (layer_above.print_z > layer_new.print_z + EPSILON) | |
1546 | if (layer_above.print_z > layer_new.print_z - EPSILON) | |
1097 | 1547 | break; |
1098 | 1548 | if (! layer_support_areas[layer_id_above].empty()) { |
1099 | 1549 | #ifdef SLIC3R_DEBUG |
1146 | 1596 | projection, |
1147 | 1597 | // Trimming polygons, to trim the stretched support islands. |
1148 | 1598 | trimming, |
1149 | // How much to offset the extracted contour outside of the grid. | |
1599 | // Grid spacing. | |
1150 | 1600 | m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), |
1151 | 1601 | Geometry::deg2rad(m_object_config->support_material_angle.value)); |
1152 | 1602 | tbb::task_group task_group_inner; |
1157 | 1607 | , &layer |
1158 | 1608 | #endif /* SLIC3R_DEBUG */ |
1159 | 1609 | ] { |
1160 | layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25); | |
1610 | layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true); | |
1161 | 1611 | #ifdef SLIC3R_DEBUG |
1162 | 1612 | Slic3r::SVG::export_expolygons( |
1163 | 1613 | debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z), |
1171 | 1621 | , &layer |
1172 | 1622 | #endif /* SLIC3R_DEBUG */ |
1173 | 1623 | ] { |
1174 | projection_new = support_grid_pattern.extract_support(-5); | |
1624 | projection_new = support_grid_pattern.extract_support(-5, true); | |
1175 | 1625 | #ifdef SLIC3R_DEBUG |
1176 | 1626 | Slic3r::SVG::export_expolygons( |
1177 | 1627 | debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), |
1184 | 1634 | task_group.wait(); |
1185 | 1635 | } |
1186 | 1636 | std::reverse(bottom_contacts.begin(), bottom_contacts.end()); |
1187 | trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); | |
1637 | // trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy); | |
1638 | trim_support_layers_by_object(object, bottom_contacts, | |
1639 | m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, | |
1640 | m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); | |
1641 | ||
1188 | 1642 | } // ! top_contacts.empty() |
1189 | 1643 | |
1190 | 1644 | return bottom_contacts; |
1501 | 1955 | assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z); |
1502 | 1956 | |
1503 | 1957 | // Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new. |
1504 | idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, | |
1505 | [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); | |
1506 | ||
1507 | 1958 | // New polygons for layer_intermediate. |
1508 | 1959 | Polygons polygons_new; |
1509 | 1960 | |
1522 | 1973 | // 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here. |
1523 | 1974 | // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. |
1524 | 1975 | // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. |
1525 | int idx_top_contact_overlapping = idx_top_contact_above; | |
1526 | while (idx_top_contact_overlapping >= 0 && | |
1527 | top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) | |
1528 | -- idx_top_contact_overlapping; | |
1976 | idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, | |
1977 | [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); | |
1529 | 1978 | // Collect all the top_contact layer intersecting with this layer. |
1530 | for (; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { | |
1979 | for ( int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { | |
1531 | 1980 | MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; |
1532 | 1981 | if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) |
1533 | 1982 | break; |
1607 | 2056 | ++ iRun; |
1608 | 2057 | #endif /* SLIC3R_DEBUG */ |
1609 | 2058 | |
1610 | trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_gap_xy); | |
2059 | // trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy); | |
2060 | this->trim_support_layers_by_object(object, intermediate_layers, | |
2061 | m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, | |
2062 | m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); | |
1611 | 2063 | } |
1612 | 2064 | |
1613 | 2065 | void PrintObjectSupportMaterial::trim_support_layers_by_object( |
1652 | 2104 | const Layer &object_layer = *object.layers[i]; |
1653 | 2105 | if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) |
1654 | 2106 | break; |
1655 | polygons_append(polygons_trimming, (Polygons)object_layer.slices); | |
2107 | polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
1656 | 2108 | } |
1657 | 2109 | if (! this->m_slicing_params.soluble_interface) { |
1658 | 2110 | // Collect all bottom surfaces, which will be extruded with a bridging flow. |
1659 | 2111 | for (; i < object.layers.size(); ++ i) { |
1660 | 2112 | const Layer &object_layer = *object.layers[i]; |
1661 | 2113 | bool some_region_overlaps = false; |
1662 | for (LayerRegion* region : object_layer.regions) { | |
1663 | coordf_t nozzle_dmr = region->region()->nozzle_dmr_avg(*this->m_print_config); | |
1664 | if (object_layer.print_z - nozzle_dmr > support_layer.print_z + gap_extra_above - EPSILON) | |
2114 | for (LayerRegion *region : object_layer.regions) { | |
2115 | coordf_t bridging_height = region->region()->bridging_height_avg(*this->m_print_config); | |
2116 | if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) | |
1665 | 2117 | break; |
1666 | 2118 | some_region_overlaps = true; |
1667 | polygons_append(polygons_trimming, to_polygons(region->slices.filter_by_type(stBottomBridge))); | |
2119 | polygons_append(polygons_trimming, | |
2120 | offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)), | |
2121 | gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
2122 | if (region->region()->config.overhangs.value) | |
2123 | SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); | |
1668 | 2124 | } |
1669 | 2125 | if (! some_region_overlaps) |
1670 | 2126 | break; |
1674 | 2130 | // perimeter's width. $support contains the full shape of support |
1675 | 2131 | // material, thus including the width of its foremost extrusion. |
1676 | 2132 | // We leave a gap equal to a full extrusion width. |
1677 | support_layer.polygons = diff( | |
1678 | support_layer.polygons, | |
1679 | offset(polygons_trimming, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | |
2133 | support_layer.polygons = diff(support_layer.polygons, polygons_trimming); | |
1680 | 2134 | } |
1681 | 2135 | }); |
1682 | 2136 | BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; |
1799 | 2253 | coordf_t top_z = intermediate_layers[std::min<int>(intermediate_layers.size()-1, idx_intermediate_layer + m_object_config->support_material_interface_layers - 1)]->print_z; |
1800 | 2254 | coordf_t bottom_z = intermediate_layers[std::max<int>(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z; |
1801 | 2255 | // Move idx_top_contact_first up until above the current print_z. |
1802 | idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); | |
2256 | idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON | |
1803 | 2257 | // Collect the top contact areas above this intermediate layer, below top_z. |
1804 | 2258 | Polygons polygons_top_contact_projected; |
1805 | 2259 | for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) { |
1806 | 2260 | const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; |
2261 | //FIXME maybe this adds one interface layer in excess? | |
1807 | 2262 | if (top_contact_layer.bottom_z - EPSILON > top_z) |
1808 | 2263 | break; |
1809 | 2264 | polygons_append(polygons_top_contact_projected, top_contact_layer.polygons); |
1860 | 2315 | fill_params.density = density; |
1861 | 2316 | fill_params.complete = true; |
1862 | 2317 | fill_params.dont_adjust = true; |
1863 | for (ExPolygons::const_iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { | |
1864 | Surface surface(stInternal, *it_expolygon); | |
2318 | for (const ExPolygon &expoly : expolygons) { | |
2319 | Surface surface(stInternal, expoly); | |
1865 | 2320 | extrusion_entities_append_paths( |
1866 | 2321 | dst, |
1867 | 2322 | filler->fill_surface(&surface, fill_params), |
1882 | 2337 | fill_params.density = density; |
1883 | 2338 | fill_params.complete = true; |
1884 | 2339 | fill_params.dont_adjust = true; |
1885 | for (ExPolygons::iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { | |
1886 | Surface surface(stInternal, std::move(*it_expolygon)); | |
2340 | for (ExPolygon &expoly : expolygons) { | |
2341 | Surface surface(stInternal, std::move(expoly)); | |
1887 | 2342 | extrusion_entities_append_paths( |
1888 | 2343 | dst, |
1889 | 2344 | filler->fill_surface(&surface, fill_params), |
2358 | 2813 | (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); |
2359 | 2814 | } |
2360 | 2815 | private: |
2361 | ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&); | |
2816 | ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {} | |
2362 | 2817 | const std::vector<ExtrusionPathFragment> &m_path_fragments; |
2363 | 2818 | }; |
2364 | 2819 | const coord_t search_radius = 7; |
2710 | 3165 | continue; |
2711 | 3166 | //FIXME When paralellizing, each thread shall have its own copy of the fillers. |
2712 | 3167 | bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; |
3168 | //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore | |
3169 | // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) | |
2713 | 3170 | Flow interface_flow( |
2714 | 3171 | float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)), |
2715 | 3172 | float(layer_ex.layer->height), |
11 | 11 | class PrintObjectConfig; |
12 | 12 | |
13 | 13 | // how much we extend support around the actual contact area |
14 | //FIXME this should be dependent on the nozzle diameter! | |
14 | 15 | #define SUPPORT_MATERIAL_MARGIN 1.5 |
15 | 16 | |
16 | 17 | // This class manages raft and supports for a single PrintObject. |
70 | 71 | overhang_polygons = nullptr; |
71 | 72 | } |
72 | 73 | |
74 | void reset() { | |
75 | layer_type = sltUnknown; | |
76 | print_z = 0.; | |
77 | bottom_z = 0.; | |
78 | height = 0.; | |
79 | idx_object_layer_above = size_t(-1); | |
80 | idx_object_layer_below = size_t(-1); | |
81 | bridging = false; | |
82 | polygons.clear(); | |
83 | delete contact_polygons; | |
84 | contact_polygons = nullptr; | |
85 | delete overhang_polygons; | |
86 | overhang_polygons = nullptr; | |
87 | } | |
88 | ||
73 | 89 | bool operator==(const MyLayer &layer2) const { |
74 | 90 | return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; |
75 | 91 | } |
36 | 36 | |
37 | 37 | void clear() { surfaces.clear(); } |
38 | 38 | bool empty() const { return surfaces.empty(); } |
39 | bool has(SurfaceType type) const { | |
40 | for (const Surface &surface : this->surfaces) | |
41 | if (surface.surface_type == type) return true; | |
42 | return false; | |
43 | } | |
39 | 44 | |
40 | 45 | void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; } |
41 | 46 | void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); } |
0 | 0 | #include "TriangleMesh.hpp" |
1 | 1 | #include "ClipperUtils.hpp" |
2 | 2 | #include "Geometry.hpp" |
3 | #include "MultiPoint.hpp" | |
3 | 4 | #include "qhull/src/libqhullcpp/Qhull.h" |
4 | 5 | #include "qhull/src/libqhullcpp/QhullFacetList.h" |
5 | 6 | #include "qhull/src/libqhullcpp/QhullVertexSet.h" |
20 | 21 | |
21 | 22 | #include <Eigen/Dense> |
22 | 23 | |
24 | // for SLIC3R_DEBUG_SLICE_PROCESSING | |
25 | #include "libslic3r.h" | |
26 | ||
23 | 27 | #if 0 |
24 | 28 | #define DEBUG |
25 | 29 | #define _DEBUG |
26 | 30 | #undef NDEBUG |
31 | #define SLIC3R_DEBUG | |
32 | // #define SLIC3R_TRIANGLEMESH_DEBUG | |
27 | 33 | #endif |
28 | 34 | |
29 | 35 | #include <assert.h> |
30 | 36 | |
31 | #ifdef SLIC3R_DEBUG | |
32 | // #define SLIC3R_TRIANGLEMESH_DEBUG | |
37 | #if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) | |
33 | 38 | #include "SVG.hpp" |
34 | 39 | #endif |
35 | 40 | |
202 | 207 | } |
203 | 208 | |
204 | 209 | // fill_holes |
210 | #if 0 | |
211 | // Don't fill holes, the current algorithm does more harm than good on complex holes. | |
212 | // Rather let the slicing algorithm close gaps in 2D slices. | |
205 | 213 | if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { |
206 | 214 | stl_fill_holes(&stl); |
207 | 215 | stl_clear_error(&stl); |
208 | 216 | } |
217 | #endif | |
209 | 218 | |
210 | 219 | // normal_directions |
211 | 220 | stl_fix_normal_directions(&stl); |
223 | 232 | |
224 | 233 | BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; |
225 | 234 | } |
226 | ||
227 | 235 | |
228 | 236 | float TriangleMesh::volume() |
229 | 237 | { |
439 | 447 | facet_visited[facet_idx] = true; |
440 | 448 | for (int j = 0; j < 3; ++ j) { |
441 | 449 | int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; |
442 | if (! facet_visited[neighbor_idx]) | |
450 | if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) | |
443 | 451 | facet_queue[facet_queue_cnt ++] = neighbor_idx; |
444 | 452 | } |
445 | 453 | } |
482 | 490 | facet_visited[facet_idx] = true; |
483 | 491 | for (int j = 0; j < 3; ++ j) { |
484 | 492 | int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; |
485 | if (! facet_visited[neighbor_idx]) | |
493 | if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) | |
486 | 494 | facet_queue[facet_queue_cnt ++] = neighbor_idx; |
487 | 495 | } |
488 | 496 | } |
491 | 499 | return num_bodies; |
492 | 500 | } |
493 | 501 | |
494 | TriangleMeshPtrs | |
495 | TriangleMesh::split() const | |
502 | TriangleMeshPtrs TriangleMesh::split() const | |
496 | 503 | { |
497 | 504 | TriangleMeshPtrs meshes; |
498 | 505 | std::set<int> seen_facets; |
544 | 551 | return meshes; |
545 | 552 | } |
546 | 553 | |
547 | void | |
548 | TriangleMesh::merge(const TriangleMesh &mesh) | |
554 | void TriangleMesh::merge(const TriangleMesh &mesh) | |
549 | 555 | { |
550 | 556 | // reset stats and metadata |
551 | 557 | int number_of_facets = this->stl.stats.number_of_facets; |
599 | 605 | return Slic3r::Geometry::convex_hull(pp); |
600 | 606 | } |
601 | 607 | |
602 | BoundingBoxf3 | |
603 | TriangleMesh::bounding_box() const | |
608 | BoundingBoxf3 TriangleMesh::bounding_box() const | |
604 | 609 | { |
605 | 610 | BoundingBoxf3 bb; |
606 | 611 | bb.defined = true; |
747 | 752 | return stl.facet_start ? &stl.facet_start->vertex[0].x : nullptr; |
748 | 753 | } |
749 | 754 | |
750 | void | |
751 | TriangleMesh::require_shared_vertices() | |
755 | void TriangleMesh::require_shared_vertices() | |
752 | 756 | { |
753 | 757 | BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; |
754 | 758 | if (!this->repaired) |
757 | 761 | BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; |
758 | 762 | stl_generate_shared_vertices(&(this->stl)); |
759 | 763 | } |
764 | #ifdef _DEBUG | |
765 | // Verify validity of neighborship data. | |
766 | for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) { | |
767 | const stl_neighbors &nbr = stl.neighbors_start[facet_idx]; | |
768 | const int *vertices = stl.v_indices[facet_idx].vertex; | |
769 | for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) { | |
770 | int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx]; | |
771 | if (nbr_face != -1) { | |
772 | assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]); | |
773 | assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]); | |
774 | } | |
775 | } | |
776 | } | |
777 | #endif /* _DEBUG */ | |
760 | 778 | BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; |
761 | 779 | } |
762 | ||
763 | 780 | |
764 | 781 | TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : |
765 | 782 | mesh(_mesh) |
846 | 863 | } |
847 | 864 | } |
848 | 865 | |
849 | void | |
850 | TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers) const | |
866 | void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers) const | |
851 | 867 | { |
852 | 868 | BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; |
853 | 869 | |
909 | 925 | { |
910 | 926 | static int iRun = 0; |
911 | 927 | for (size_t i = 0; i < z.size(); ++ i) { |
912 | Polygons &polygons = (*layers)[i]; | |
913 | SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), union_ex(polygons, true)); | |
928 | Polygons &polygons = (*layers)[i]; | |
929 | ExPolygons expolygons = union_ex(polygons, true); | |
930 | SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons); | |
931 | { | |
932 | BoundingBox bbox; | |
933 | for (const IntersectionLine &l : lines[i]) { | |
934 | bbox.merge(l.a); | |
935 | bbox.merge(l.b); | |
936 | } | |
937 | SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox); | |
938 | svg.draw(expolygons); | |
939 | for (const IntersectionLine &l : lines[i]) | |
940 | svg.draw(l, "red", 0); | |
941 | svg.draw_outline(expolygons, "black", "blue", 0); | |
942 | svg.Close(); | |
943 | } | |
944 | #if 0 | |
945 | //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. | |
914 | 946 | for (Polygon &poly : polygons) { |
915 | 947 | for (size_t i = 1; i < poly.points.size(); ++ i) |
916 | 948 | assert(poly.points[i-1] != poly.points[i]); |
917 | 949 | assert(poly.points.front() != poly.points.back()); |
918 | 950 | } |
951 | #endif | |
919 | 952 | } |
920 | 953 | ++ iRun; |
921 | 954 | } |
931 | 964 | const float min_z = fminf(facet.vertex[0].z, fminf(facet.vertex[1].z, facet.vertex[2].z)); |
932 | 965 | const float max_z = fmaxf(facet.vertex[0].z, fmaxf(facet.vertex[1].z, facet.vertex[2].z)); |
933 | 966 | |
934 | #ifdef SLIC3R_DEBUG | |
967 | #ifdef SLIC3R_TRIANGLEMESH_DEBUG | |
935 | 968 | printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, |
936 | 969 | facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z, |
937 | 970 | facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z, |
938 | 971 | facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z); |
939 | 972 | printf("z: min = %.2f, max = %.2f\n", min_z, max_z); |
940 | #endif | |
973 | #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ | |
941 | 974 | |
942 | 975 | // find layer extents |
943 | 976 | std::vector<float>::const_iterator min_layer, max_layer; |
944 | 977 | min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z |
945 | 978 | max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z |
946 | #ifdef SLIC3R_DEBUG | |
979 | #ifdef SLIC3R_TRIANGLEMESH_DEBUG | |
947 | 980 | printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); |
948 | #endif | |
981 | #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ | |
949 | 982 | |
950 | 983 | for (std::vector<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) { |
951 | 984 | std::vector<float>::size_type layer_idx = it - z.begin(); |
952 | 985 | IntersectionLine il; |
953 | if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il)) { | |
986 | if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { | |
954 | 987 | boost::lock_guard<boost::mutex> l(*lines_mutex); |
955 | 988 | if (il.edge_type == feHorizontal) { |
956 | // Insert all three edges of the face. | |
957 | const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; | |
958 | const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; | |
959 | for (int j = 0; j < 3; ++ j) { | |
960 | int a_id = vertices[j % 3]; | |
961 | int b_id = vertices[(j+1) % 3]; | |
962 | if (reverse) | |
963 | std::swap(a_id, b_id); | |
964 | const stl_vertex *a = &this->v_scaled_shared[a_id]; | |
965 | const stl_vertex *b = &this->v_scaled_shared[b_id]; | |
966 | il.a.x = a->x; | |
967 | il.a.y = a->y; | |
968 | il.b.x = b->x; | |
969 | il.b.y = b->y; | |
970 | il.a_id = a_id; | |
971 | il.b_id = b_id; | |
972 | (*lines)[layer_idx].push_back(il); | |
973 | } | |
989 | // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. | |
974 | 990 | } else |
975 | (*lines)[layer_idx].push_back(il); | |
976 | } | |
977 | } | |
978 | } | |
979 | ||
980 | void | |
981 | TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const | |
991 | (*lines)[layer_idx].emplace_back(il); | |
992 | } | |
993 | } | |
994 | } | |
995 | ||
996 | void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const | |
982 | 997 | { |
983 | 998 | std::vector<Polygons> layers_p; |
984 | 999 | this->slice(z, &layers_p); |
999 | 1014 | } |
1000 | 1015 | |
1001 | 1016 | // Return true, if the facet has been sliced and line_out has been filled. |
1002 | bool TriangleMeshSlicer::slice_facet( | |
1017 | TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( | |
1003 | 1018 | float slice_z, const stl_facet &facet, const int facet_idx, |
1004 | 1019 | const float min_z, const float max_z, |
1005 | 1020 | IntersectionLine *line_out) const |
1006 | 1021 | { |
1007 | 1022 | IntersectionPoint points[3]; |
1008 | 1023 | size_t num_points = 0; |
1009 | size_t points_on_layer[3]; | |
1010 | size_t num_points_on_layer = 0; | |
1024 | size_t point_on_layer = size_t(-1); | |
1011 | 1025 | |
1012 | 1026 | // Reorder vertices so that the first one is the one with lowest Z. |
1013 | 1027 | // This is needed to get all intersection lines in a consistent order |
1014 | 1028 | // (external on the right of the line) |
1029 | const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; | |
1015 | 1030 | int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); |
1016 | for (int j = i; j - i < 3; ++ j) { // loop through facet edges | |
1031 | for (int j = i; j - i < 3; ++j) { // loop through facet edges | |
1017 | 1032 | int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; |
1018 | const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; | |
1019 | 1033 | int a_id = vertices[j % 3]; |
1020 | 1034 | int b_id = vertices[(j+1) % 3]; |
1021 | 1035 | const stl_vertex *a = &this->v_scaled_shared[a_id]; |
1027 | 1041 | const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; |
1028 | 1042 | const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; |
1029 | 1043 | const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; |
1044 | const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal; | |
1045 | // We may ignore this edge for slicing purposes, but we may still use it for object cutting. | |
1046 | FacetSliceType result = Slicing; | |
1047 | const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx]; | |
1030 | 1048 | if (min_z == max_z) { |
1031 | 1049 | // All three vertices are aligned with slice_z. |
1032 | 1050 | line_out->edge_type = feHorizontal; |
1033 | if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { | |
1051 | result = Cutting; | |
1052 | if (normal.z < 0) { | |
1034 | 1053 | // If normal points downwards this is a bottom horizontal facet so we reverse its point order. |
1035 | 1054 | std::swap(a, b); |
1036 | 1055 | std::swap(a_id, b_id); |
1037 | 1056 | } |
1038 | } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) { | |
1039 | // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. | |
1040 | line_out->edge_type = feTop; | |
1041 | std::swap(a, b); | |
1042 | std::swap(a_id, b_id); | |
1043 | 1057 | } else { |
1044 | // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. | |
1045 | line_out->edge_type = feBottom; | |
1058 | // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. | |
1059 | int nbr_idx = j % 3; | |
1060 | int nbr_face = nbr.neighbor[nbr_idx]; | |
1061 | // Is the third vertex below the cutting plane? | |
1062 | bool third_below = v0.z < slice_z || v1.z < slice_z || v2.z < slice_z; | |
1063 | // Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice | |
1064 | // only if it is the upper edge. | |
1065 | // (the bottom most edge resp. vertex of a triangle is not owned by the triangle, but the top most edge resp. vertex is part of the triangle | |
1066 | // in respect to the cutting plane). | |
1067 | result = third_below ? Slicing : Cutting; | |
1068 | if (third_below) { | |
1069 | line_out->edge_type = feTop; | |
1070 | std::swap(a, b); | |
1071 | std::swap(a_id, b_id); | |
1072 | } else | |
1073 | line_out->edge_type = feBottom; | |
1046 | 1074 | } |
1047 | 1075 | line_out->a.x = a->x; |
1048 | 1076 | line_out->a.y = a->y; |
1050 | 1078 | line_out->b.y = b->y; |
1051 | 1079 | line_out->a_id = a_id; |
1052 | 1080 | line_out->b_id = b_id; |
1053 | return true; | |
1081 | assert(line_out->a != line_out->b); | |
1082 | return result; | |
1054 | 1083 | } |
1055 | 1084 | |
1056 | 1085 | if (a->z == slice_z) { |
1057 | 1086 | // Only point a alings with the cutting plane. |
1058 | points_on_layer[num_points_on_layer ++] = num_points; | |
1059 | IntersectionPoint &point = points[num_points ++]; | |
1060 | point.x = a->x; | |
1061 | point.y = a->y; | |
1062 | point.point_id = a_id; | |
1087 | if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { | |
1088 | point_on_layer = num_points; | |
1089 | IntersectionPoint &point = points[num_points ++]; | |
1090 | point.x = a->x; | |
1091 | point.y = a->y; | |
1092 | point.point_id = a_id; | |
1093 | } | |
1063 | 1094 | } else if (b->z == slice_z) { |
1064 | 1095 | // Only point b alings with the cutting plane. |
1065 | points_on_layer[num_points_on_layer ++] = num_points; | |
1066 | IntersectionPoint &point = points[num_points ++]; | |
1067 | point.x = b->x; | |
1068 | point.y = b->y; | |
1069 | point.point_id = b_id; | |
1096 | if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { | |
1097 | point_on_layer = num_points; | |
1098 | IntersectionPoint &point = points[num_points ++]; | |
1099 | point.x = b->x; | |
1100 | point.y = b->y; | |
1101 | point.point_id = b_id; | |
1102 | } | |
1070 | 1103 | } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { |
1071 | 1104 | // A general case. The face edge intersects the cutting plane. Calculate the intersection point. |
1072 | IntersectionPoint &point = points[num_points ++]; | |
1073 | point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); | |
1074 | point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); | |
1075 | point.edge_id = edge_id; | |
1076 | } | |
1077 | } | |
1078 | ||
1079 | // We can't have only one point on layer because each vertex gets detected | |
1080 | // twice (once for each edge), and we can't have three points on layer, | |
1081 | // because we assume this code is not getting called for horizontal facets. | |
1082 | assert(num_points_on_layer == 0 || num_points_on_layer == 2); | |
1083 | if (num_points_on_layer > 0) { | |
1084 | assert(points[points_on_layer[0]].point_id == points[points_on_layer[1]].point_id); | |
1085 | assert(num_points == 2 || num_points == 3); | |
1086 | if (num_points < 3) | |
1087 | // This triangle touches the cutting plane with a single vertex. Ignore it. | |
1088 | return false; | |
1089 | // Erase one of the duplicate points. | |
1090 | -- num_points; | |
1091 | for (int i = points_on_layer[1]; i < num_points; ++ i) | |
1092 | points[i] = points[i + 1]; | |
1093 | } | |
1094 | ||
1095 | // Facets must intersect each plane 0 or 2 times. | |
1096 | assert(num_points == 0 || num_points == 2); | |
1105 | assert(a_id != b_id); | |
1106 | // Sort the edge to give a consistent answer. | |
1107 | if (a_id > b_id) { | |
1108 | std::swap(a_id, b_id); | |
1109 | std::swap(a, b); | |
1110 | } | |
1111 | IntersectionPoint &point = points[num_points]; | |
1112 | double t = (double(slice_z) - double(b->z)) / (double(a->z) - double(b->z)); | |
1113 | if (t <= 0.) { | |
1114 | if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { | |
1115 | point.x = a->x; | |
1116 | point.y = a->y; | |
1117 | point_on_layer = num_points ++; | |
1118 | point.point_id = a_id; | |
1119 | } | |
1120 | } else if (t >= 1.) { | |
1121 | if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { | |
1122 | point.x = b->x; | |
1123 | point.y = b->y; | |
1124 | point_on_layer = num_points ++; | |
1125 | point.point_id = b_id; | |
1126 | } | |
1127 | } else { | |
1128 | point.x = coord_t(floor(double(b->x) + (double(a->x) - double(b->x)) * t + 0.5)); | |
1129 | point.y = coord_t(floor(double(b->y) + (double(a->y) - double(b->y)) * t + 0.5)); | |
1130 | point.edge_id = edge_id; | |
1131 | ++ num_points; | |
1132 | } | |
1133 | } | |
1134 | } | |
1135 | ||
1136 | // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. | |
1137 | assert(num_points < 3); | |
1097 | 1138 | if (num_points == 2) { |
1098 | line_out->edge_type = feNone; | |
1139 | line_out->edge_type = feGeneral; | |
1099 | 1140 | line_out->a = (Point)points[1]; |
1100 | 1141 | line_out->b = (Point)points[0]; |
1101 | 1142 | line_out->a_id = points[1].point_id; |
1102 | 1143 | line_out->b_id = points[0].point_id; |
1103 | 1144 | line_out->edge_a_id = points[1].edge_id; |
1104 | 1145 | line_out->edge_b_id = points[0].edge_id; |
1105 | return true; | |
1106 | } | |
1107 | return false; | |
1108 | } | |
1109 | ||
1110 | void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const | |
1111 | { | |
1112 | // Remove tangent edges. | |
1113 | //FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane. | |
1114 | for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) | |
1115 | if (! line->skip && line->edge_type != feNone) { | |
1116 | // This line is af facet edge. There may be a duplicate line with the same end vertices. | |
1117 | // If the line is is an edge connecting two facets, find another facet edge | |
1118 | // having the same endpoints but in reverse order. | |
1119 | for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2) | |
1120 | if (! line2->skip && line2->edge_type != feNone) { | |
1121 | // Are these facets adjacent? (sharing a common edge on this layer) | |
1122 | if (line->a_id == line2->a_id && line->b_id == line2->b_id) { | |
1123 | line2->skip = true; | |
1124 | /* if they are both oriented upwards or downwards (like a 'V') | |
1125 | then we can remove both edges from this layer since it won't | |
1126 | affect the sliced shape */ | |
1127 | /* if one of them is oriented upwards and the other is oriented | |
1128 | downwards, let's only keep one of them (it doesn't matter which | |
1129 | one since all 'top' lines were reversed at slicing) */ | |
1130 | if (line->edge_type == line2->edge_type) { | |
1131 | line->skip = true; | |
1146 | // Not a zero lenght edge. | |
1147 | //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. | |
1148 | //assert(line_out->a != line_out->b); | |
1149 | // The plane cuts at least one edge in a general position. | |
1150 | assert(line_out->a_id == -1 || line_out->b_id == -1); | |
1151 | assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1); | |
1152 | // General slicing position, use the segment for both slicing and object cutting. | |
1153 | #if 0 | |
1154 | if (line_out->a_id != -1 && line_out->b_id != -1) { | |
1155 | // Solving a degenerate case, where both the intersections snapped to an edge. | |
1156 | // Correctly classify the face as below or above based on the position of the 3rd point. | |
1157 | int i = vertices[0]; | |
1158 | if (i == line_out->a_id || i == line_out->b_id) | |
1159 | i = vertices[1]; | |
1160 | if (i == line_out->a_id || i == line_out->b_id) | |
1161 | i = vertices[2]; | |
1162 | assert(i != line_out->a_id && i != line_out->b_id); | |
1163 | line_out->edge_type = (this->v_scaled_shared[i].z < slice_z) ? feTop : feBottom; | |
1164 | } | |
1165 | #endif | |
1166 | return Slicing; | |
1167 | } | |
1168 | return NoSlice; | |
1169 | } | |
1170 | ||
1171 | //FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing | |
1172 | // and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. | |
1173 | // So the following code makes only sense now to handle degenerate meshes with more than two faces | |
1174 | // sharing a single edge. | |
1175 | static inline void remove_tangent_edges(std::vector<IntersectionLine> &lines) | |
1176 | { | |
1177 | std::vector<IntersectionLine*> by_vertex_pair; | |
1178 | by_vertex_pair.reserve(lines.size()); | |
1179 | for (IntersectionLine& line : lines) | |
1180 | if (line.edge_type != feGeneral && line.a_id != -1) | |
1181 | // This is a face edge. Check whether there is its neighbor stored in lines. | |
1182 | by_vertex_pair.emplace_back(&line); | |
1183 | auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) { | |
1184 | // Sort vertices of l1, l2 lexicographically | |
1185 | int l1a = l1->a_id; | |
1186 | int l1b = l1->b_id; | |
1187 | int l2a = l2->a_id; | |
1188 | int l2b = l2->b_id; | |
1189 | if (l1a > l1b) | |
1190 | std::swap(l1a, l1b); | |
1191 | if (l2a > l2b) | |
1192 | std::swap(l2a, l2b); | |
1193 | // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored. | |
1194 | return l1a < l2a || (l1a == l2a && l1b < l2b); | |
1195 | }; | |
1196 | std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted); | |
1197 | for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) { | |
1198 | IntersectionLine &l1 = **line; | |
1199 | if (! l1.skip()) { | |
1200 | // Iterate as long as line and line2 edges share the same end points. | |
1201 | for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) { | |
1202 | // Lines must share the end points. | |
1203 | assert(! edges_lower_sorted(*line, *line2)); | |
1204 | assert(! edges_lower_sorted(*line2, *line)); | |
1205 | IntersectionLine &l2 = **line2; | |
1206 | if (l2.skip()) | |
1207 | continue; | |
1208 | if (l1.a_id == l2.a_id) { | |
1209 | assert(l1.b_id == l2.b_id); | |
1210 | l2.set_skip(); | |
1211 | // If they are both oriented upwards or downwards (like a 'V'), | |
1212 | // then we can remove both edges from this layer since it won't | |
1213 | // affect the sliced shape. | |
1214 | // If one of them is oriented upwards and the other is oriented | |
1215 | // downwards, let's only keep one of them (it doesn't matter which | |
1216 | // one since all 'top' lines were reversed at slicing). | |
1217 | if (l1.edge_type == l2.edge_type) { | |
1218 | l1.set_skip(); | |
1219 | break; | |
1220 | } | |
1221 | } else { | |
1222 | assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); | |
1223 | // If this edge joins two horizontal facets, remove both of them. | |
1224 | if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) { | |
1225 | l1.set_skip(); | |
1226 | l2.set_skip(); | |
1227 | break; | |
1228 | } | |
1229 | } | |
1230 | } | |
1231 | } | |
1232 | } | |
1233 | } | |
1234 | ||
1235 | struct OpenPolyline { | |
1236 | OpenPolyline() {}; | |
1237 | OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : | |
1238 | start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } | |
1239 | void reverse() { | |
1240 | std::swap(start, end); | |
1241 | std::reverse(points.begin(), points.end()); | |
1242 | } | |
1243 | IntersectionReference start; | |
1244 | IntersectionReference end; | |
1245 | Points points; | |
1246 | double length; | |
1247 | bool consumed; | |
1248 | }; | |
1249 | ||
1250 | // called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. | |
1251 | // Only connects segments crossing triangles of the same orientation. | |
1252 | static void chain_lines_by_triangle_connectivity(std::vector<IntersectionLine> &lines, Polygons &loops, std::vector<OpenPolyline> &open_polylines) | |
1253 | { | |
1254 | // Build a map of lines by edge_a_id and a_id. | |
1255 | std::vector<IntersectionLine*> by_edge_a_id; | |
1256 | std::vector<IntersectionLine*> by_a_id; | |
1257 | by_edge_a_id.reserve(lines.size()); | |
1258 | by_a_id.reserve(lines.size()); | |
1259 | for (IntersectionLine &line : lines) { | |
1260 | if (! line.skip()) { | |
1261 | if (line.edge_a_id != -1) | |
1262 | by_edge_a_id.emplace_back(&line); | |
1263 | if (line.a_id != -1) | |
1264 | by_a_id.emplace_back(&line); | |
1265 | } | |
1266 | } | |
1267 | auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; | |
1268 | auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; | |
1269 | std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); | |
1270 | std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); | |
1271 | // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. | |
1272 | IntersectionLines::iterator it_line_seed = lines.begin(); | |
1273 | for (;;) { | |
1274 | // take first spare line and start a new loop | |
1275 | IntersectionLine *first_line = nullptr; | |
1276 | for (; it_line_seed != lines.end(); ++ it_line_seed) | |
1277 | if (it_line_seed->is_seed_candidate()) { | |
1278 | //if (! it_line_seed->skip()) { | |
1279 | first_line = &(*it_line_seed ++); | |
1280 | break; | |
1281 | } | |
1282 | if (first_line == nullptr) | |
1283 | break; | |
1284 | first_line->set_skip(); | |
1285 | Points loop_pts; | |
1286 | loop_pts.emplace_back(first_line->a); | |
1287 | IntersectionLine *last_line = first_line; | |
1288 | ||
1289 | /* | |
1290 | printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", | |
1291 | first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, | |
1292 | first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); | |
1293 | */ | |
1294 | ||
1295 | IntersectionLine key; | |
1296 | for (;;) { | |
1297 | // find a line starting where last one finishes | |
1298 | IntersectionLine* next_line = nullptr; | |
1299 | if (last_line->edge_b_id != -1) { | |
1300 | key.edge_a_id = last_line->edge_b_id; | |
1301 | auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); | |
1302 | if (it_begin != by_edge_a_id.end()) { | |
1303 | auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); | |
1304 | for (auto it_line = it_begin; it_line != it_end; ++ it_line) | |
1305 | if (! (*it_line)->skip()) { | |
1306 | next_line = *it_line; | |
1132 | 1307 | break; |
1133 | 1308 | } |
1134 | } else if (line->a_id == line2->b_id && line->b_id == line2->a_id) { | |
1135 | /* if this edge joins two horizontal facets, remove both of them */ | |
1136 | if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) { | |
1137 | line->skip = true; | |
1138 | line2->skip = true; | |
1309 | } | |
1310 | } | |
1311 | if (next_line == nullptr && last_line->b_id != -1) { | |
1312 | key.a_id = last_line->b_id; | |
1313 | auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); | |
1314 | if (it_begin != by_a_id.end()) { | |
1315 | auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); | |
1316 | for (auto it_line = it_begin; it_line != it_end; ++ it_line) | |
1317 | if (! (*it_line)->skip()) { | |
1318 | next_line = *it_line; | |
1139 | 1319 | break; |
1140 | 1320 | } |
1141 | } | |
1142 | 1321 | } |
1143 | } | |
1144 | ||
1145 | struct OpenPolyline { | |
1146 | OpenPolyline() {}; | |
1147 | OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : | |
1148 | start(start), end(end), points(std::move(points)), consumed(false) {} | |
1149 | void reverse() { | |
1150 | std::swap(start, end); | |
1151 | std::reverse(points.begin(), points.end()); | |
1152 | } | |
1153 | IntersectionReference start; | |
1154 | IntersectionReference end; | |
1155 | Points points; | |
1156 | bool consumed; | |
1322 | } | |
1323 | if (next_line == nullptr) { | |
1324 | // Check whether we closed this loop. | |
1325 | if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || | |
1326 | (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { | |
1327 | // The current loop is complete. Add it to the output. | |
1328 | loops.emplace_back(std::move(loop_pts)); | |
1329 | #ifdef SLIC3R_TRIANGLEMESH_DEBUG | |
1330 | printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); | |
1331 | #endif | |
1332 | } else { | |
1333 | // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. | |
1334 | loop_pts.emplace_back(last_line->b); | |
1335 | open_polylines.emplace_back(OpenPolyline( | |
1336 | IntersectionReference(first_line->a_id, first_line->edge_a_id), | |
1337 | IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); | |
1338 | } | |
1339 | break; | |
1340 | } | |
1341 | /* | |
1342 | printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", | |
1343 | next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, | |
1344 | next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); | |
1345 | */ | |
1346 | loop_pts.emplace_back(next_line->a); | |
1347 | last_line = next_line; | |
1348 | next_line->set_skip(); | |
1349 | } | |
1350 | } | |
1351 | } | |
1352 | ||
1353 | std::vector<OpenPolyline*> open_polylines_sorted(std::vector<OpenPolyline> &open_polylines, bool update_lengths) | |
1354 | { | |
1355 | std::vector<OpenPolyline*> out; | |
1356 | out.reserve(open_polylines.size()); | |
1357 | for (OpenPolyline &opl : open_polylines) | |
1358 | if (! opl.consumed) { | |
1359 | if (update_lengths) | |
1360 | opl.length = Slic3r::length(opl.points); | |
1361 | out.emplace_back(&opl); | |
1362 | } | |
1363 | std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; }); | |
1364 | return out; | |
1365 | } | |
1366 | ||
1367 | // called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices. | |
1368 | // Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. | |
1369 | static void chain_open_polylines_exact(std::vector<OpenPolyline> &open_polylines, Polygons &loops, bool try_connect_reversed) | |
1370 | { | |
1371 | // Store the end points of open_polylines into vectors sorted | |
1372 | struct OpenPolylineEnd { | |
1373 | OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} | |
1374 | OpenPolyline *polyline; | |
1375 | // Is it the start or end point? | |
1376 | bool start; | |
1377 | const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } | |
1378 | // Return a unique ID for the intersection point. | |
1379 | // Return a positive id for a point, or a negative id for an edge. | |
1380 | int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } | |
1381 | bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } | |
1157 | 1382 | }; |
1158 | std::vector<OpenPolyline> open_polylines; | |
1159 | { | |
1160 | // Build a map of lines by edge_a_id and a_id. | |
1161 | std::vector<IntersectionLine*> by_edge_a_id; | |
1162 | std::vector<IntersectionLine*> by_a_id; | |
1163 | by_edge_a_id.reserve(lines.size()); | |
1164 | by_a_id.reserve(lines.size()); | |
1165 | for (IntersectionLine &line : lines) { | |
1166 | if (! line.skip) { | |
1167 | if (line.edge_a_id != -1) | |
1168 | by_edge_a_id.emplace_back(&line); | |
1169 | if (line.a_id != -1) | |
1170 | by_a_id.emplace_back(&line); | |
1171 | } | |
1172 | } | |
1173 | auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; | |
1174 | auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; | |
1175 | std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); | |
1176 | std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); | |
1177 | // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. | |
1178 | IntersectionLines::iterator it_line_seed = lines.begin(); | |
1383 | auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; | |
1384 | std::vector<OpenPolylineEnd> by_id; | |
1385 | by_id.reserve(2 * open_polylines.size()); | |
1386 | for (OpenPolyline &opl : open_polylines) { | |
1387 | if (opl.start.point_id != -1 || opl.start.edge_id != -1) | |
1388 | by_id.emplace_back(OpenPolylineEnd(&opl, true)); | |
1389 | if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) | |
1390 | by_id.emplace_back(OpenPolylineEnd(&opl, false)); | |
1391 | } | |
1392 | std::sort(by_id.begin(), by_id.end(), by_id_lower); | |
1393 | // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). | |
1394 | auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector<OpenPolylineEnd>::iterator { | |
1395 | for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); | |
1396 | it != by_id.end() && it->id() == end.id(); ++ it) | |
1397 | if (*it == end) | |
1398 | return it; | |
1399 | return by_id.end(); | |
1400 | }; | |
1401 | // Try to connect the loops. | |
1402 | std::vector<OpenPolyline*> sorted_by_length = open_polylines_sorted(open_polylines, false); | |
1403 | for (OpenPolyline *opl : sorted_by_length) { | |
1404 | if (opl->consumed) | |
1405 | continue; | |
1406 | opl->consumed = true; | |
1407 | OpenPolylineEnd end(opl, false); | |
1179 | 1408 | for (;;) { |
1180 | // take first spare line and start a new loop | |
1181 | IntersectionLine *first_line = nullptr; | |
1182 | for (; it_line_seed != lines.end(); ++ it_line_seed) | |
1183 | if (! it_line_seed->skip) { | |
1184 | first_line = &(*it_line_seed ++); | |
1185 | break; | |
1186 | } | |
1187 | if (first_line == nullptr) | |
1188 | break; | |
1189 | first_line->skip = true; | |
1190 | Points loop_pts; | |
1191 | loop_pts.emplace_back(first_line->a); | |
1192 | IntersectionLine *last_line = first_line; | |
1193 | ||
1194 | /* | |
1195 | printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", | |
1196 | first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, | |
1197 | first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); | |
1198 | */ | |
1199 | ||
1200 | IntersectionLine key; | |
1201 | for (;;) { | |
1202 | // find a line starting where last one finishes | |
1203 | IntersectionLine* next_line = nullptr; | |
1204 | if (last_line->edge_b_id != -1) { | |
1205 | key.edge_a_id = last_line->edge_b_id; | |
1206 | auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); | |
1207 | if (it_begin != by_edge_a_id.end()) { | |
1208 | auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); | |
1209 | for (auto it_line = it_begin; it_line != it_end; ++ it_line) | |
1210 | if (! (*it_line)->skip) { | |
1211 | next_line = *it_line; | |
1212 | break; | |
1213 | } | |
1214 | } | |
1215 | } | |
1216 | if (next_line == nullptr && last_line->b_id != -1) { | |
1217 | key.a_id = last_line->b_id; | |
1218 | auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); | |
1219 | if (it_begin != by_a_id.end()) { | |
1220 | auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); | |
1221 | for (auto it_line = it_begin; it_line != it_end; ++ it_line) | |
1222 | if (! (*it_line)->skip) { | |
1223 | next_line = *it_line; | |
1224 | break; | |
1225 | } | |
1226 | } | |
1227 | } | |
1228 | if (next_line == nullptr) { | |
1229 | // Check whether we closed this loop. | |
1230 | if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || | |
1231 | (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { | |
1232 | // The current loop is complete. Add it to the output. | |
1233 | loops->emplace_back(std::move(loop_pts)); | |
1234 | #ifdef SLIC3R_TRIANGLEMESH_DEBUG | |
1235 | printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); | |
1236 | #endif | |
1237 | } else { | |
1238 | // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. | |
1239 | loop_pts.emplace_back(last_line->b); | |
1240 | open_polylines.emplace_back(OpenPolyline( | |
1241 | IntersectionReference(first_line->a_id, first_line->edge_a_id), | |
1242 | IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); | |
1243 | } | |
1244 | break; | |
1245 | } | |
1246 | /* | |
1247 | printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", | |
1248 | next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, | |
1249 | next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); | |
1250 | */ | |
1251 | loop_pts.emplace_back(next_line->a); | |
1252 | last_line = next_line; | |
1253 | next_line->skip = true; | |
1254 | } | |
1255 | } | |
1256 | } | |
1257 | ||
1258 | // Now process the open polylines. | |
1259 | if (! open_polylines.empty()) { | |
1260 | // Store the end points of open_polylines into vectors sorted | |
1261 | struct OpenPolylineEnd { | |
1262 | OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} | |
1263 | OpenPolyline *polyline; | |
1264 | // Is it the start or end point? | |
1265 | bool start; | |
1266 | const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } | |
1267 | int point_id() const { return ipref().point_id; } | |
1268 | int edge_id () const { return ipref().edge_id; } | |
1269 | }; | |
1270 | auto by_edge_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.edge_id() < ope2.edge_id(); }; | |
1271 | auto by_point_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.point_id() < ope2.point_id(); }; | |
1272 | std::vector<OpenPolylineEnd> by_edge_id; | |
1273 | std::vector<OpenPolylineEnd> by_point_id; | |
1274 | by_edge_id.reserve(2 * open_polylines.size()); | |
1275 | by_point_id.reserve(2 * open_polylines.size()); | |
1276 | for (OpenPolyline &opl : open_polylines) { | |
1277 | if (opl.start.edge_id != -1) | |
1278 | by_edge_id .emplace_back(OpenPolylineEnd(&opl, true)); | |
1279 | if (opl.end.edge_id != -1) | |
1280 | by_edge_id .emplace_back(OpenPolylineEnd(&opl, false)); | |
1281 | if (opl.start.point_id != -1) | |
1282 | by_point_id.emplace_back(OpenPolylineEnd(&opl, true)); | |
1283 | if (opl.end.point_id != -1) | |
1284 | by_point_id.emplace_back(OpenPolylineEnd(&opl, false)); | |
1285 | } | |
1286 | std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower); | |
1287 | std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower); | |
1288 | ||
1289 | // Try to connect the loops. | |
1290 | for (OpenPolyline &opl : open_polylines) { | |
1291 | if (opl.consumed) | |
1292 | continue; | |
1293 | opl.consumed = true; | |
1294 | OpenPolylineEnd end(&opl, false); | |
1295 | for (;;) { | |
1296 | // find a line starting where last one finishes | |
1297 | OpenPolylineEnd* next_start = nullptr; | |
1298 | if (end.edge_id() != -1) { | |
1299 | auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower); | |
1300 | if (it_begin != by_edge_id.end()) { | |
1301 | auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower); | |
1302 | for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge) | |
1303 | if (! it_edge->polyline->consumed) { | |
1304 | next_start = &(*it_edge); | |
1305 | break; | |
1306 | } | |
1307 | } | |
1308 | } | |
1309 | if (next_start == nullptr && end.point_id() != -1) { | |
1310 | auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower); | |
1311 | if (it_begin != by_point_id.end()) { | |
1312 | auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower); | |
1313 | for (auto it_point = it_begin; it_point != it_end; ++ it_point) | |
1314 | if (! it_point->polyline->consumed) { | |
1315 | next_start = &(*it_point); | |
1316 | break; | |
1317 | } | |
1318 | } | |
1319 | } | |
1320 | if (next_start == nullptr) { | |
1321 | // The current loop could not be closed. Unmark the segment. | |
1322 | opl.consumed = false; | |
1323 | break; | |
1324 | } | |
1325 | // Attach this polyline to the end of the initial polyline. | |
1326 | if (next_start->start) { | |
1327 | auto it = next_start->polyline->points.begin(); | |
1328 | std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); | |
1329 | //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end()); | |
1330 | } else { | |
1331 | auto it = next_start->polyline->points.rbegin(); | |
1332 | std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points)); | |
1333 | //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.rend()); | |
1334 | } | |
1335 | end = *next_start; | |
1336 | end.start = !end.start; | |
1337 | next_start->polyline->points.clear(); | |
1338 | next_start->polyline->consumed = true; | |
1339 | // Check whether we closed this loop. | |
1340 | const IntersectionReference &ip1 = opl.start; | |
1341 | const IntersectionReference &ip2 = end.ipref(); | |
1342 | if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) || | |
1343 | (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) { | |
1344 | // The current loop is complete. Add it to the output. | |
1345 | assert(opl.points.front().point_id == opl.points.back().point_id); | |
1346 | assert(opl.points.front().edge_id == opl.points.back().edge_id); | |
1347 | // Remove the duplicate last point. | |
1348 | opl.points.pop_back(); | |
1349 | if (opl.points.size() >= 3) { | |
1409 | // find a line starting where last one finishes | |
1410 | auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); | |
1411 | for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) | |
1412 | if (! it_next_start->polyline->consumed) | |
1413 | goto found; | |
1414 | // The current loop could not be closed. Unmark the segment. | |
1415 | opl->consumed = false; | |
1416 | break; | |
1417 | found: | |
1418 | // Attach this polyline to the end of the initial polyline. | |
1419 | if (it_next_start->start) { | |
1420 | auto it = it_next_start->polyline->points.begin(); | |
1421 | std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); | |
1422 | } else { | |
1423 | auto it = it_next_start->polyline->points.rbegin(); | |
1424 | std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); | |
1425 | } | |
1426 | opl->length += it_next_start->polyline->length; | |
1427 | // Mark the next polyline as consumed. | |
1428 | it_next_start->polyline->points.clear(); | |
1429 | it_next_start->polyline->length = 0.; | |
1430 | it_next_start->polyline->consumed = true; | |
1431 | if (try_connect_reversed) { | |
1432 | // Running in a mode, where the polylines may be connected by mixing their orientations. | |
1433 | // Update the end point lookup structure after the end point of the current polyline was extended. | |
1434 | auto it_end = find_polyline_end(end); | |
1435 | auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); | |
1436 | // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. | |
1437 | std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); | |
1438 | // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. | |
1439 | std::swap(*it_end, *it_next_end); | |
1440 | } | |
1441 | // Check whether we closed this loop. | |
1442 | if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) || | |
1443 | (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) { | |
1444 | // The current loop is complete. Add it to the output. | |
1445 | //assert(opl->points.front().point_id == opl->points.back().point_id); | |
1446 | //assert(opl->points.front().edge_id == opl->points.back().edge_id); | |
1447 | // Remove the duplicate last point. | |
1448 | opl->points.pop_back(); | |
1449 | if (opl->points.size() >= 3) { | |
1450 | if (try_connect_reversed && area(opl->points) < 0) | |
1350 | 1451 | // The closed polygon is patched from pieces with messed up orientation, therefore |
1351 | 1452 | // the orientation of the patched up polygon is not known. |
1352 | 1453 | // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. |
1353 | double area = 0.; | |
1354 | for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++) | |
1355 | area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y); | |
1356 | if (area < 0) | |
1357 | std::reverse(opl.points.begin(), opl.points.end()); | |
1358 | loops->emplace_back(std::move(opl.points)); | |
1359 | } | |
1360 | opl.points.clear(); | |
1361 | break; | |
1454 | std::reverse(opl->points.begin(), opl->points.end()); | |
1455 | loops.emplace_back(std::move(opl->points)); | |
1362 | 1456 | } |
1363 | // Continue with the current loop. | |
1364 | } | |
1365 | } | |
1366 | } | |
1457 | opl->points.clear(); | |
1458 | break; | |
1459 | } | |
1460 | // Continue with the current loop. | |
1461 | } | |
1462 | } | |
1463 | } | |
1464 | ||
1465 | // called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices, | |
1466 | // possibly closing small gaps. | |
1467 | // Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. | |
1468 | static void chain_open_polylines_close_gaps(std::vector<OpenPolyline> &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) | |
1469 | { | |
1470 | const coord_t max_gap_scaled = (coord_t)scale_(max_gap); | |
1471 | ||
1472 | // Sort the open polylines by their length, so the new loops will be seeded from longer chains. | |
1473 | // Update the polyline lengths, return only not yet consumed polylines. | |
1474 | std::vector<OpenPolyline*> sorted_by_length = open_polylines_sorted(open_polylines, true); | |
1475 | ||
1476 | // Store the end points of open_polylines into ClosestPointInRadiusLookup<OpenPolylineEnd>. | |
1477 | struct OpenPolylineEnd { | |
1478 | OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} | |
1479 | OpenPolyline *polyline; | |
1480 | // Is it the start or end point? | |
1481 | bool start; | |
1482 | const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } | |
1483 | bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } | |
1484 | }; | |
1485 | struct OpenPolylineEndAccessor { | |
1486 | const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } | |
1487 | }; | |
1488 | typedef ClosestPointInRadiusLookup<OpenPolylineEnd, OpenPolylineEndAccessor> ClosestPointLookupType; | |
1489 | ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); | |
1490 | for (OpenPolyline *opl : sorted_by_length) { | |
1491 | closest_end_point_lookup.insert(OpenPolylineEnd(opl, true)); | |
1492 | if (try_connect_reversed) | |
1493 | closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); | |
1494 | } | |
1495 | // Try to connect the loops. | |
1496 | for (OpenPolyline *opl : sorted_by_length) { | |
1497 | if (opl->consumed) | |
1498 | continue; | |
1499 | OpenPolylineEnd end(opl, false); | |
1500 | if (try_connect_reversed) | |
1501 | // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. | |
1502 | closest_end_point_lookup.erase(end); | |
1503 | opl->consumed = true; | |
1504 | size_t n_segments_joined = 1; | |
1505 | for (;;) { | |
1506 | // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). | |
1507 | std::pair<const OpenPolylineEnd*, double> next_start_and_dist = closest_end_point_lookup.find(end.point()); | |
1508 | const OpenPolylineEnd *next_start = next_start_and_dist.first; | |
1509 | // Check whether we closed this loop. | |
1510 | double current_loop_closing_distance2 = opl->points.front().distance_to_sq(opl->points.back()); | |
1511 | bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); | |
1512 | if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { | |
1513 | // Heuristics to decide, whether to close the loop, or connect another polyline. | |
1514 | // One should avoid closing loops shorter than max_gap_scaled. | |
1515 | loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points); | |
1516 | } | |
1517 | if (loop_closed) { | |
1518 | // Remove the start point of the current polyline from the lookup. | |
1519 | // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. | |
1520 | opl->consumed = false; | |
1521 | closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); | |
1522 | if (current_loop_closing_distance2 == 0.) { | |
1523 | // Remove the duplicate last point. | |
1524 | opl->points.pop_back(); | |
1525 | } else { | |
1526 | // The end points are different, keep both of them. | |
1527 | } | |
1528 | if (opl->points.size() >= 3) { | |
1529 | if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) | |
1530 | // The closed polygon is patched from pieces with messed up orientation, therefore | |
1531 | // the orientation of the patched up polygon is not known. | |
1532 | // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. | |
1533 | std::reverse(opl->points.begin(), opl->points.end()); | |
1534 | loops.emplace_back(std::move(opl->points)); | |
1535 | } | |
1536 | opl->points.clear(); | |
1537 | opl->consumed = true; | |
1538 | break; | |
1539 | } | |
1540 | if (next_start == nullptr) { | |
1541 | // The current loop could not be closed. Unmark the segment. | |
1542 | opl->consumed = false; | |
1543 | if (try_connect_reversed) | |
1544 | // Re-insert the end point. | |
1545 | closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); | |
1546 | break; | |
1547 | } | |
1548 | // Attach this polyline to the end of the initial polyline. | |
1549 | if (next_start->start) { | |
1550 | auto it = next_start->polyline->points.begin(); | |
1551 | if (*it == opl->points.back()) | |
1552 | ++ it; | |
1553 | std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points)); | |
1554 | } else { | |
1555 | auto it = next_start->polyline->points.rbegin(); | |
1556 | if (*it == opl->points.back()) | |
1557 | ++ it; | |
1558 | std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points)); | |
1559 | } | |
1560 | ++ n_segments_joined; | |
1561 | // Remove the end points of the consumed polyline segment from the lookup. | |
1562 | OpenPolyline *opl2 = next_start->polyline; | |
1563 | closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); | |
1564 | if (try_connect_reversed) | |
1565 | closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); | |
1566 | opl2->points.clear(); | |
1567 | opl2->consumed = true; | |
1568 | // Continue with the current loop. | |
1569 | } | |
1570 | } | |
1571 | } | |
1572 | ||
1573 | void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const | |
1574 | { | |
1575 | #if 0 | |
1576 | //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. | |
1577 | //#ifdef _DEBUG | |
1578 | for (const Line &l : lines) | |
1579 | assert(l.a != l.b); | |
1580 | #endif /* _DEBUG */ | |
1581 | ||
1582 | // There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane, | |
1583 | // only the bottom triangle is considered to be cutting the plane. | |
1584 | // remove_tangent_edges(lines); | |
1585 | ||
1586 | #ifdef SLIC3R_DEBUG_SLICE_PROCESSING | |
1587 | BoundingBox bbox_svg; | |
1588 | { | |
1589 | static int iRun = 0; | |
1590 | for (const Line &line : lines) { | |
1591 | bbox_svg.merge(line.a); | |
1592 | bbox_svg.merge(line.b); | |
1593 | } | |
1594 | SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg); | |
1595 | for (const Line &line : lines) | |
1596 | svg.draw(line); | |
1597 | svg.Close(); | |
1598 | } | |
1599 | #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ | |
1600 | ||
1601 | std::vector<OpenPolyline> open_polylines; | |
1602 | chain_lines_by_triangle_connectivity(lines, *loops, open_polylines); | |
1603 | ||
1604 | #ifdef SLIC3R_DEBUG_SLICE_PROCESSING | |
1605 | { | |
1606 | static int iRun = 0; | |
1607 | SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg); | |
1608 | svg.draw(union_ex(*loops)); | |
1609 | for (const OpenPolyline &pl : open_polylines) | |
1610 | svg.draw(Polyline(pl.points), "red"); | |
1611 | svg.Close(); | |
1612 | } | |
1613 | #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ | |
1614 | ||
1615 | // Now process the open polylines. | |
1616 | // Do it in two rounds, first try to connect in the same direction only, | |
1617 | // then try to connect the open polylines in reversed order as well. | |
1618 | chain_open_polylines_exact(open_polylines, *loops, false); | |
1619 | chain_open_polylines_exact(open_polylines, *loops, true); | |
1620 | ||
1621 | #ifdef SLIC3R_DEBUG_SLICE_PROCESSING | |
1622 | { | |
1623 | static int iRun = 0; | |
1624 | SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); | |
1625 | svg.draw(union_ex(*loops)); | |
1626 | for (const OpenPolyline &pl : open_polylines) { | |
1627 | if (pl.points.empty()) | |
1628 | continue; | |
1629 | svg.draw(Polyline(pl.points), "red"); | |
1630 | svg.draw(pl.points.front(), "blue"); | |
1631 | svg.draw(pl.points.back(), "blue"); | |
1632 | } | |
1633 | svg.Close(); | |
1634 | } | |
1635 | #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ | |
1636 | ||
1637 | // Try to close gaps. | |
1638 | // Do it in two rounds, first try to connect in the same direction only, | |
1639 | // then try to connect the open polylines in reversed order as well. | |
1640 | const double max_gap = 2.; //mm | |
1641 | chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); | |
1642 | chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); | |
1643 | ||
1644 | #ifdef SLIC3R_DEBUG_SLICE_PROCESSING | |
1645 | { | |
1646 | static int iRun = 0; | |
1647 | SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); | |
1648 | svg.draw(union_ex(*loops)); | |
1649 | for (const OpenPolyline &pl : open_polylines) { | |
1650 | if (pl.points.empty()) | |
1651 | continue; | |
1652 | svg.draw(Polyline(pl.points), "red"); | |
1653 | svg.draw(pl.points.front(), "blue"); | |
1654 | svg.draw(pl.points.back(), "blue"); | |
1655 | } | |
1656 | svg.Close(); | |
1657 | } | |
1658 | #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ | |
1367 | 1659 | } |
1368 | 1660 | |
1369 | 1661 | // Only used to cut the mesh into two halves. |
1543 | 1835 | |
1544 | 1836 | // intersect facet with cutting plane |
1545 | 1837 | IntersectionLine line; |
1546 | if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line)) { | |
1838 | if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) { | |
1547 | 1839 | // Save intersection lines for generating correct triangulations. |
1548 | 1840 | if (line.edge_type == feTop) { |
1549 | 1841 | lower_lines.push_back(line); |
81 | 81 | |
82 | 82 | enum FacetEdgeType { |
83 | 83 | // A general case, the cutting plane intersect a face at two different edges. |
84 | feNone, | |
84 | feGeneral, | |
85 | 85 | // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. |
86 | 86 | feTop, |
87 | 87 | // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. |
115 | 115 | class IntersectionLine : public Line |
116 | 116 | { |
117 | 117 | public: |
118 | IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {} | |
119 | ||
120 | bool skip() const { return (this->flags & SKIP) != 0; } | |
121 | void set_skip() { this->flags |= SKIP; } | |
122 | ||
123 | bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } | |
124 | void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } | |
125 | ||
118 | 126 | // Inherits Point a, b |
119 | 127 | // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. |
120 | 128 | // Vertex indices of the line end points. |
123 | 131 | // Source mesh edges of the line end points. |
124 | 132 | int edge_a_id; |
125 | 133 | int edge_b_id; |
126 | // feNone, feTop, feBottom, feHorizontal | |
134 | // feGeneral, feTop, feBottom, feHorizontal | |
127 | 135 | FacetEdgeType edge_type; |
128 | // Used by TriangleMeshSlicer::make_loops() to skip duplicate edges. | |
129 | bool skip; | |
130 | IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {}; | |
136 | // Used by TriangleMeshSlicer::slice() to skip duplicate edges. | |
137 | enum { | |
138 | // Triangle edge added, because it has no neighbor. | |
139 | EDGE0_NO_NEIGHBOR = 0x001, | |
140 | EDGE1_NO_NEIGHBOR = 0x002, | |
141 | EDGE2_NO_NEIGHBOR = 0x004, | |
142 | // Triangle edge added, because it makes a fold with another horizontal edge. | |
143 | EDGE0_FOLD = 0x010, | |
144 | EDGE1_FOLD = 0x020, | |
145 | EDGE2_FOLD = 0x040, | |
146 | // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds). | |
147 | NO_SEED = 0x100, | |
148 | SKIP = 0x200, | |
149 | }; | |
150 | uint32_t flags; | |
131 | 151 | }; |
132 | 152 | typedef std::vector<IntersectionLine> IntersectionLines; |
133 | 153 | typedef std::vector<IntersectionLine*> IntersectionLinePtrs; |
138 | 158 | TriangleMeshSlicer(TriangleMesh* _mesh); |
139 | 159 | void slice(const std::vector<float> &z, std::vector<Polygons>* layers) const; |
140 | 160 | void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const; |
141 | bool slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, | |
161 | enum FacetSliceType { | |
162 | NoSlice = 0, | |
163 | Slicing = 1, | |
164 | Cutting = 2 | |
165 | }; | |
166 | FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, | |
142 | 167 | const float min_z, const float max_z, IntersectionLine *line_out) const; |
143 | 168 | void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; |
144 | 169 |
8 | 8 | |
9 | 9 | extern void set_logging_level(unsigned int level); |
10 | 10 | extern void trace(unsigned int level, const char *message); |
11 | extern void disable_multi_threading(); | |
11 | 12 | |
12 | 13 | // Set a path with GUI resource files. |
13 | 14 | void set_var_dir(const std::string &path); |
41 | 42 | extern local_encoded_string encode_path(const char *src); |
42 | 43 | extern std::string decode_path(const char *src); |
43 | 44 | extern std::string normalize_utf8_nfc(const char *src); |
45 | ||
46 | // Safely rename a file even if the target exists. | |
47 | // On Windows, the file explorer (or anti-virus or whatever else) often locks the file | |
48 | // for a short while, so the file may not be movable. Retry while we see recoverable errors. | |
49 | extern int rename_file(const std::string &from, const std::string &to); | |
44 | 50 | |
45 | 51 | // File path / name / extension splitting utilities, working with UTF-8, |
46 | 52 | // to be published to Perl. |
13 | 13 | #include <boost/thread.hpp> |
14 | 14 | |
15 | 15 | #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" |
16 | #define SLIC3R_VERSION "1.41.0" | |
16 | #define SLIC3R_VERSION "1.41.2-beta" | |
17 | 17 | #define SLIC3R_BUILD "UNKNOWN" |
18 | 18 | |
19 | 19 | typedef int32_t coord_t; |
22 | 22 | #include <boost/nowide/fstream.hpp> |
23 | 23 | #include <boost/nowide/integration/filesystem.hpp> |
24 | 24 | #include <boost/nowide/convert.hpp> |
25 | #include <boost/nowide/cstdio.hpp> | |
26 | ||
27 | #include <tbb/task_scheduler_init.h> | |
25 | 28 | |
26 | 29 | namespace Slic3r { |
27 | 30 | |
81 | 84 | (::boost::log::keywords::severity = severity)) << message; |
82 | 85 | } |
83 | 86 | |
87 | void disable_multi_threading() | |
88 | { | |
89 | // Disable parallelization so the Shiny profiler works | |
90 | static tbb::task_scheduler_init *tbb_init = nullptr; | |
91 | if (tbb_init == nullptr) | |
92 | tbb_init = new tbb::task_scheduler_init(1); | |
93 | } | |
94 | ||
84 | 95 | static std::string g_var_dir; |
85 | 96 | |
86 | 97 | void set_var_dir(const std::string &dir) |
136 | 147 | const std::string& data_dir() |
137 | 148 | { |
138 | 149 | return g_data_dir; |
150 | } | |
151 | ||
152 | // borrowed from LLVM lib/Support/Windows/Path.inc | |
153 | int rename_file(const std::string &from, const std::string &to) | |
154 | { | |
155 | int ec = 0; | |
156 | ||
157 | #ifdef _WIN32 | |
158 | ||
159 | // Convert to utf-16. | |
160 | std::wstring wide_from = boost::nowide::widen(from); | |
161 | std::wstring wide_to = boost::nowide::widen(to); | |
162 | ||
163 | // Retry while we see recoverable errors. | |
164 | // System scanners (eg. indexer) might open the source file when it is written | |
165 | // and closed. | |
166 | bool TryReplace = true; | |
167 | ||
168 | // This loop may take more than 2000 x 1ms to finish. | |
169 | for (int i = 0; i < 2000; ++ i) { | |
170 | if (i > 0) | |
171 | // Sleep 1ms | |
172 | ::Sleep(1); | |
173 | if (TryReplace) { | |
174 | // Try ReplaceFile first, as it is able to associate a new data stream | |
175 | // with the destination even if the destination file is currently open. | |
176 | if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL)) | |
177 | return 0; | |
178 | DWORD ReplaceError = ::GetLastError(); | |
179 | ec = -1; // ReplaceError | |
180 | // If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or | |
181 | // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW(). | |
182 | if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT || | |
183 | ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) { | |
184 | TryReplace = false; | |
185 | continue; | |
186 | } | |
187 | // If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry | |
188 | // using ReplaceFileW(). | |
189 | if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED) | |
190 | continue; | |
191 | // We get ERROR_FILE_NOT_FOUND if the destination file is missing. | |
192 | // MoveFileEx can handle this case. | |
193 | if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION) | |
194 | break; | |
195 | } | |
196 | if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) | |
197 | return 0; | |
198 | DWORD MoveError = ::GetLastError(); | |
199 | ec = -1; // MoveError | |
200 | if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION) | |
201 | break; | |
202 | } | |
203 | ||
204 | #else | |
205 | ||
206 | boost::nowide::remove(to.c_str()); | |
207 | ec = boost::nowide::rename(from.c_str(), to.c_str()); | |
208 | ||
209 | #endif | |
210 | ||
211 | return ec; | |
139 | 212 | } |
140 | 213 | |
141 | 214 | } // namespace Slic3r |
42 | 42 | PresetBundle* get_preset_bundle(); |
43 | 43 | } |
44 | 44 | |
45 | static const PrintObjectStep STEP_SLICE = posSlice; | |
46 | static const PrintObjectStep STEP_PERIMETERS = posPerimeters; | |
47 | static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill; | |
48 | static const PrintObjectStep STEP_INFILL = posInfill; | |
49 | static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial; | |
50 | static const PrintStep STEP_SKIRT = psSkirt; | |
51 | static const PrintStep STEP_BRIM = psBrim; | |
52 | static const PrintStep STEP_WIPE_TOWER = psWipeTower; | |
53 | ||
54 | 45 | AppControllerBoilerplate::ProgresIndicatorPtr |
55 | 46 | AppControllerBoilerplate::global_progress_indicator() { |
56 | 47 | ProgresIndicatorPtr ret; |
70 | 61 | pri_data_->m.unlock(); |
71 | 62 | } |
72 | 63 | |
73 | void PrintController::make_skirt() | |
74 | { | |
75 | assert(print_ != nullptr); | |
76 | ||
77 | // prerequisites | |
78 | for(auto obj : print_->objects) make_perimeters(obj); | |
79 | for(auto obj : print_->objects) infill(obj); | |
80 | for(auto obj : print_->objects) gen_support_material(obj); | |
81 | ||
82 | if(!print_->state.is_done(STEP_SKIRT)) { | |
83 | print_->state.set_started(STEP_SKIRT); | |
84 | print_->skirt.clear(); | |
85 | if(print_->has_skirt()) print_->_make_skirt(); | |
86 | ||
87 | print_->state.set_done(STEP_SKIRT); | |
88 | } | |
89 | } | |
90 | ||
91 | void PrintController::make_brim() | |
92 | { | |
93 | assert(print_ != nullptr); | |
94 | ||
95 | // prerequisites | |
96 | for(auto obj : print_->objects) make_perimeters(obj); | |
97 | for(auto obj : print_->objects) infill(obj); | |
98 | for(auto obj : print_->objects) gen_support_material(obj); | |
99 | make_skirt(); | |
100 | ||
101 | if(!print_->state.is_done(STEP_BRIM)) { | |
102 | print_->state.set_started(STEP_BRIM); | |
103 | ||
104 | // since this method must be idempotent, we clear brim paths *before* | |
105 | // checking whether we need to generate them | |
106 | print_->brim.clear(); | |
107 | ||
108 | if(print_->config.brim_width > 0) print_->_make_brim(); | |
109 | ||
110 | print_->state.set_done(STEP_BRIM); | |
111 | } | |
112 | } | |
113 | ||
114 | void PrintController::make_wipe_tower() | |
115 | { | |
116 | assert(print_ != nullptr); | |
117 | ||
118 | // prerequisites | |
119 | for(auto obj : print_->objects) make_perimeters(obj); | |
120 | for(auto obj : print_->objects) infill(obj); | |
121 | for(auto obj : print_->objects) gen_support_material(obj); | |
122 | make_skirt(); | |
123 | make_brim(); | |
124 | ||
125 | if(!print_->state.is_done(STEP_WIPE_TOWER)) { | |
126 | print_->state.set_started(STEP_WIPE_TOWER); | |
127 | ||
128 | // since this method must be idempotent, we clear brim paths *before* | |
129 | // checking whether we need to generate them | |
130 | print_->brim.clear(); | |
131 | ||
132 | if(print_->has_wipe_tower()) print_->_make_wipe_tower(); | |
133 | ||
134 | print_->state.set_done(STEP_WIPE_TOWER); | |
135 | } | |
136 | } | |
137 | ||
138 | void PrintController::slice(PrintObject *pobj) | |
139 | { | |
140 | assert(pobj != nullptr && print_ != nullptr); | |
141 | ||
142 | if(pobj->state.is_done(STEP_SLICE)) return; | |
143 | ||
144 | pobj->state.set_started(STEP_SLICE); | |
145 | ||
146 | pobj->_slice(); | |
147 | ||
148 | auto msg = pobj->_fix_slicing_errors(); | |
149 | if(!msg.empty()) report_issue(IssueType::WARN, msg); | |
150 | ||
151 | // simplify slices if required | |
152 | if (print_->config.resolution) | |
153 | pobj->_simplify_slices(scale_(print_->config.resolution)); | |
154 | ||
155 | ||
156 | if(pobj->layers.empty()) | |
157 | report_issue(IssueType::ERR, | |
158 | _(L("No layers were detected. You might want to repair your " | |
159 | "STL file(s) or check their size or thickness and retry")) | |
160 | ); | |
161 | ||
162 | pobj->state.set_done(STEP_SLICE); | |
163 | } | |
164 | ||
165 | void PrintController::make_perimeters(PrintObject *pobj) | |
166 | { | |
167 | assert(pobj != nullptr); | |
168 | ||
169 | slice(pobj); | |
170 | ||
171 | if (!pobj->state.is_done(STEP_PERIMETERS)) { | |
172 | pobj->_make_perimeters(); | |
173 | } | |
174 | } | |
175 | ||
176 | void PrintController::infill(PrintObject *pobj) | |
177 | { | |
178 | assert(pobj != nullptr); | |
179 | ||
180 | make_perimeters(pobj); | |
181 | ||
182 | if (!pobj->state.is_done(STEP_PREPARE_INFILL)) { | |
183 | pobj->state.set_started(STEP_PREPARE_INFILL); | |
184 | ||
185 | pobj->_prepare_infill(); | |
186 | ||
187 | pobj->state.set_done(STEP_PREPARE_INFILL); | |
188 | } | |
189 | ||
190 | pobj->_infill(); | |
191 | } | |
192 | ||
193 | void PrintController::gen_support_material(PrintObject *pobj) | |
194 | { | |
195 | assert(pobj != nullptr); | |
196 | ||
197 | // prerequisites | |
198 | slice(pobj); | |
199 | ||
200 | if(!pobj->state.is_done(STEP_SUPPORTMATERIAL)) { | |
201 | pobj->state.set_started(STEP_SUPPORTMATERIAL); | |
202 | ||
203 | pobj->clear_support_layers(); | |
204 | ||
205 | if((pobj->config.support_material || pobj->config.raft_layers > 0) | |
206 | && pobj->layers.size() > 1) { | |
207 | pobj->_generate_support_material(); | |
208 | } | |
209 | ||
210 | pobj->state.set_done(STEP_SUPPORTMATERIAL); | |
211 | } | |
212 | } | |
213 | ||
214 | void PrintController::slice(AppControllerBoilerplate::ProgresIndicatorPtr pri) | |
215 | { | |
216 | auto st = pri->state(); | |
217 | ||
218 | Slic3r::trace(3, "Starting the slicing process."); | |
219 | ||
220 | pri->update(st+20, _(L("Generating perimeters"))); | |
221 | for(auto obj : print_->objects) make_perimeters(obj); | |
222 | ||
223 | pri->update(st+60, _(L("Infilling layers"))); | |
224 | for(auto obj : print_->objects) infill(obj); | |
225 | ||
226 | pri->update(st+70, _(L("Generating support material"))); | |
227 | for(auto obj : print_->objects) gen_support_material(obj); | |
228 | ||
229 | pri->message_fmt(_(L("Weight: %.1fg, Cost: %.1f")), | |
230 | print_->total_weight, print_->total_cost); | |
231 | pri->state(st+85); | |
232 | ||
233 | ||
234 | pri->update(st+88, _(L("Generating skirt"))); | |
235 | make_skirt(); | |
236 | ||
237 | ||
238 | pri->update(st+90, _(L("Generating brim"))); | |
239 | make_brim(); | |
240 | ||
241 | pri->update(st+95, _(L("Generating wipe tower"))); | |
242 | make_wipe_tower(); | |
243 | ||
244 | pri->update(st+100, _(L("Done"))); | |
245 | ||
246 | // time to make some statistics.. | |
247 | ||
248 | Slic3r::trace(3, _(L("Slicing process finished."))); | |
249 | } | |
250 | ||
251 | void PrintController::slice() | |
252 | { | |
253 | auto pri = global_progress_indicator(); | |
254 | if(!pri) pri = create_progress_indicator(100, L("Slicing")); | |
255 | slice(pri); | |
256 | } | |
257 | ||
258 | void IProgressIndicator::message_fmt( | |
259 | const string &fmtstr, ...) { | |
64 | void ProgressIndicator::message_fmt( | |
65 | const std::string &fmtstr, ...) { | |
260 | 66 | std::stringstream ss; |
261 | 67 | va_list args; |
262 | 68 | va_start(args, fmtstr); |
320 | 126 | for(auto& v : bedpoints) |
321 | 127 | bed.append(Point::new_scale(v.x, v.y)); |
322 | 128 | |
323 | if(pind) pind->update(0, _(L("Arranging objects..."))); | |
129 | if(pind) pind->update(0, L("Arranging objects...")); | |
324 | 130 | |
325 | 131 | try { |
132 | arr::BedShapeHint hint; | |
133 | // TODO: from Sasha from GUI | |
134 | hint.type = arr::BedShapeType::WHO_KNOWS; | |
135 | ||
326 | 136 | arr::arrange(*model_, |
327 | 137 | min_obj_distance, |
328 | 138 | bed, |
329 | arr::BOX, | |
139 | hint, | |
330 | 140 | false, // create many piles not just one pile |
331 | 141 | [pind, count](unsigned rem) { |
332 | 142 | if(pind) |
333 | pind->update(count - rem, _(L("Arranging objects..."))); | |
143 | pind->update(count - rem, L("Arranging objects...")); | |
334 | 144 | }); |
335 | 145 | } catch(std::exception& e) { |
336 | 146 | std::cerr << e.what() << std::endl; |
337 | 147 | report_issue(IssueType::ERR, |
338 | _(L("Could not arrange model objects! " | |
339 | "Some geometries may be invalid.")), | |
340 | _(L("Exception occurred"))); | |
148 | L("Could not arrange model objects! " | |
149 | "Some geometries may be invalid."), | |
150 | L("Exception occurred")); | |
341 | 151 | } |
342 | 152 | |
343 | 153 | // Restore previous max value |
344 | 154 | if(pind) { |
345 | 155 | pind->max(pmax); |
346 | pind->update(0, _(L("Arranging done."))); | |
156 | pind->update(0, L("Arranging done.")); | |
347 | 157 | } |
348 | 158 | }); |
349 | 159 |
6 | 6 | #include <atomic> |
7 | 7 | #include <iostream> |
8 | 8 | |
9 | #include "IProgressIndicator.hpp" | |
9 | #include "ProgressIndicator.hpp" | |
10 | 10 | |
11 | 11 | namespace Slic3r { |
12 | 12 | |
14 | 14 | class Print; |
15 | 15 | class PrintObject; |
16 | 16 | class PrintConfig; |
17 | ||
17 | class ProgressStatusBar; | |
18 | class DynamicPrintConfig; | |
18 | 19 | |
19 | 20 | /** |
20 | 21 | * @brief A boilerplate class for creating application logic. It should provide |
32 | 33 | public: |
33 | 34 | |
34 | 35 | /// A Progress indicator object smart pointer |
35 | using ProgresIndicatorPtr = std::shared_ptr<IProgressIndicator>; | |
36 | using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>; | |
36 | 37 | |
37 | 38 | private: |
38 | 39 | class PriData; // Some structure to store progress indication data |
45 | 46 | AppControllerBoilerplate(); |
46 | 47 | ~AppControllerBoilerplate(); |
47 | 48 | |
48 | using Path = string; | |
49 | using Path = std::string; | |
49 | 50 | using PathList = std::vector<Path>; |
50 | 51 | |
51 | 52 | /// Common runtime issue types |
66 | 67 | * @return Returns a list of paths choosed by the user. |
67 | 68 | */ |
68 | 69 | PathList query_destination_paths( |
69 | const string& title, | |
70 | const std::string& title, | |
70 | 71 | const std::string& extensions) const; |
71 | 72 | |
72 | 73 | /** |
73 | 74 | * @brief Same as query_destination_paths but works for directories only. |
74 | 75 | */ |
75 | 76 | PathList query_destination_dirs( |
76 | const string& title) const; | |
77 | const std::string& title) const; | |
77 | 78 | |
78 | 79 | /** |
79 | 80 | * @brief Same as query_destination_paths but returns only one path. |
80 | 81 | */ |
81 | 82 | Path query_destination_path( |
82 | const string& title, | |
83 | const std::string& title, | |
83 | 84 | const std::string& extensions, |
84 | 85 | const std::string& hint = "") const; |
85 | 86 | |
94 | 95 | * title. |
95 | 96 | */ |
96 | 97 | bool report_issue(IssueType issuetype, |
97 | const string& description, | |
98 | const string& brief); | |
98 | const std::string& description, | |
99 | const std::string& brief); | |
99 | 100 | |
100 | 101 | bool report_issue(IssueType issuetype, |
101 | const string& description); | |
102 | const std::string& description); | |
102 | 103 | |
103 | 104 | /** |
104 | 105 | * @brief Return the global progress indicator for the current controller. |
149 | 150 | */ |
150 | 151 | ProgresIndicatorPtr create_progress_indicator( |
151 | 152 | unsigned statenum, |
152 | const string& title, | |
153 | const string& firstmsg) const; | |
153 | const std::string& title, | |
154 | const std::string& firstmsg) const; | |
154 | 155 | |
155 | 156 | ProgresIndicatorPtr create_progress_indicator( |
156 | 157 | unsigned statenum, |
157 | const string& title) const; | |
158 | const std::string& title) const; | |
158 | 159 | |
159 | 160 | // This is a global progress indicator placeholder. In the Slic3r UI it can |
160 | 161 | // contain the progress indicator on the statusbar. |
166 | 167 | */ |
167 | 168 | class PrintController: public AppControllerBoilerplate { |
168 | 169 | Print *print_ = nullptr; |
169 | protected: | |
170 | ||
171 | void make_skirt(); | |
172 | void make_brim(); | |
173 | void make_wipe_tower(); | |
174 | ||
175 | void make_perimeters(PrintObject *pobj); | |
176 | void infill(PrintObject *pobj); | |
177 | void gen_support_material(PrintObject *pobj); | |
178 | ||
179 | /** | |
180 | * @brief Slice one pront object. | |
181 | * @param pobj The print object. | |
182 | */ | |
183 | void slice(PrintObject *pobj); | |
184 | ||
185 | void slice(ProgresIndicatorPtr pri); | |
186 | ||
187 | 170 | public: |
188 | 171 | |
189 | 172 | // Must be public for perl to use it |
197 | 180 | inline static Ptr create(Print *print) { |
198 | 181 | return PrintController::Ptr( new PrintController(print) ); |
199 | 182 | } |
200 | ||
201 | /** | |
202 | * @brief Slice the loaded print scene. | |
203 | */ | |
204 | void slice(); | |
205 | 183 | |
206 | 184 | const PrintConfig& config() const; |
207 | 185 | }; |
247 | 225 | * In perl we have a progress indicating status bar on the bottom of the |
248 | 226 | * window which is defined and created in perl. We can pass the ID-s of the |
249 | 227 | * gauge and the statusbar id and make a wrapper implementation of the |
250 | * IProgressIndicator interface so we can use this GUI widget from C++. | |
228 | * ProgressIndicator interface so we can use this GUI widget from C++. | |
251 | 229 | * |
252 | 230 | * This function should be called from perl. |
253 | 231 | * |
31 | 31 | |
32 | 32 | AppControllerBoilerplate::PathList |
33 | 33 | AppControllerBoilerplate::query_destination_paths( |
34 | const string &title, | |
34 | const std::string &title, | |
35 | 35 | const std::string &extensions) const |
36 | 36 | { |
37 | 37 | |
38 | wxFileDialog dlg(wxTheApp->GetTopWindow(), title ); | |
38 | wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); | |
39 | 39 | dlg.SetWildcard(extensions); |
40 | 40 | |
41 | 41 | dlg.ShowModal(); |
51 | 51 | |
52 | 52 | AppControllerBoilerplate::Path |
53 | 53 | AppControllerBoilerplate::query_destination_path( |
54 | const string &title, | |
54 | const std::string &title, | |
55 | 55 | const std::string &extensions, |
56 | 56 | const std::string& hint) const |
57 | 57 | { |
58 | wxFileDialog dlg(wxTheApp->GetTopWindow(), title ); | |
58 | wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); | |
59 | 59 | dlg.SetWildcard(extensions); |
60 | 60 | |
61 | 61 | dlg.SetFilename(hint); |
70 | 70 | } |
71 | 71 | |
72 | 72 | bool AppControllerBoilerplate::report_issue(IssueType issuetype, |
73 | const string &description, | |
74 | const string &brief) | |
73 | const std::string &description, | |
74 | const std::string &brief) | |
75 | 75 | { |
76 | 76 | auto icon = wxICON_INFORMATION; |
77 | 77 | auto style = wxOK|wxCENTRE; |
83 | 83 | case IssueType::FATAL: icon = wxICON_ERROR; |
84 | 84 | } |
85 | 85 | |
86 | auto ret = wxMessageBox(description, brief, icon | style); | |
86 | auto ret = wxMessageBox(_(description), _(brief), icon | style); | |
87 | 87 | return ret != wxCANCEL; |
88 | 88 | } |
89 | 89 | |
90 | 90 | bool AppControllerBoilerplate::report_issue( |
91 | 91 | AppControllerBoilerplate::IssueType issuetype, |
92 | const string &description) | |
93 | { | |
94 | return report_issue(issuetype, description, string()); | |
92 | const std::string &description) | |
93 | { | |
94 | return report_issue(issuetype, description, std::string()); | |
95 | 95 | } |
96 | 96 | |
97 | 97 | wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent); |
103 | 103 | * the main thread as well. |
104 | 104 | */ |
105 | 105 | class GuiProgressIndicator: |
106 | public IProgressIndicator, public wxEvtHandler { | |
106 | public ProgressIndicator, public wxEvtHandler { | |
107 | 107 | |
108 | 108 | wxProgressDialog gauge_; |
109 | using Base = IProgressIndicator; | |
109 | using Base = ProgressIndicator; | |
110 | 110 | wxString message_; |
111 | 111 | int range_; wxString title_; |
112 | 112 | bool is_asynch_ = false; |
135 | 135 | /// Get the mode of parallel operation. |
136 | 136 | inline bool asynch() const { return is_asynch_; } |
137 | 137 | |
138 | inline GuiProgressIndicator(int range, const string& title, | |
139 | const string& firstmsg) : | |
138 | inline GuiProgressIndicator(int range, const wxString& title, | |
139 | const wxString& firstmsg) : | |
140 | 140 | gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(), |
141 | 141 | wxPD_APP_MODAL | wxPD_AUTO_HIDE), |
142 | 142 | message_(firstmsg), |
148 | 148 | Bind(PROGRESS_STATUS_UPDATE_EVENT, |
149 | 149 | &GuiProgressIndicator::_state, |
150 | 150 | this, id_); |
151 | } | |
152 | ||
153 | virtual void cancel() override { | |
154 | update(max(), "Abort"); | |
155 | IProgressIndicator::cancel(); | |
156 | 151 | } |
157 | 152 | |
158 | 153 | virtual void state(float val) override { |
169 | 164 | } else _state(st); |
170 | 165 | } |
171 | 166 | |
172 | virtual void message(const string & msg) override { | |
173 | message_ = msg; | |
174 | } | |
175 | ||
176 | virtual void messageFmt(const string& fmt, ...) { | |
167 | virtual void message(const std::string & msg) override { | |
168 | message_ = _(msg); | |
169 | } | |
170 | ||
171 | virtual void messageFmt(const std::string& fmt, ...) { | |
177 | 172 | va_list arglist; |
178 | 173 | va_start(arglist, fmt); |
179 | message_ = wxString::Format(wxString(fmt), arglist); | |
174 | message_ = wxString::Format(_(fmt), arglist); | |
180 | 175 | va_end(arglist); |
181 | 176 | } |
182 | 177 | |
183 | virtual void title(const string & title) override { | |
184 | title_ = title; | |
178 | virtual void title(const std::string & title) override { | |
179 | title_ = _(title); | |
185 | 180 | } |
186 | 181 | }; |
187 | 182 | } |
188 | 183 | |
189 | 184 | AppControllerBoilerplate::ProgresIndicatorPtr |
190 | 185 | AppControllerBoilerplate::create_progress_indicator( |
191 | unsigned statenum, const string& title, const string& firstmsg) const | |
186 | unsigned statenum, | |
187 | const std::string& title, | |
188 | const std::string& firstmsg) const | |
192 | 189 | { |
193 | 190 | auto pri = |
194 | 191 | std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg); |
201 | 198 | } |
202 | 199 | |
203 | 200 | AppControllerBoilerplate::ProgresIndicatorPtr |
204 | AppControllerBoilerplate::create_progress_indicator(unsigned statenum, | |
205 | const string &title) const | |
206 | { | |
207 | return create_progress_indicator(statenum, title, string()); | |
201 | AppControllerBoilerplate::create_progress_indicator( | |
202 | unsigned statenum, const std::string &title) const | |
203 | { | |
204 | return create_progress_indicator(statenum, title, std::string()); | |
208 | 205 | } |
209 | 206 | |
210 | 207 | namespace { |
211 | 208 | |
212 | 209 | // A wrapper progress indicator class around the statusbar created in perl. |
213 | class Wrapper: public IProgressIndicator, public wxEvtHandler { | |
210 | class Wrapper: public ProgressIndicator, public wxEvtHandler { | |
214 | 211 | wxGauge *gauge_; |
215 | 212 | wxStatusBar *stbar_; |
216 | using Base = IProgressIndicator; | |
217 | std::string message_; | |
213 | using Base = ProgressIndicator; | |
214 | wxString message_; | |
218 | 215 | AppControllerBoilerplate& ctl_; |
219 | 216 | |
220 | 217 | void showProgress(bool show = true) { |
222 | 219 | } |
223 | 220 | |
224 | 221 | void _state(unsigned st) { |
225 | if( st <= IProgressIndicator::max() ) { | |
222 | if( st <= ProgressIndicator::max() ) { | |
226 | 223 | Base::state(st); |
227 | 224 | |
228 | 225 | if(!gauge_->IsShown()) showProgress(true); |
265 | 262 | virtual void max(float val) override { |
266 | 263 | if(val > 1.0) { |
267 | 264 | gauge_->SetRange(static_cast<int>(val)); |
268 | IProgressIndicator::max(val); | |
265 | ProgressIndicator::max(val); | |
269 | 266 | } |
270 | 267 | } |
271 | 268 | |
279 | 276 | } |
280 | 277 | } |
281 | 278 | |
282 | virtual void message(const string & msg) override { | |
283 | message_ = msg; | |
284 | } | |
285 | ||
286 | virtual void message_fmt(const string& fmt, ...) override { | |
279 | virtual void message(const std::string & msg) override { | |
280 | message_ = _(msg); | |
281 | } | |
282 | ||
283 | virtual void message_fmt(const std::string& fmt, ...) override { | |
287 | 284 | va_list arglist; |
288 | 285 | va_start(arglist, fmt); |
289 | message_ = wxString::Format(fmt, arglist); | |
286 | message_ = wxString::Format(_(fmt), arglist); | |
290 | 287 | va_end(arglist); |
291 | 288 | } |
292 | 289 | |
293 | virtual void title(const string & /*title*/) override {} | |
290 | virtual void title(const std::string & /*title*/) override {} | |
294 | 291 | |
295 | 292 | }; |
296 | 293 | } |
621 | 621 | const ModelVolume *model_volume = model_object->volumes[volume_idx]; |
622 | 622 | |
623 | 623 | int extruder_id = -1; |
624 | if (!model_volume->modifier) | |
624 | if (model_volume->is_model_part()) | |
625 | 625 | { |
626 | 626 | extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0; |
627 | 627 | if (extruder_id == 0) |
634 | 634 | volumes_idx.push_back(int(this->volumes.size())); |
635 | 635 | float color[4]; |
636 | 636 | memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); |
637 | color[3] = model_volume->modifier ? 0.5f : 1.f; | |
637 | if (model_volume->is_support_blocker()) { | |
638 | color[0] = 1.0f; | |
639 | color[1] = 0.2f; | |
640 | color[2] = 0.2f; | |
641 | } else if (model_volume->is_support_enforcer()) { | |
642 | color[0] = 0.2f; | |
643 | color[1] = 0.2f; | |
644 | color[2] = 1.0f; | |
645 | } | |
646 | color[3] = model_volume->is_model_part() ? 1.f : 0.5f; | |
638 | 647 | this->volumes.emplace_back(new GLVolume(color)); |
639 | 648 | GLVolume &v = *this->volumes.back(); |
640 | 649 | if (use_VBOs) |
657 | 666 | else if (drag_by == "instance") |
658 | 667 | v.drag_group_id = obj_idx * 1000 + instance_idx; |
659 | 668 | |
660 | if (!model_volume->modifier) | |
669 | if (model_volume->is_model_part()) | |
661 | 670 | { |
662 | 671 | v.set_convex_hull(model_volume->get_convex_hull()); |
663 | 672 | v.layer_height_texture = layer_height_texture; |
664 | 673 | if (extruder_id != -1) |
665 | 674 | v.extruder_id = extruder_id; |
666 | 675 | } |
667 | v.is_modifier = model_volume->modifier; | |
668 | v.shader_outside_printer_detection_enabled = !model_volume->modifier; | |
676 | v.is_modifier = ! model_volume->is_model_part(); | |
677 | v.shader_outside_printer_detection_enabled = model_volume->is_model_part(); | |
669 | 678 | v.set_origin(Pointf3(instance->offset.x, instance->offset.y, 0.0)); |
670 | 679 | v.set_angle_z(instance->rotation); |
671 | 680 | v.set_scale_factor(instance->scaling_factor); |
1181 | 1190 | b1_prev = b1; |
1182 | 1191 | v_prev = v; |
1183 | 1192 | |
1184 | if (bottom_z_different) | |
1193 | if (bottom_z_different && (closed || (!is_first && !is_last))) | |
1185 | 1194 | { |
1186 | 1195 | // Found a change of the layer thickness -> Add a cap at the beginning of this segment. |
1187 | 1196 | volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); |
1189 | 1198 | |
1190 | 1199 | if (! closed) { |
1191 | 1200 | // Terminate open paths with caps. |
1192 | if (is_first && !bottom_z_different) | |
1201 | if (is_first) | |
1193 | 1202 | volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); |
1194 | 1203 | // We don't use 'else' because both cases are true if we have only one line. |
1195 | if (is_last && !bottom_z_different) | |
1204 | if (is_last) | |
1196 | 1205 | volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]); |
1197 | 1206 | } |
1198 | 1207 |
15 | 15 | #include <boost/property_tree/ini_parser.hpp> |
16 | 16 | #include <boost/property_tree/ptree.hpp> |
17 | 17 | #include <boost/algorithm/string/predicate.hpp> |
18 | #include <boost/format.hpp> | |
19 | ||
18 | 20 | |
19 | 21 | namespace Slic3r { |
20 | 22 | |
59 | 61 | |
60 | 62 | if (get("remember_output_path").empty()) |
61 | 63 | set("remember_output_path", "1"); |
64 | ||
65 | // Remove legacy window positions/sizes | |
66 | erase("", "main_frame_maximized"); | |
67 | erase("", "main_frame_pos"); | |
68 | erase("", "main_frame_size"); | |
69 | erase("", "object_settings_maximized"); | |
70 | erase("", "object_settings_pos"); | |
71 | erase("", "object_settings_size"); | |
62 | 72 | } |
63 | 73 | |
64 | 74 | void AppConfig::load() |
116 | 126 | |
117 | 127 | void AppConfig::save() |
118 | 128 | { |
129 | // The config is first written to a file with a PID suffix and then moved | |
130 | // to avoid race conditions with multiple instances of Slic3r | |
131 | ||
132 | const auto path = config_path(); | |
133 | std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str(); | |
134 | ||
119 | 135 | boost::nowide::ofstream c; |
120 | c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc); | |
136 | c.open(path_pid, std::ios::out | std::ios::trunc); | |
121 | 137 | c << "# " << Slic3r::header_slic3r_generated() << std::endl; |
122 | 138 | // Make sure the "no" category is written first. |
123 | 139 | for (const std::pair<std::string, std::string> &kvp : m_storage[""]) |
146 | 162 | } |
147 | 163 | } |
148 | 164 | c.close(); |
165 | ||
166 | rename_file(path_pid, path); | |
167 | ||
149 | 168 | m_dirty = false; |
150 | 169 | } |
151 | 170 |
71 | 71 | bool has(const std::string &key) const |
72 | 72 | { return this->has("", key); } |
73 | 73 | |
74 | void erase(const std::string §ion, const std::string &key) | |
75 | { | |
76 | auto it = m_storage.find(section); | |
77 | if (it != m_storage.end()) { | |
78 | it->second.erase(key); | |
79 | } | |
80 | } | |
81 | ||
74 | 82 | void clear_section(const std::string §ion) |
75 | 83 | { m_storage[section].clear(); } |
76 | 84 |
408 | 408 | |
409 | 409 | void PageFirmware::apply_custom_config(DynamicPrintConfig &config) |
410 | 410 | { |
411 | ConfigOptionEnum<GCodeFlavor> opt; | |
412 | ||
413 | 411 | auto sel = gcode_picker->GetSelection(); |
414 | if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) { | |
415 | config.set_key_value("gcode_flavor", &opt); | |
412 | if (sel >= 0 && sel < gcode_opt.enum_labels.size()) { | |
413 | auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel)); | |
414 | config.set_key_value("gcode_flavor", opt); | |
416 | 415 | } |
417 | 416 | } |
418 | 417 | |
870 | 869 | // If the screen is smaller, resize wizrad to match, which will enable scrollbars. |
871 | 870 | auto wizard_size = GetSize(); |
872 | 871 | unsigned width, height; |
873 | GUI::get_current_screen_size(width, height); | |
874 | wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN))); | |
875 | wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN))); | |
876 | SetMinSize(wizard_size); | |
872 | if (GUI::get_current_screen_size(this, width, height)) { | |
873 | wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN))); | |
874 | wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN))); | |
875 | SetMinSize(wizard_size); | |
876 | } | |
877 | 877 | Fit(); |
878 | 878 | |
879 | 879 | p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); |
223 | 223 | }), temp->GetId()); |
224 | 224 | #endif // __WXGTK__ |
225 | 225 | |
226 | temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent) | |
226 | temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt) | |
227 | 227 | { |
228 | 228 | #ifdef __WXGTK__ |
229 | bChangedValueEvent = true; | |
230 | #else | |
229 | if (bChangedValueEvent) | |
230 | #endif //__WXGTK__ | |
231 | 231 | on_change_field(); |
232 | #endif //__WXGTK__ | |
233 | 232 | }), temp->GetId()); |
234 | 233 | |
235 | 234 | #ifdef __WXGTK__ |
236 | temp->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) | |
237 | { | |
238 | if (bChangedValueEvent) { | |
239 | on_change_field(); | |
240 | bChangedValueEvent = false; | |
241 | } | |
242 | event.Skip(); | |
243 | }); | |
235 | // to correct value updating on GTK we should: | |
236 | // call on_change_field() on wxEVT_KEY_UP instead of wxEVT_TEXT | |
237 | // and prevent value updating on wxEVT_KEY_DOWN | |
238 | temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this); | |
239 | temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this); | |
244 | 240 | #endif //__WXGTK__ |
245 | 241 | |
246 | 242 | // select all text using Ctrl+A |
265 | 261 | |
266 | 262 | void TextCtrl::enable() { dynamic_cast<wxTextCtrl*>(window)->Enable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(true); } |
267 | 263 | void TextCtrl::disable() { dynamic_cast<wxTextCtrl*>(window)->Disable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(false); } |
264 | ||
265 | #ifdef __WXGTK__ | |
266 | void TextCtrl::change_field_value(wxEvent& event) | |
267 | { | |
268 | if (bChangedValueEvent = event.GetEventType()==wxEVT_KEY_UP) | |
269 | on_change_field(); | |
270 | event.Skip(); | |
271 | }; | |
272 | #endif //__WXGTK__ | |
268 | 273 | |
269 | 274 | void CheckBox::BUILD() { |
270 | 275 | auto size = wxSize(wxDefaultSize); |
221 | 221 | class TextCtrl : public Field { |
222 | 222 | using Field::Field; |
223 | 223 | #ifdef __WXGTK__ |
224 | bool bChangedValueEvent = false; | |
224 | bool bChangedValueEvent = true; | |
225 | void change_field_value(wxEvent& event); | |
225 | 226 | #endif //__WXGTK__ |
226 | 227 | public: |
227 | 228 | TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} |
366 | 366 | |
367 | 367 | auto ports = Utils::scan_serial_ports_extended(); |
368 | 368 | ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { |
369 | return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT; | |
369 | return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT; | |
370 | 370 | }), ports.end()); |
371 | 371 | |
372 | 372 | if (ports.size() == 1) { |
389 | 389 | |
390 | 390 | void FirmwareDialog::priv::lookup_port_mmu() |
391 | 391 | { |
392 | static const auto msg_not_found = | |
393 | "The Multi Material Control device was not found.\n" | |
394 | "If the device is connected, please press the Reset button next to the USB connector ..."; | |
395 | ||
392 | 396 | BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; |
393 | 397 | |
394 | 398 | auto ports = Utils::scan_serial_ports_extended(); |
395 | 399 | ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { |
396 | return port.id_vendor != USB_VID_PRUSA && | |
400 | return port.id_vendor != USB_VID_PRUSA || | |
397 | 401 | port.id_product != USB_PID_MMU_BOOT && |
398 | 402 | port.id_product != USB_PID_MMU_APP; |
399 | 403 | }), ports.end()); |
400 | 404 | |
401 | 405 | if (ports.size() == 0) { |
402 | 406 | BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ..."; |
403 | ||
404 | queue_status(_(L( | |
405 | "The Multi Material Control device was not found.\n" | |
406 | "If the device is connected, please press the Reset button next to the USB connector ..." | |
407 | ))); | |
408 | ||
407 | queue_status(_(L(msg_not_found))); | |
409 | 408 | wait_for_mmu_bootloader(30); |
410 | 409 | } else if (ports.size() > 1) { |
411 | 410 | BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; |
416 | 415 | BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port; |
417 | 416 | mmu_reboot(ports[0]); |
418 | 417 | wait_for_mmu_bootloader(10); |
418 | ||
419 | if (! port) { | |
420 | // The device in bootloader mode was not found, inform the user and wait some more... | |
421 | 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 ..."; | |
422 | queue_status(_(L(msg_not_found))); | |
423 | wait_for_mmu_bootloader(30); | |
424 | } | |
419 | 425 | } else { |
420 | 426 | port = ports[0]; |
421 | 427 | } |
701 | 707 | panel->SetSizer(vsizer); |
702 | 708 | |
703 | 709 | auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); |
704 | p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY); | |
710 | p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr, | |
711 | "Hex files (*.hex)|*.hex|All files|*.*"); | |
705 | 712 | |
706 | 713 | auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:"))); |
707 | 714 | p->port_picker = new wxComboBox(panel, wxID_ANY); |
3435 | 3435 | { |
3436 | 3436 | const Size& cnv_size = get_canvas_size(); |
3437 | 3437 | _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); |
3438 | if (m_canvas != nullptr) | |
3439 | m_canvas->Refresh(); | |
3438 | ||
3439 | // Because of performance problems on macOS, where PaintEvents are not delivered | |
3440 | // frequently enough, we call render() here directly when we can. | |
3441 | // We can't do that when m_force_zoom_to_bed_enabled == true, because then render() | |
3442 | // ends up calling back here via _force_zoom_to_bed(), causing a stack overflow. | |
3443 | if (m_canvas != nullptr) { | |
3444 | m_force_zoom_to_bed_enabled ? m_canvas->Refresh() : render(); | |
3445 | } | |
3440 | 3446 | } |
3441 | 3447 | } |
3442 | 3448 |
6 | 6 | #include <boost/lexical_cast.hpp> |
7 | 7 | #include <boost/algorithm/string.hpp> |
8 | 8 | #include <boost/format.hpp> |
9 | #include <boost/lexical_cast.hpp> | |
9 | 10 | |
10 | 11 | #if __APPLE__ |
11 | 12 | #import <IOKit/pwr_mgt/IOPMLib.h> |
825 | 826 | double brim_width = config->opt_float("brim_width"); |
826 | 827 | if (boost::any_cast<bool>(value) == true) |
827 | 828 | { |
828 | new_val = m_brim_width == 0.0 ? 10 : | |
829 | new_val = m_brim_width == 0.0 ? 5 : | |
829 | 830 | m_brim_width < 0.0 ? m_brim_width * (-1) : |
830 | 831 | m_brim_width; |
831 | 832 | } |
973 | 974 | |
974 | 975 | } |
975 | 976 | |
976 | void get_current_screen_size(unsigned &width, unsigned &height) | |
977 | { | |
978 | wxDisplay display(wxDisplay::GetFromWindow(g_wxMainFrame)); | |
977 | bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height) | |
978 | { | |
979 | const auto idx = wxDisplay::GetFromWindow(window); | |
980 | if (idx == wxNOT_FOUND) { | |
981 | return false; | |
982 | } | |
983 | ||
984 | wxDisplay display(idx); | |
979 | 985 | const auto disp_size = display.GetClientArea(); |
980 | 986 | width = disp_size.GetWidth(); |
981 | 987 | height = disp_size.GetHeight(); |
982 | } | |
988 | ||
989 | return true; | |
990 | } | |
991 | ||
992 | void save_window_size(wxTopLevelWindow *window, const std::string &name) | |
993 | { | |
994 | const wxSize size = window->GetSize(); | |
995 | const wxPoint pos = window->GetPosition(); | |
996 | const auto maximized = window->IsMaximized() ? "1" : "0"; | |
997 | ||
998 | g_AppConfig->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str()); | |
999 | g_AppConfig->set((boost::format("window_%1%_maximized") % name).str(), maximized); | |
1000 | } | |
1001 | ||
1002 | void restore_window_size(wxTopLevelWindow *window, const std::string &name) | |
1003 | { | |
1004 | // XXX: This still doesn't behave nicely in some situations (mostly on Linux). | |
1005 | // The problem is that it's hard to obtain window position with respect to screen geometry reliably | |
1006 | // from wxWidgets. Sometimes wxWidgets claim a window is located on a different screen than on which | |
1007 | // it's actually visible. I suspect this has something to do with window initialization (maybe we | |
1008 | // restore window geometry too early), but haven't yet found a workaround. | |
1009 | ||
1010 | const auto display_idx = wxDisplay::GetFromWindow(window); | |
1011 | if (display_idx == wxNOT_FOUND) { return; } | |
1012 | ||
1013 | const auto display = wxDisplay(display_idx).GetClientArea(); | |
1014 | std::vector<std::string> pair; | |
1015 | ||
1016 | try { | |
1017 | const auto key_size = (boost::format("window_%1%_size") % name).str(); | |
1018 | if (g_AppConfig->has(key_size)) { | |
1019 | if (unescape_strings_cstyle(g_AppConfig->get(key_size), pair) && pair.size() == 2) { | |
1020 | auto width = boost::lexical_cast<int>(pair[0]); | |
1021 | auto height = boost::lexical_cast<int>(pair[1]); | |
1022 | ||
1023 | window->SetSize(width, height); | |
1024 | } | |
1025 | } | |
1026 | } catch(const boost::bad_lexical_cast &) {} | |
1027 | ||
1028 | // Maximizing should be the last thing to do. | |
1029 | // This ensure the size and position are sane when the user un-maximizes the window. | |
1030 | const auto key_maximized = (boost::format("window_%1%_maximized") % name).str(); | |
1031 | if (g_AppConfig->get(key_maximized) == "1") { | |
1032 | window->Maximize(true); | |
1033 | } | |
1034 | } | |
1035 | ||
983 | 1036 | |
984 | 1037 | void about() |
985 | 1038 | { |
23 | 23 | class wxFlexGridSizer; |
24 | 24 | class wxButton; |
25 | 25 | class wxFileDialog; |
26 | class wxTopLevelWindow; | |
26 | 27 | |
27 | 28 | namespace Slic3r { |
28 | 29 | |
181 | 182 | int get_export_option(wxFileDialog* dlg); |
182 | 183 | |
183 | 184 | // Returns the dimensions of the screen on which the main frame is displayed |
184 | void get_current_screen_size(unsigned &width, unsigned &height); | |
185 | bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height); | |
186 | ||
187 | // Save window size and maximized status into AppConfig | |
188 | void save_window_size(wxTopLevelWindow *window, const std::string &name); | |
189 | // Restore the above | |
190 | void restore_window_size(wxTopLevelWindow *window, const std::string &name); | |
185 | 191 | |
186 | 192 | // Display an About dialog |
187 | 193 | extern void about(); |
291 | 291 | "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", |
292 | 292 | "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", |
293 | 293 | "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", |
294 | "min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers", | |
294 | "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", | |
295 | 295 | "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", |
296 | 296 | "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", |
297 | 297 | "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", |
416 | 416 | try { |
417 | 417 | Preset preset(m_type, name, false); |
418 | 418 | preset.file = dir_entry.path().string(); |
419 | preset.load(keys); | |
419 | DynamicPrintConfig &config = preset.load(keys); | |
420 | // Report configuration fields, which are misplaced into a wrong group. | |
421 | std::string incorrect_keys; | |
422 | if (config.remove_keys_not_in(this->default_preset().config, incorrect_keys) > 0) | |
423 | BOOST_LOG_TRIVIAL(error) << "Error in \"" << dir_entry.path().string() << "\": The preset contains the following incorrect keys: " << | |
424 | incorrect_keys << ", which were ignored"; | |
425 | // Normalize once again to set the length of the filament specific vectors to 1. | |
426 | Preset::normalize(config); | |
420 | 427 | m_presets.emplace_back(preset); |
421 | 428 | } catch (const std::runtime_error &err) { |
422 | 429 | errors_cummulative += err.what(); |
918 | 918 | DynamicPrintConfig config(default_config); |
919 | 919 | for (auto &kvp : section.second) |
920 | 920 | config.set_deserialize(kvp.first, kvp.second.data()); |
921 | Preset::normalize(config); | |
922 | 921 | // Report configuration fields, which are misplaced into a wrong group. |
923 | 922 | std::string incorrect_keys; |
924 | size_t n_incorrect_keys = 0; | |
925 | for (const std::string &key : config.keys()) | |
926 | if (! default_config.has(key)) { | |
927 | if (incorrect_keys.empty()) | |
928 | incorrect_keys = key; | |
929 | else { | |
930 | incorrect_keys += ", "; | |
931 | incorrect_keys += key; | |
932 | } | |
933 | config.erase(key); | |
934 | ++ n_incorrect_keys; | |
935 | } | |
936 | if (! incorrect_keys.empty()) | |
923 | if (config.remove_keys_not_in(default_config, incorrect_keys) > 0) | |
937 | 924 | BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << |
938 | section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; | |
925 | section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; | |
926 | Preset::normalize(config); | |
939 | 927 | if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) { |
940 | 928 | // Filter out printer presets, which are not mentioned in the vendor profile. |
941 | 929 | // These presets are considered not installed. |
846 | 846 | page = add_options_page(_(L("Support material")), "building.png"); |
847 | 847 | optgroup = page->new_optgroup(_(L("Support material"))); |
848 | 848 | optgroup->append_single_option_line("support_material"); |
849 | optgroup->append_single_option_line("support_material_auto"); | |
849 | 850 | optgroup->append_single_option_line("support_material_threshold"); |
850 | 851 | optgroup->append_single_option_line("support_material_enforce_layers"); |
851 | 852 | |
1182 | 1183 | |
1183 | 1184 | bool have_raft = m_config->opt_int("raft_layers") > 0; |
1184 | 1185 | bool have_support_material = m_config->opt_bool("support_material") || have_raft; |
1186 | bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto"); | |
1185 | 1187 | bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; |
1186 | 1188 | bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; |
1187 | for (auto el : {"support_material_threshold", "support_material_pattern", "support_material_with_sheath", | |
1189 | for (auto el : {"support_material_pattern", "support_material_with_sheath", | |
1188 | 1190 | "support_material_spacing", "support_material_angle", "support_material_interface_layers", |
1189 | 1191 | "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", |
1190 | 1192 | "support_material_xy_spacing" }) |
1191 | 1193 | get_field(el)->toggle(have_support_material); |
1194 | get_field("support_material_threshold")->toggle(have_support_material_auto); | |
1192 | 1195 | |
1193 | 1196 | for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", |
1194 | 1197 | "support_material_interface_speed", "support_material_interface_contact_loops" }) |
0 | #ifndef IPROGRESSINDICATOR_HPP | |
1 | #define IPROGRESSINDICATOR_HPP | |
2 | ||
3 | #include <string> | |
4 | #include <functional> | |
5 | #include "Strings.hpp" | |
6 | ||
7 | namespace Slic3r { | |
8 | ||
9 | /** | |
10 | * @brief Generic progress indication interface. | |
11 | */ | |
12 | class IProgressIndicator { | |
13 | public: | |
14 | using CancelFn = std::function<void(void)>; // Cancel functio signature. | |
15 | ||
16 | private: | |
17 | float state_ = .0f, max_ = 1.f, step_; | |
18 | CancelFn cancelfunc_ = [](){}; | |
19 | ||
20 | public: | |
21 | ||
22 | inline virtual ~IProgressIndicator() {} | |
23 | ||
24 | /// Get the maximum of the progress range. | |
25 | float max() const { return max_; } | |
26 | ||
27 | /// Get the current progress state | |
28 | float state() const { return state_; } | |
29 | ||
30 | /// Set the maximum of hte progress range | |
31 | virtual void max(float maxval) { max_ = maxval; } | |
32 | ||
33 | /// Set the current state of the progress. | |
34 | virtual void state(float val) { state_ = val; } | |
35 | ||
36 | /** | |
37 | * @brief Number of states int the progress. Can be used insted of giving a | |
38 | * maximum value. | |
39 | */ | |
40 | virtual void states(unsigned statenum) { | |
41 | step_ = max_ / statenum; | |
42 | } | |
43 | ||
44 | /// Message shown on the next status update. | |
45 | virtual void message(const string&) = 0; | |
46 | ||
47 | /// Title of the operaton. | |
48 | virtual void title(const string&) = 0; | |
49 | ||
50 | /// Formatted message for the next status update. Works just like sprinf. | |
51 | virtual void message_fmt(const string& fmt, ...); | |
52 | ||
53 | /// Set up a cancel callback for the operation if feasible. | |
54 | inline void on_cancel(CancelFn func) { cancelfunc_ = func; } | |
55 | ||
56 | /** | |
57 | * Explicitly shut down the progress indicator and call the associated | |
58 | * callback. | |
59 | */ | |
60 | virtual void cancel() { cancelfunc_(); } | |
61 | ||
62 | /// Convinience function to call message and status update in one function. | |
63 | void update(float st, const string& msg) { | |
64 | message(msg); state(st); | |
65 | } | |
66 | }; | |
67 | ||
68 | } | |
69 | ||
70 | #endif // IPROGRESSINDICATOR_HPP |
0 | #ifndef IPROGRESSINDICATOR_HPP | |
1 | #define IPROGRESSINDICATOR_HPP | |
2 | ||
3 | #include <string> | |
4 | #include <functional> | |
5 | ||
6 | namespace Slic3r { | |
7 | ||
8 | /** | |
9 | * @brief Generic progress indication interface. | |
10 | */ | |
11 | class ProgressIndicator { | |
12 | public: | |
13 | using CancelFn = std::function<void(void)>; // Cancel function signature. | |
14 | ||
15 | private: | |
16 | float state_ = .0f, max_ = 1.f, step_; | |
17 | CancelFn cancelfunc_ = [](){}; | |
18 | ||
19 | public: | |
20 | ||
21 | inline virtual ~ProgressIndicator() {} | |
22 | ||
23 | /// Get the maximum of the progress range. | |
24 | float max() const { return max_; } | |
25 | ||
26 | /// Get the current progress state | |
27 | float state() const { return state_; } | |
28 | ||
29 | /// Set the maximum of the progress range | |
30 | virtual void max(float maxval) { max_ = maxval; } | |
31 | ||
32 | /// Set the current state of the progress. | |
33 | virtual void state(float val) { state_ = val; } | |
34 | ||
35 | /** | |
36 | * @brief Number of states int the progress. Can be used instead of giving a | |
37 | * maximum value. | |
38 | */ | |
39 | virtual void states(unsigned statenum) { | |
40 | step_ = max_ / statenum; | |
41 | } | |
42 | ||
43 | /// Message shown on the next status update. | |
44 | virtual void message(const std::string&) = 0; | |
45 | ||
46 | /// Title of the operation. | |
47 | virtual void title(const std::string&) = 0; | |
48 | ||
49 | /// Formatted message for the next status update. Works just like sprintf. | |
50 | virtual void message_fmt(const std::string& fmt, ...); | |
51 | ||
52 | /// Set up a cancel callback for the operation if feasible. | |
53 | virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; } | |
54 | ||
55 | /// Convenience function to call message and status update in one function. | |
56 | void update(float st, const std::string& msg) { | |
57 | message(msg); state(st); | |
58 | } | |
59 | }; | |
60 | ||
61 | } | |
62 | ||
63 | #endif // IPROGRESSINDICATOR_HPP |
0 | #ifndef STRINGS_HPP | |
1 | #define STRINGS_HPP | |
2 | ||
3 | #include "GUI/GUI.hpp" | |
4 | ||
5 | namespace Slic3r { | |
6 | using string = wxString; | |
7 | } | |
8 | ||
9 | #endif // STRINGS_HPP |
196 | 196 | { |
197 | 197 | return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%") |
198 | 198 | % get_base_url() |
199 | % filename | |
199 | % Http::url_encode(filename) | |
200 | 200 | % timestamp_str()).str(); |
201 | 201 | } |
202 | 202 | |
229 | 229 | auto tm = *std::localtime(&t); |
230 | 230 | |
231 | 231 | char buffer[BUFFER_SIZE]; |
232 | std::strftime(buffer, BUFFER_SIZE, "time=%Y-%d-%mT%H:%M:%S", &tm); | |
232 | std::strftime(buffer, BUFFER_SIZE, "time=%Y-%m-%dT%H:%M:%S", &tm); | |
233 | 233 | |
234 | 234 | return std::string(buffer); |
235 | 235 | } |
247 | 247 | bool Duet::start_print(wxString &msg, const std::string &filename) const |
248 | 248 | { |
249 | 249 | bool res = false; |
250 | ||
250 | 251 | auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"") |
251 | 252 | % get_base_url() |
252 | % filename).str(); | |
253 | % Http::url_encode(filename)).str(); | |
253 | 254 | |
254 | 255 | auto http = Http::get(std::move(url)); |
255 | 256 | http.on_error([&](std::string body, std::string error, unsigned status) { |
274 | 275 | return root.get<int>("err", 0); |
275 | 276 | } |
276 | 277 | |
277 | ||
278 | } | |
278 | } |
420 | 420 | return res; |
421 | 421 | } |
422 | 422 | |
423 | std::string Http::url_encode(const std::string &str) | |
424 | { | |
425 | ::CURL *curl = ::curl_easy_init(); | |
426 | if (curl == nullptr) { | |
427 | return str; | |
428 | } | |
429 | char *ce = ::curl_easy_escape(curl, str.c_str(), str.length()); | |
430 | std::string encoded = std::string(ce); | |
431 | ||
432 | ::curl_free(ce); | |
433 | ::curl_easy_cleanup(curl); | |
434 | ||
435 | return encoded; | |
436 | } | |
437 | ||
423 | 438 | std::ostream& operator<<(std::ostream &os, const Http::Progress &progress) |
424 | 439 | { |
425 | 440 | os << "Http::Progress(" |
97 | 97 | |
98 | 98 | // Tells whether current backend supports seting up a CA file using ca_file() |
99 | 99 | static bool ca_file_supported(); |
100 | ||
101 | // converts the given string to an url_encoded_string | |
102 | static std::string url_encode(const std::string &str); | |
100 | 103 | private: |
101 | 104 | Http(const std::string &url); |
102 | 105 |
230 | 230 | spi.port = path; |
231 | 231 | #ifdef __linux__ |
232 | 232 | auto friendly_name = sysfs_tty_prop(name, "product"); |
233 | spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path; | |
233 | if (friendly_name) { | |
234 | spi.is_printer = looks_like_printer(*friendly_name); | |
235 | spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str(); | |
236 | } else { | |
237 | spi.friendly_name = path; | |
238 | } | |
234 | 239 | auto vid = sysfs_tty_prop_hex(name, "idVendor"); |
235 | 240 | auto pid = sysfs_tty_prop_hex(name, "idProduct"); |
236 | 241 | if (vid && pid) { |
78 | 78 | my $m = Slic3r::TriangleMesh->new; |
79 | 79 | $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); |
80 | 80 | $m->repair; |
81 | my @z = (0,2,4,8,6,8,10,12,14,16,18,20); | |
81 | # The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be | |
82 | # open intervals at the bottom end, closed at the top end. | |
83 | my @z = (0.0001,2,4,8,6,8,10,12,14,16,18,20); | |
82 | 84 | my $result = $m->slice(\@z); |
83 | 85 | my $SCALING_FACTOR = 0.000001; |
84 | 86 | for my $i (0..$#z) { |
104 | 106 | # this second test also checks that performing a second slice on a mesh after |
105 | 107 | # a transformation works properly (shared_vertices is correctly invalidated); |
106 | 108 | # at Z = -10 we have a bottom horizontal surface |
107 | my $slices = $m->slice([ -5, -10 ]); | |
109 | # (The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be | |
110 | # open intervals at the bottom end, closed at the top end, so the Z = -10 is shifted a bit up to get a valid slice). | |
111 | my $slices = $m->slice([ -5, -10+0.00001 ]); | |
108 | 112 | is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area'; |
109 | 113 | } |
110 | 114 | } |
7 | 7 | %} |
8 | 8 | |
9 | 9 | %name{Slic3r::PrintController} class PrintController { |
10 | ||
11 | 10 | PrintController(Print *print); |
12 | ||
13 | void slice(); | |
14 | 11 | }; |
15 | 12 | |
16 | 13 | %name{Slic3r::AppController} class AppController { |
110 | 110 | void deregister_on_request_update_callback() |
111 | 111 | %code%{ Slic3r::GUI::g_on_request_update_callback.deregister_callback(); %}; |
112 | 112 | |
113 | void save_window_size(SV *window, std::string name) | |
114 | %code%{ Slic3r::GUI::save_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %}; | |
115 | ||
116 | void restore_window_size(SV *window, std::string name) | |
117 | %code%{ Slic3r::GUI::restore_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %}; | |
118 |
16 | 16 | %code%{ RETVAL = &THIS->thin_fills; %}; |
17 | 17 | Ref<SurfaceCollection> fill_surfaces() |
18 | 18 | %code%{ RETVAL = &THIS->fill_surfaces; %}; |
19 | Ref<SurfaceCollection> perimeter_surfaces() | |
20 | %code%{ RETVAL = &THIS->perimeter_surfaces; %}; | |
21 | 19 | Polygons bridged() |
22 | 20 | %code%{ RETVAL = THIS->bridged; %}; |
23 | 21 | Ref<PolylineCollection> unsupported_bridge_edges() |
337 | 337 | %code%{ RETVAL = &THIS->config; %}; |
338 | 338 | Ref<TriangleMesh> mesh() |
339 | 339 | %code%{ RETVAL = &THIS->mesh; %}; |
340 | Ref<TriangleMesh> convex_hull() | |
341 | %code%{ RETVAL = &THIS->get_convex_hull(); %}; | |
340 | 342 | |
341 | 343 | bool modifier() |
342 | %code%{ RETVAL = THIS->modifier; %}; | |
344 | %code%{ RETVAL = THIS->is_modifier(); %}; | |
343 | 345 | void set_modifier(bool modifier) |
344 | %code%{ THIS->modifier = modifier; %}; | |
346 | %code%{ THIS->set_type(modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); %}; | |
347 | bool model_part() | |
348 | %code%{ RETVAL = THIS->is_model_part(); %}; | |
349 | bool support_enforcer() | |
350 | %code%{ RETVAL = THIS->is_support_enforcer(); %}; | |
351 | void set_support_enforcer() | |
352 | %code%{ THIS->set_type(ModelVolume::SUPPORT_ENFORCER); %}; | |
353 | bool support_blocker() | |
354 | %code%{ RETVAL = THIS->is_support_blocker(); %}; | |
355 | void set_support_blocker() | |
356 | %code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %}; | |
345 | 357 | |
346 | 358 | size_t split(unsigned int max_extruders); |
347 | 359 |
276 | 276 | } |
277 | 277 | RETVAL = THIS->total_cost; |
278 | 278 | OUTPUT: |
279 | RETVAL | |
280 | ||
281 | double | |
282 | Print::total_wipe_tower_cost(...) | |
283 | CODE: | |
284 | if (items > 1) { | |
285 | THIS->total_wipe_tower_cost = (double)SvNV(ST(1)); | |
286 | } | |
287 | RETVAL = THIS->total_wipe_tower_cost; | |
288 | OUTPUT: | |
289 | RETVAL | |
290 | ||
291 | double | |
292 | Print::total_wipe_tower_filament(...) | |
293 | CODE: | |
294 | if (items > 1) { | |
295 | THIS->total_wipe_tower_filament = (double)SvNV(ST(1)); | |
296 | } | |
297 | RETVAL = THIS->total_wipe_tower_filament; | |
298 | OUTPUT: | |
299 | RETVAL | |
300 | ||
301 | int | |
302 | Print::m_wipe_tower_number_of_toolchanges(...) | |
303 | CODE: | |
304 | if (items > 1) { | |
305 | THIS->m_wipe_tower_number_of_toolchanges = (double)SvNV(ST(1)); | |
306 | } | |
307 | RETVAL = THIS->m_wipe_tower_number_of_toolchanges; | |
308 | OUTPUT: | |
279 | 309 | RETVAL |
280 | 310 | %} |
281 | 311 | }; |