Codebase list slic3r-prusa / cd5213c
New upstream version 1.41.2+dfsg Chow Loong Jin 5 years ago
115 changed file(s) with 6200 addition(s) and 3085 deletion(s). Raw diff Collapse all Expand all
8787 $self->Fit;
8888 $self->SetMinSize([760, 490]);
8989 $self->SetSize($self->GetMinSize);
90 wxTheApp->restore_window_pos($self, "main_frame");
90 Slic3r::GUI::restore_window_size($self, "main_frame");
9191 $self->Show;
9292 $self->Layout;
9393 }
100100 return;
101101 }
102102 # save window size
103 wxTheApp->save_window_pos($self, "main_frame");
103 Slic3r::GUI::save_window_size($self, "main_frame");
104104 # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback,
105105 # but in rare cases it may not have been called yet.
106106 wxTheApp->{app_config}->save;
237237 my @expolygons = ();
238238 foreach my $volume (@{$self->{model_object}->volumes}) {
239239 next if !$volume->mesh;
240 next if $volume->modifier;
240 next if !$volume->model_part;
241241 my $expp = $volume->mesh->slice([ $z_cut ])->[0];
242242 push @expolygons, @$expp;
243243 }
1515 use constant ICON_OBJECT => 0;
1616 use constant ICON_SOLIDMESH => 1;
1717 use constant ICON_MODIFIERMESH => 2;
18 use constant ICON_SUPPORT_ENFORCER => 3;
19 use constant ICON_SUPPORT_BLOCKER => 4;
1820
1921 sub new {
2022 my ($class, $parent, %params) = @_;
3436 y => 0,
3537 z => 0,
3638 };
37
39
3840 # create TreeCtrl
3941 my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100],
4042 wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT
4547 $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT
4648 $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH
4749 $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
4852
4953 my $rootId = $tree->AddRoot("Object", ICON_OBJECT);
5054 $tree->SetPlData($rootId, { type => 'object' });
8892 $self->{btn_move_down}->SetFont($Slic3r::GUI::small_font);
8993
9094 # 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 });
92103 my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL);
93104 $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
94105
224235 my $selectedId = $rootId;
225236 foreach my $volume_id (0..$#{$object->volumes}) {
226237 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;
229243 my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
230244 if ($volume_id == $selected_volume_idx) {
231245 $selectedId = $itemId;
287301
288302 if (my $itemData = $self->get_selection) {
289303 my ($config, @opt_keys);
304 my $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_OBJECT;
305 my $support = 0;
290306 if ($itemData->{type} eq 'volume') {
291307 # select volume in 3D preview
292308 if ($self->{canvas}) {
300316 # attach volume config to settings panel
301317 my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
302318
303 if ($volume->modifier) {
319 if (! $volume->model_part) {
304320 $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 }
305329 } else {
330 $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART;
306331 $self->{optgroup_movers}->disable;
307332 }
308333 $config = $volume->config;
309334 $self->{staticbox}->SetLabel('Part Settings');
310
311335 # get default values
312 @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys};
336 @opt_keys = $support ? () : @{Slic3r::Config::PrintRegion->new->get_keys};
313337 } elsif ($itemData->{type} eq 'object') {
314338 # select nothing in 3D preview
315339
322346 # get default values
323347 my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys);
324348
325 # decide which settings will be shown by default
349 # decide which settings will be shown by default
326350 if ($itemData->{type} eq 'object') {
327351 $config->set_ifndef('wipe_into_objects', 0);
328352 $config->set_ifndef('wipe_into_infill', 0);
329353 }
330354
331355 # 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);
335362 $self->{settings_panel}->set_default_config($default_config);
336363 $self->{settings_panel}->set_config($config);
337364 $self->{settings_panel}->set_opt_keys(\@opt_keys);
338365
339366 # 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);
346371 $self->{settings_panel}->enable;
347372 }
348373
349374 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 }
350395 }
351396
352397 sub on_btn_load {
361406 }
362407
363408 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 }
364418 foreach my $volume (@{$object->volumes}) {
365419 my $new_volume = $self->{model_object}->add_volume($volume);
366420 $new_volume->set_modifier($is_modifier);
367421 $new_volume->set_name(basename($input_file));
368422
369423 # 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 }
371428
372429 # set a default extruder value, since user can't add it manually
373430 $new_volume->config->set_ifndef('extruder', 0);
3232 $self->{layers}->Closing;
3333
3434 # save window size
35 wxTheApp->save_window_pos($self, "object_settings");
35 Slic3r::GUI::save_window_size($self, "object_settings");
3636
3737 $self->EndModal(wxID_OK);
3838 $self->{parts}->Destroy;
4848
4949 $self->Layout;
5050
51 wxTheApp->restore_window_pos($self, "object_settings");
51 Slic3r::GUI::restore_window_size($self, "object_settings");
5252
5353 return $self;
5454 }
66 use utf8;
77
88 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);
1211 use base 'Wx::ScrolledWindow';
1312
1413 use constant ICON_MATERIAL => 0;
1514 use constant ICON_SOLIDMESH => 1;
1615 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;
1722
1823 my %icons = (
1924 'Advanced' => 'wand.png',
3540 $self->{config} = Slic3r::Config->new;
3641 # On change callback.
3742 $self->{on_change} = $params{on_change};
43 $self->{type} = TYPE_OBJECT;
3844 $self->{fixed_options} = {};
3945
4046 $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
4147
4248 $self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
4349 $self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0);
44
50
4551 # option selector
4652 {
4753 # create the button
109115 $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ];
110116 }
111117
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
112128 sub set_fixed_options {
113129 my ($self, $opt_keys) = @_;
114130 $self->{fixed_options} = { map {$_ => 1} @$opt_keys };
120136
121137 $self->{options_sizer}->Clear(1);
122138 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
124154 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 }
129161 }
130162 foreach my $category (sort keys %categories) {
131163 my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
135167 full_labels => 1,
136168 label_font => $Slic3r::GUI::small_font,
137169 sidetext_font => $Slic3r::GUI::small_font,
138 label_width => 120,
170 label_width => 150,
139171 on_change => sub { $self->{on_change}->() if $self->{on_change} },
140172 extra_column => sub {
141173 my ($line) = @_;
766766 $model->convert_multipart_object(scalar(@$nozzle_dmrs)) if $dialog->ShowModal() == wxID_YES;
767767 }
768768
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
769778 if ($one_by_one) {
770779 push @obj_idx, $self->load_model_objects(@{$model->objects});
771780 } else {
16451654 $grid_sizer->AddGrowableCol(1, 1);
16461655 $grid_sizer->AddGrowableCol(3, 1);
16471656 $print_info_sizer->Add($grid_sizer, 0, wxEXPAND);
1657 my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0;
16481658 my @info = (
16491659 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
16511668 L("Used Filament (mm³)")
16521669 => sprintf("%.2f" , $self->{print}->total_extruded_volume),
16531670 L("Used Filament (g)"),
16541671 => sprintf("%.2f" , $self->{print}->total_weight),
16551672 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),
16571680 L("Estimated printing time (normal mode)")
16581681 => $self->{print}->estimated_normal_print_time,
16591682 L("Estimated printing time (silent mode)")
16601683 => $self->{print}->estimated_silent_print_time
16611684 );
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
16621688 while ( my $label = shift @info) {
16631689 my $value = shift @info;
16641690 next if $value eq "N/A";
16731699
16741700 $scrolled_window_sizer->Show(2, $show);
16751701 $scrolled_window_panel->Layout;
1702 $self->Layout;
16761703 }
16771704
16781705 sub do_print {
355355 }
356356 }
357357
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
382358 1;
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
04 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
114 0.2.2 Edited MMU2 Single mode purge line
215 0.2.1 Added PET and BVOH settings for MMU2
316 0.2.0-beta5 Fixed MMU1 ramming parameters
44 name = Prusa Research
55 # Configuration version of this file. Config file will only be installed, if the config_version differs.
66 # This means, the server may force the Slic3r configuration to be downgraded.
7 config_version = 0.2.2
7 config_version = 0.3.2
88 # Where to get the updates from?
99 config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/
1010
3333 [printer_model:MK2SMM]
3434 name = Original Prusa i3 MK2/S MMU 1.0
3535 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
3640
3741 # All presets starting with asterisk, for example *common*, are intermediate and they will
3842 # not make it into the user interface.
180184 support_material_synchronize_layers = 1
181185 support_material_threshold = 80
182186 support_material_with_sheath = 1
183 wipe_tower = 1
184187
185188 # XXXXXXXXXXXXXXXXXXXX
186189 # XXX--- 0.05mm ---XXX
328331 [print:0.15mm 100mms Linear Advance]
329332 inherits = *0.15mm*
330333 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
332335 external_perimeter_speed = 50
333336 infill_speed = 100
334337 max_print_speed = 150
340343
341344 [print:0.15mm OPTIMAL]
342345 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
344347 top_infill_extrusion_width = 0.45
345348
346349 [print:0.15mm OPTIMAL 0.25 nozzle]
373376 solid_infill_speed = 200
374377 top_solid_infill_speed = 50
375378
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
376397 [print:0.15mm OPTIMAL SOLUBLE FULL]
377398 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
379400 external_perimeter_speed = 25
380401 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder
381402 perimeter_speed = 40
382403 solid_infill_speed = 40
383404 top_infill_extrusion_width = 0.45
384405 top_solid_infill_speed = 30
385 wipe_tower = 1
386406
387407 [print:0.15mm OPTIMAL SOLUBLE INTERFACE]
388408 inherits = 0.15mm OPTIMAL SOLUBLE FULL
403423 perimeter_speed = 45
404424 solid_infill_speed = 200
405425 top_solid_infill_speed = 50
426
406427 [print:*0.20mm*]
407428 inherits = *common*
408429 bottom_solid_layers = 4
435456
436457 [print:0.20mm 100mms Linear Advance]
437458 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
439460 external_perimeter_speed = 50
440461 infill_speed = 100
441462 max_print_speed = 150
457478 solid_infill_speed = 200
458479 top_solid_infill_speed = 50
459480
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
460499 [print:0.20mm NORMAL]
461500 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
463502
464503 [print:0.20mm NORMAL 0.6 nozzle]
465504 inherits = *0.20mm*; *0.6nozzle*
467506
468507 [print:0.20mm NORMAL SOLUBLE FULL]
469508 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
471510 external_perimeter_speed = 30
472511 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder
473512 perimeter_speed = 40
518557 [print:0.35mm FAST]
519558 inherits = *0.35mm*
520559 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
522561 first_layer_extrusion_width = 0.42
523562 perimeter_extrusion_width = 0.43
524563 solid_infill_extrusion_width = 0.7
546585 support_material_with_sheath = 0
547586 support_material_xy_spacing = 150%
548587
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
549635 # XXXXXXxxXXXXXXXXXXXXXX
550636 # XXX--- filament ---XXX
551637 # XXXXXXXXxxXXXXXXXXXXXX
554640 cooling = 1
555641 compatible_printers =
556642 # 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)
558644 end_filament_gcode = "; Filament-specific end gcode"
559645 extrusion_multiplier = 1
560646 filament_loading_speed = 28
654740 [filament:ColorFabb Brass Bronze]
655741 inherits = *PLA*
656742 # 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)
658744 extrusion_multiplier = 1.2
659745 filament_cost = 80.65
660746 filament_density = 4
686772 [filament:ColorFabb Woodfil]
687773 inherits = *PLA*
688774 # 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)
690776 extrusion_multiplier = 1.2
691777 filament_cost = 62.9
692778 filament_density = 1.15
788874 [filament:Fillamentum Timberfil]
789875 inherits = *PLA*
790876 # 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)
792878 extrusion_multiplier = 1.2
793879 filament_cost = 68
794880 filament_density = 1.15
850936
851937 [filament:*ABS MMU2*]
852938 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
854940 filament_cooling_final_speed = 50
855941 filament_cooling_initial_speed = 10
856942 filament_cooling_moves = 5
888974
889975 [filament:*PET MMU2*]
890976 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
892978 temperature = 230
893979 first_layer_temperature = 230
894980 filament_cooling_final_speed = 1
9141000 filament_density = 1.24
9151001 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"
9161002
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
9171010 [filament:*PLA MMU2*]
9181011 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
9201013 temperature = 205
9211014 filament_cooling_final_speed = 1
9221015 filament_cooling_initial_speed = 2
9311024 inherits = *PLA MMU2*
9321025
9331026 [filament:Prusa PLA MMU2]
1027 inherits = *PLA MMU2*
1028
1029 [filament:Prusament PLA MMU2]
9341030 inherits = *PLA MMU2*
9351031
9361032 [filament:SemiFlex or Flexfill 98A]
9971093
9981094 [filament:Verbatim BVOH MMU2]
9991095 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
10011097 temperature = 195
10021098 filament_notes = BVOH
10031099 fan_always_on = 1
10971193 printer_model = MK2S
10981194 printer_variant = 0.4
10991195 default_print_profile = 0.15mm OPTIMAL
1100 default_filament_profile = Prusa PLA
1196 default_filament_profile = Prusament PLA
11011197
11021198 [printer:*multimaterial*]
11031199 inherits = *common*
11921288 inherits = Original Prusa i3 MK2
11931289 printer_model = MK2.5
11941290 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
11961356
11971357 [printer:Original Prusa i3 MK2.5 0.25 nozzle]
11981358 inherits = Original Prusa i3 MK2 0.25 nozzle
11991359 printer_model = MK2.5
12001360 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
12021362
12031363 [printer:Original Prusa i3 MK2.5 0.6 nozzle]
12041364 inherits = Original Prusa i3 MK2 0.6 nozzle
12051365 printer_model = MK2.5
12061366 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
12081368
12091369 # XXXXXXXXXXXXXXXXX
12101370 # XXX--- MK3 ---XXX
12201380 machine_max_acceleration_y = 1000,960
12211381 machine_max_acceleration_z = 1000,1000
12221382 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
12251385 machine_max_feedrate_z = 12,12
12261386 machine_max_jerk_e = 1.5,1.5
12271387 machine_max_jerk_x = 8,8
12341394 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
12351395 retract_lift_below = 209
12361396 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}
12381398 printer_model = MK3
12391399 default_print_profile = 0.15mm OPTIMAL MK3
12401400
12641424 extra_loading_move = -13
12651425 printer_model = MK3MMU2
12661426 default_print_profile = 0.15mm OPTIMAL MK3
1267 default_filament_profile = Prusa PLA MMU2
1427 default_filament_profile = Prusament PLA MMU2
12681428
12691429 [printer:Original Prusa i3 MK3 MMU2 Single]
12701430 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
12721433 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
12731434
12741435 [printer:Original Prusa i3 MK3 MMU2]
12791440 machine_max_acceleration_e = 8000,8000
12801441 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4
12811442 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
12831444 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
12841445
12851446 # 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.
12861447 [obsolete_presets]
12871448 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"
266266 ${LIBDIR}/slic3r/Utils/Time.hpp
267267 ${LIBDIR}/slic3r/Utils/HexFile.cpp
268268 ${LIBDIR}/slic3r/Utils/HexFile.hpp
269 ${LIBDIR}/slic3r/IProgressIndicator.hpp
269 ${LIBDIR}/slic3r/ProgressIndicator.hpp
270270 ${LIBDIR}/slic3r/AppController.hpp
271271 ${LIBDIR}/slic3r/AppController.cpp
272272 ${LIBDIR}/slic3r/AppControllerWx.cpp
273 ${LIBDIR}/slic3r/Strings.hpp
274273 )
275274
276275 add_library(admesh STATIC
503502 endif ()
504503
505504 # 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")
507506 set_target_properties(XS PROPERTIES
508507 COMPILE_FLAGS "/Zi"
509508 LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF"
747746 set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
748747
749748 add_subdirectory(${LIBDIR}/libnest2d)
749 target_compile_definitions(libslic3r PUBLIC -DUSE_TBB)
750750 target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
751751 target_include_directories(libslic3r_gui PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
752752
286286 {
287287 // skip solid/endsolid
288288 // (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");
290290 fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid")
291291 // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs.
292292 int res_normal = fscanf(stl->fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]);
3030 ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp
3131 ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp
3232 ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp
33 ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/rotfinder.hpp
3334 ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp
3435 ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp
3536 ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp
8889 endif()
8990
9091 if(LIBNEST2D_BUILD_EXAMPLES)
92
9193 add_executable(example examples/main.cpp
9294 # tools/libnfpglue.hpp
9395 # tools/libnfpglue.cpp
96 tools/nfp_svgnest.hpp
97 tools/nfp_svgnest_glue.hpp
9498 tools/svgtools.hpp
9599 tests/printer_parts.cpp
96100 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)
98115
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()
99125
100126 target_link_libraries(example ${LIBNEST2D_LIBRARIES})
101127 target_include_directories(example PUBLIC ${LIBNEST2D_HEADERS})
88 existing implementation to avoid copying or having unnecessary dependencies.
99
1010 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
1212 fast and robust, being built on top of boost geometry and the
1313 [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.
1616
1717 This software is currently under construction and lacks a throughout
1818 documentation and some essential algorithms as well. At this stage it works well
1919 for rectangles and convex closed polygons without considering holes and
2020 concavities.
2121
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.
2333
2434 # References
2535 - [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()
00 #include <iostream>
11 #include <string>
22 #include <fstream>
3
43 //#define DEBUG_EXPORT_NFP
54
65 #include <libnest2d.h>
87 #include "tests/printer_parts.h"
98 #include "tools/benchmark.h"
109 #include "tools/svgtools.hpp"
10 #include "libnest2d/rotfinder.hpp"
11
1112 //#include "tools/libnfpglue.hpp"
13 //#include "tools/nfp_svgnest_glue.hpp"
14
1215
1316 using namespace libnest2d;
1417 using ItemGroup = std::vector<std::reference_wrapper<Item>>;
4952 using namespace libnest2d;
5053
5154 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
538101 std::vector<Item> input;
539102 input.insert(input.end(), prusaParts().begin(), prusaParts().end());
540103 // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end());
541104 // input.insert(input.end(), stegoParts().begin(), stegoParts().end());
542105 // 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());
545106
546107 Box bin(250*SCALE, 210*SCALE);
547108 // PolygonImpl bin = {
559120 // {}
560121 // };
561122
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>;
566129
567130 Packer arrange(bin, min_obj_distance);
568131
570133 pconf.alignment = Placer::Config::Alignment::CENTER;
571134 pconf.starting_point = Placer::Config::Alignment::CENTER;
572135 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;
669138
670139 Packer::SelectionConfig sconf;
671140 // sconf.allow_parallel = false;
672141 // sconf.force_parallel = false;
673142 // sconf.try_triplets = true;
674143 // sconf.try_reverse_order = true;
675 // sconf.waste_increment = 0.005;
144 // sconf.waste_increment = 0.01;
676145
677146 arrange.configure(pconf, sconf);
678147
679148 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");
686149 std::cout << "Remaining items: " << r << std::endl;
687 })/*.useMinimumBoundigBoxRotation()*/;
150 });
151
152 // findMinimumBoundingBoxRotations(input.begin(), input.end());
688153
689154 Benchmark bench;
690155
692157 Packer::ResultType result;
693158
694159 try {
695 result = arrange.arrange(input.begin(), input.end());
160 result = arrange.execute(input.begin(), input.end());
696161 } catch(GeometryException& ge) {
697162 std::cerr << "Geometry error: " << ge.what() << std::endl;
698163 return ;
706171 std::vector<double> eff;
707172 eff.reserve(result.size());
708173
709 auto bin_area = ShapeLike::area<PolygonImpl>(bin);
174 auto bin_area = sl::area(bin);
710175 for(auto& r : result) {
711176 double a = 0;
712177 std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); });
727192 for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); }
728193 std::cout << ") Total: " << total << std::endl;
729194
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 // }
734199
735200 if(total != input.size()) std::cout << "ERROR " << "could not pack "
736201 << input.size() - total << " elements!"
743208 SVGWriter svgw(conf);
744209 svgw.setSize(Box(250*SCALE, 210*SCALE));
745210 svgw.writePackGroup(result);
746 // std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);});
747211 svgw.save("out");
748212 }
749213
750214 int main(void /*int argc, char **argv*/) {
751215 arrangeRectangles();
752 // findDegenerateCase();
753
754216 return EXIT_SUCCESS;
755217 }
3535 using libnest2d::setY;
3636 using Box = libnest2d::_Box<PointImpl>;
3737 using Segment = libnest2d::_Segment<PointImpl>;
38 using Shapes = libnest2d::Nfp::Shapes<PolygonImpl>;
38 using Shapes = libnest2d::nfp::Shapes<PolygonImpl>;
3939
4040 }
4141
240240
241241 template<> struct exterior_ring<bp2d::PolygonImpl> {
242242 static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) {
243 return libnest2d::ShapeLike::getContour(p);
243 return libnest2d::shapelike::getContour(p);
244244 }
245245
246246 static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) {
247 return libnest2d::ShapeLike::getContour(p);
247 return libnest2d::shapelike::getContour(p);
248248 }
249249 };
250250
270270 static inline libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
271271 bp2d::PolygonImpl& p)
272272 {
273 return libnest2d::ShapeLike::holes(p);
273 return libnest2d::shapelike::holes(p);
274274 }
275275
276276 static inline const libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
277277 bp2d::PolygonImpl const& p)
278278 {
279 return libnest2d::ShapeLike::holes(p);
279 return libnest2d::shapelike::holes(p);
280280 }
281281 };
282282
310310
311311 namespace libnest2d { // Now the algorithms that boost can provide...
312312
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 )
316316 {
317317 return boost::geometry::distance(p1, p2);
318318 }
319319
320320 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 )
323322 {
324323 return boost::geometry::distance(p, seg);
325324 }
326
325 }
326
327 namespace shapelike {
327328 // Tell libnest2d how to make string out of a ClipperPolygon object
328329 template<>
329 inline bool ShapeLike::intersects(const PathImpl& sh1,
330 const PathImpl& sh2)
330 inline bool intersects(const PathImpl& sh1, const PathImpl& sh2)
331331 {
332332 return boost::geometry::intersects(sh1, sh2);
333333 }
334334
335335 // Tell libnest2d how to make string out of a ClipperPolygon object
336336 template<>
337 inline bool ShapeLike::intersects(const PolygonImpl& sh1,
338 const PolygonImpl& sh2)
337 inline bool intersects(const PolygonImpl& sh1, const PolygonImpl& sh2)
339338 {
340339 return boost::geometry::intersects(sh1, sh2);
341340 }
342341
343342 // Tell libnest2d how to make string out of a ClipperPolygon object
344343 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)
347345 {
348346 return boost::geometry::intersects(s1, s2);
349347 }
350348
351349 #ifndef DISABLE_BOOST_AREA
352350 template<>
353 inline double ShapeLike::area(const PolygonImpl& shape)
351 inline double area(const PolygonImpl& shape, const PolygonTag&)
354352 {
355353 return boost::geometry::area(shape);
356354 }
357355 #endif
358356
359357 template<>
360 inline bool ShapeLike::isInside<PolygonImpl>(const PointImpl& point,
361 const PolygonImpl& shape)
358 inline bool isInside(const PointImpl& point, const PolygonImpl& shape)
362359 {
363360 return boost::geometry::within(point, shape);
364361 }
365362
366363 template<>
367 inline bool ShapeLike::isInside(const PolygonImpl& sh1,
368 const PolygonImpl& sh2)
364 inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2)
369365 {
370366 return boost::geometry::within(sh1, sh2);
371367 }
372368
373369 template<>
374 inline bool ShapeLike::touches( const PolygonImpl& sh1,
375 const PolygonImpl& sh2)
370 inline bool touches(const PolygonImpl& sh1, const PolygonImpl& sh2)
376371 {
377372 return boost::geometry::touches(sh1, sh2);
378373 }
379374
380375 template<>
381 inline bool ShapeLike::touches( const PointImpl& point,
382 const PolygonImpl& shape)
376 inline bool touches( const PointImpl& point, const PolygonImpl& shape)
383377 {
384378 return boost::geometry::touches(point, shape);
385379 }
386380
387381 #ifndef DISABLE_BOOST_BOUNDING_BOX
388382 template<>
389 inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh)
383 inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&)
390384 {
391385 bp2d::Box b;
392386 boost::geometry::envelope(sh, b);
394388 }
395389
396390 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&)
398393 {
399394 bp2d::Box b;
400395 boost::geometry::envelope(shapes, b);
404399
405400 #ifndef DISABLE_BOOST_CONVEX_HULL
406401 template<>
407 inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh)
402 inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&)
408403 {
409404 PolygonImpl ret;
410405 boost::geometry::convex_hull(sh, ret);
412407 }
413408
414409 template<>
415 inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes)
410 inline PolygonImpl convexHull(const TMultiShape<PolygonImpl>& shapes,
411 const MultiPolygonTag&)
416412 {
417413 PolygonImpl ret;
418414 boost::geometry::convex_hull(shapes, ret);
420416 }
421417 #endif
422418
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
451419 #ifndef DISABLE_BOOST_OFFSET
452420 template<>
453 inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance)
421 inline void offset(PolygonImpl& sh, bp2d::Coord distance)
454422 {
455423 PolygonImpl cpy = sh;
456424 boost::geometry::buffer(cpy, sh, distance);
457425 }
458426 #endif
459427
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
471428 #ifndef DISABLE_BOOST_SERIALIZE
472 template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
429 template<> inline std::string serialize<libnest2d::Formats::SVG>(
473430 const PolygonImpl& sh, double scale)
474431 {
475432 std::stringstream ss;
481438
482439 Polygonf::ring_type ring;
483440 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++) {
487444 auto& v = *it;
488445 ring.emplace_back(getX(v)*scale, getY(v)*scale);
489446 };
490447
491 auto H = ShapeLike::holes(sh);
448 auto H = shapelike::holes(sh);
492449 for(PathImpl& h : H ) {
493450 Polygonf::ring_type hf;
494451 for(auto it = h.begin(); it != h.end(); it++) {
511468
512469 #ifndef DISABLE_BOOST_UNSERIALIZE
513470 template<>
514 inline void ShapeLike::unserialize<libnest2d::Formats::SVG>(
471 inline void unserialize<libnest2d::Formats::SVG>(
515472 PolygonImpl& sh,
516473 const std::string& str)
517474 {
518475 }
519476 #endif
520477
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)
523479 {
524480 std::string message;
525481 bool ret = boost::geometry::is_valid(sh, message);
526482
527483 return {ret, message};
528484 }
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
529512
530513 }
531514
9898 using Type = PointImpl;
9999 };
100100
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;
109103 };
110104
111105 template<> struct CountourType<PolygonImpl> {
112106 using Type = PathImpl;
113107 };
114108
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)
117127 {
118128 return p.X;
119129 }
120130
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)
123133 {
124134 return p.Y;
125135 }
126136
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)
129139 {
130140 return p.X;
131141 }
132142
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)
136145 {
137146 return p.Y;
138147 }
139148
140 template<>
141 inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity)
142 {
143 return sh.Contour.reserve(vertex_capacity);
144149 }
145150
146151 #define DISABLE_BOOST_AREA
174179
175180 return ClipperLib::Area(sh.Contour) + a;
176181 }
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 {
182195 return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
183196 }
184197
185 template<>
186 inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
198 template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
199 {
187200 #define DISABLE_BOOST_OFFSET
188201
189202 using ClipperLib::ClipperOffset;
233246 }
234247
235248 // 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 {
237251 std::stringstream ss;
238252
239253 ss << "Contour {\n";
256270 }
257271
258272 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 {
290275 PolygonImpl p;
291276 p.Contour = path;
292277
307292 return p;
308293 }
309294
310 template<> inline PolygonImpl ShapeLike::create( PathImpl&& path,
311 HoleStore&& holes) {
295 template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
312296 PolygonImpl p;
313297 p.Contour.swap(path);
314298
330314 return p;
331315 }
332316
333 template<> inline const THolesContainer<PolygonImpl>&
334 ShapeLike::holes(const PolygonImpl& sh)
317 template<>
318 inline const THolesContainer<PolygonImpl>& holes(const PolygonImpl& sh)
335319 {
336320 return sh.Holes;
337321 }
338322
339 template<> inline THolesContainer<PolygonImpl>&
340 ShapeLike::holes(PolygonImpl& sh)
323 template<> inline THolesContainer<PolygonImpl>& holes(PolygonImpl& sh)
341324 {
342325 return sh.Holes;
343326 }
344327
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)
347330 {
348331 return sh.Holes[idx];
349332 }
350333
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)
353337 {
354338 return sh.Holes[idx];
355339 }
356340
357 template<> inline size_t ShapeLike::holeCount(const PolygonImpl& sh)
341 template<> inline size_t holeCount(const PolygonImpl& sh)
358342 {
359343 return sh.Holes.size();
360344 }
361345
362 template<> inline PathImpl& ShapeLike::getContour(PolygonImpl& sh)
346 template<> inline PathImpl& getContour(PolygonImpl& sh)
363347 {
364348 return sh.Contour;
365349 }
366350
367351 template<>
368 inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh)
352 inline const PathImpl& getContour(const PolygonImpl& sh)
369353 {
370354 return sh.Contour;
371355 }
372356
373357 #define DISABLE_BOOST_TRANSLATE
374358 template<>
375 inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
359 inline void translate(PolygonImpl& sh, const PointImpl& offs)
376360 {
377361 for(auto& p : sh.Contour) { p += offs; }
378362 for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; }
380364
381365 #define DISABLE_BOOST_ROTATE
382366 template<>
383 inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
367 inline void rotate(PolygonImpl& sh, const Radians& rads)
384368 {
385369 using Coord = TCoord<PointImpl>;
386370
401385 }
402386 }
403387
388 } // namespace shapelike
389
404390 #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;
407393
408394 ClipperLib::PolyTree result;
409395 clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
437423 return retv;
438424 }
439425
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)
442430 {
443431 ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
444432
460448
461449 }
462450
451 }
452
463453 //#define DISABLE_BOOST_SERIALIZE
464454 //#define DISABLE_BOOST_UNSERIALIZE
465455
2121 using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
2222
2323 /// 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; };
2525
2626 /// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
2727 template<class Shape>
2828 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;
5129
5230 /**
5331 * \brief A point pair base class for other point pairs (segment, box, ...).
5937 RawPoint p2;
6038 };
6139
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
6251 /**
6352 * \brief An abstraction of a box;
6453 */
6857 using PointPair<RawPoint>::p2;
6958 public:
7059
60 using Tag = BoxTag;
61 using PointType = RawPoint;
62
7163 inline _Box() = default;
7264 inline _Box(const RawPoint& p, const RawPoint& pp):
7365 PointPair<RawPoint>({p, pp}) {}
9789 double radius_ = 0;
9890 public:
9991
92 using Tag = CircleTag;
93 using PointType = RawPoint;
94
10095 _Circle() = default;
10196
10297 _Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
108103 inline void radius(double r) { radius_ = r; }
109104
110105 inline double area() const BP2D_NOEXCEPT {
111 return 2.0*Pi*radius_;
106 return 2.0*Pi*radius_*radius_;
112107 }
113108 };
114109
122117 mutable Radians angletox_ = std::nan("");
123118 public:
124119
120 using PointType = RawPoint;
121
125122 inline _Segment() = default;
126123
127124 inline _Segment(const RawPoint& p, const RawPoint& pp):
155152 inline double length();
156153 };
157154
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
159156 // used in friend declarations.
160 struct PointLike {
157 namespace pointlike {
161158
162159 template<class RawPoint>
163 static TCoord<RawPoint> x(const RawPoint& p)
160 inline TCoord<RawPoint> x(const RawPoint& p)
164161 {
165162 return p.x();
166163 }
167164
168165 template<class RawPoint>
169 static TCoord<RawPoint> y(const RawPoint& p)
166 inline TCoord<RawPoint> y(const RawPoint& p)
170167 {
171168 return p.y();
172169 }
173170
174171 template<class RawPoint>
175 static TCoord<RawPoint>& x(RawPoint& p)
172 inline TCoord<RawPoint>& x(RawPoint& p)
176173 {
177174 return p.x();
178175 }
179176
180177 template<class RawPoint>
181 static TCoord<RawPoint>& y(RawPoint& p)
178 inline TCoord<RawPoint>& y(RawPoint& p)
182179 {
183180 return p.y();
184181 }
185182
186183 template<class RawPoint>
187 static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
184 inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
188185 {
189186 static_assert(always_false<RawPoint>::value,
190187 "PointLike::distance(point, point) unimplemented!");
192189 }
193190
194191 template<class RawPoint>
195 static double distance(const RawPoint& /*p1*/,
192 inline double distance(const RawPoint& /*p1*/,
196193 const _Segment<RawPoint>& /*s*/)
197194 {
198195 static_assert(always_false<RawPoint>::value,
201198 }
202199
203200 template<class RawPoint>
204 static std::pair<TCoord<RawPoint>, bool> horizontalDistance(
201 inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
205202 const RawPoint& p, const _Segment<RawPoint>& s)
206203 {
207204 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());
211208
212209 TCoord<RawPoint> ret;
213210
227224 }
228225
229226 template<class RawPoint>
230 static std::pair<TCoord<RawPoint>, bool> verticalDistance(
227 inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
231228 const RawPoint& p, const _Segment<RawPoint>& s)
232229 {
233230 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());
237234
238235 TCoord<RawPoint> ret;
239236
251248
252249 return {ret, true};
253250 }
254 };
251 }
255252
256253 template<class RawPoint>
257254 TCoord<RawPoint> _Box<RawPoint>::width() const BP2D_NOEXCEPT
258255 {
259 return PointLike::x(maxCorner()) - PointLike::x(minCorner());
256 return pointlike::x(maxCorner()) - pointlike::x(minCorner());
260257 }
261258
262259 template<class RawPoint>
263260 TCoord<RawPoint> _Box<RawPoint>::height() const BP2D_NOEXCEPT
264261 {
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); }
273270
274271 template<class RawPoint>
275272 void setX(RawPoint& p, const TCoord<RawPoint>& val)
276273 {
277 PointLike::x<RawPoint>(p) = val;
274 pointlike::x<RawPoint>(p) = val;
278275 }
279276
280277 template<class RawPoint>
281278 void setY(RawPoint& p, const TCoord<RawPoint>& val)
282279 {
283 PointLike::y<RawPoint>(p) = val;
280 pointlike::y<RawPoint>(p) = val;
284281 }
285282
286283 template<class RawPoint>
302299 template<class RawPoint>
303300 inline double _Segment<RawPoint>::length()
304301 {
305 return PointLike::distance(first(), second());
302 return pointlike::distance(first(), second());
306303 }
307304
308305 template<class RawPoint>
312309
313310 using Coord = TCoord<RawPoint>;
314311
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 )
318315 };
319316
320317 return ret;
355352
356353 // This struct serves as a namespace. The only difference is that it can be
357354 // 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,
365362 const THolesContainer<RawShape>& holes)
366363 {
367364 return RawShape(contour, holes);
368365 }
369366
370367 template<class RawShape>
371 static RawShape create(TContour<RawShape>&& contour,
368 inline RawShape create(TContour<RawShape>&& contour,
372369 THolesContainer<RawShape>&& holes)
373370 {
374371 return RawShape(contour, holes);
375372 }
376373
377374 template<class RawShape>
378 static RawShape create(const TContour<RawShape>& contour)
375 inline RawShape create(const TContour<RawShape>& contour)
379376 {
380377 return create<RawShape>(contour, {});
381378 }
382379
383380 template<class RawShape>
384 static RawShape create(TContour<RawShape>&& contour)
381 inline RawShape create(TContour<RawShape>&& contour)
385382 {
386383 return create<RawShape>(contour, {});
387384 }
388385
389386 template<class RawShape>
390 static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
387 inline THolesContainer<RawShape>& holes(RawShape& /*sh*/)
391388 {
392389 static THolesContainer<RawShape> empty;
393390 return empty;
394391 }
395392
396393 template<class RawShape>
397 static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
394 inline const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
398395 {
399396 static THolesContainer<RawShape> empty;
400397 return empty;
401398 }
402399
403400 template<class RawShape>
404 static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
401 inline TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
405402 {
406403 return holes(sh)[idx];
407404 }
408405
409406 template<class RawShape>
410 static const TContour<RawShape>& getHole(const RawShape& sh,
407 inline const TContour<RawShape>& getHole(const RawShape& sh,
411408 unsigned long idx)
412409 {
413410 return holes(sh)[idx];
414411 }
415412
416413 template<class RawShape>
417 static size_t holeCount(const RawShape& sh)
414 inline size_t holeCount(const RawShape& sh)
418415 {
419416 return holes(sh).size();
420417 }
421418
422419 template<class RawShape>
423 static TContour<RawShape>& getContour(RawShape& sh)
420 inline TContour<RawShape>& getContour(RawShape& sh)
424421 {
425422 return sh;
426423 }
427424
428425 template<class RawShape>
429 static const TContour<RawShape>& getContour(const RawShape& sh)
426 inline const TContour<RawShape>& getContour(const RawShape& sh)
430427 {
431428 return sh;
432429 }
433430
434431 // Optional, does nothing by default
435432 template<class RawShape>
436 static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
433 inline void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
437434
438435 template<class RawShape, class...Args>
439 static void addVertex(RawShape& sh, Args...args)
436 inline void addVertex(RawShape& sh, Args...args)
440437 {
441438 return getContour(sh).emplace_back(std::forward<Args>(args)...);
442439 }
443440
444441 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*/)
470468 {
471469 return "";
472470 }
473471
474472 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)
476474 {
477475 static_assert(always_false<RawShape>::value,
478476 "ShapeLike::serialize() unimplemented!");
480478 }
481479
482480 template<Formats, class RawShape>
483 static void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
481 inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
484482 {
485483 static_assert(always_false<RawShape>::value,
486484 "ShapeLike::unserialize() unimplemented!");
487485 }
488486
489487 template<class RawShape>
490 static double area(const RawShape& /*sh*/)
488 inline double area(const RawShape& /*sh*/, const PolygonTag&)
491489 {
492490 static_assert(always_false<RawShape>::value,
493491 "ShapeLike::area() unimplemented!");
495493 }
496494
497495 template<class RawShape>
498 static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
496 inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
499497 {
500498 static_assert(always_false<RawShape>::value,
501499 "ShapeLike::intersects() unimplemented!");
503501 }
504502
505503 template<class RawShape>
506 static bool isInside(const TPoint<RawShape>& /*point*/,
504 inline bool isInside(const TPoint<RawShape>& /*point*/,
507505 const RawShape& /*shape*/)
508506 {
509507 static_assert(always_false<RawShape>::value,
512510 }
513511
514512 template<class RawShape>
515 static bool isInside(const RawShape& /*shape*/,
513 inline bool isInside(const RawShape& /*shape*/,
516514 const RawShape& /*shape*/)
517515 {
518516 static_assert(always_false<RawShape>::value,
521519 }
522520
523521 template<class RawShape>
524 static bool touches( const RawShape& /*shape*/,
522 inline bool touches( const RawShape& /*shape*/,
525523 const RawShape& /*shape*/)
526524 {
527525 static_assert(always_false<RawShape>::value,
530528 }
531529
532530 template<class RawShape>
533 static bool touches( const TPoint<RawShape>& /*point*/,
531 inline bool touches( const TPoint<RawShape>& /*point*/,
534532 const RawShape& /*shape*/)
535533 {
536534 static_assert(always_false<RawShape>::value,
539537 }
540538
541539 template<class RawShape>
542 static _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/)
540 inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/,
541 const PolygonTag&)
543542 {
544543 static_assert(always_false<RawShape>::value,
545544 "ShapeLike::boundingBox(shape) unimplemented!");
546545 }
547546
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,
552552 "ShapeLike::boundingBox(shapes) unimplemented!");
553553 }
554554
555555 template<class RawShape>
556 static RawShape convexHull(const RawShape& /*sh*/)
556 inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&)
557557 {
558558 static_assert(always_false<RawShape>::value,
559559 "ShapeLike::convexHull(shape) unimplemented!");
560560 return RawShape();
561561 }
562562
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,
567568 "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*/)
573574 {
574575 static_assert(always_false<RawShape>::value,
575576 "ShapeLike::rotate() unimplemented!");
576577 }
577578
578579 template<class RawShape, class RawPoint>
579 static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
580 inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
580581 {
581582 static_assert(always_false<RawShape>::value,
582583 "ShapeLike::translate() unimplemented!");
583584 }
584585
585586 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*/)
594594 {
595595 return {false, "ShapeLike::isValid() unimplemented!"};
596596 }
597597
598598 template<class RawShape>
599 static inline bool isConvex(const TContour<RawShape>& sh)
599 inline bool isConvex(const TContour<RawShape>& sh)
600600 {
601601 using Vertex = TPoint<RawShape>;
602602 auto first = sh.begin();
632632 // No need to implement these
633633 // *************************************************************************
634634
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& )
638637 {
639638 return box;
640639 }
641640
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 = {
648648 static_cast<Coord>(getX(circ.center()) - circ.radius()),
649649 static_cast<Coord>(getY(circ.center()) - circ.radius()) };
650650
651 TPoint<RawShape> pmax = {
651 Point pmax = {
652652 static_cast<Coord>(getX(circ.center()) + circ.radius()),
653653 static_cast<Coord>(getY(circ.center()) + circ.radius()) };
654654
655655 return {pmin, pmax};
656656 }
657657
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& )
666672 {
667673 return circ.area();
668674 }
669675
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)
672684 {
673685 return std::accumulate(shapes.begin(), shapes.end(), 0.0,
674686 [](double a, const RawShape& b) {
677689 }
678690
679691 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,
681700 const _Circle<TPoint<RawShape>>& circ)
682701 {
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,
688707 const _Box<TPoint<RawShape>>& box)
689708 {
690709 auto px = getX(point);
698717 }
699718
700719 template<class RawShape>
701 static bool isInside(const RawShape& sh,
720 inline bool isInside(const RawShape& sh,
702721 const _Circle<TPoint<RawShape>>& circ)
703722 {
704723 return std::all_of(cbegin(sh), cend(sh),
708727 }
709728
710729 template<class RawShape>
711 static bool isInside(const _Box<TPoint<RawShape>>& box,
730 inline bool isInside(const _Box<TPoint<RawShape>>& box,
712731 const _Circle<TPoint<RawShape>>& circ)
713732 {
714733 return isInside<RawShape>(box.minCorner(), circ) &&
716735 }
717736
718737 template<class RawShape>
719 static bool isInside(const _Box<TPoint<RawShape>>& ibb,
738 inline bool isInside(const _Box<TPoint<RawShape>>& ibb,
720739 const _Box<TPoint<RawShape>>& box)
721740 {
722741 auto iminX = getX(ibb.minCorner());
733752 }
734753
735754 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)
737756 {
738757 return *(begin(sh) + idx);
739758 }
740759
741760 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,
743762 unsigned long idx)
744763 {
745764 return *(cbegin(sh) + idx);
746765 }
747766
748767 template<class RawShape>
749 static inline size_t contourVertexCount(const RawShape& sh)
768 inline size_t contourVertexCount(const RawShape& sh)
750769 {
751770 return cend(sh) - cbegin(sh);
752771 }
753772
754773 template<class RawShape, class Fn>
755 static inline void foreachContourVertex(RawShape& sh, Fn fn) {
774 inline void foreachContourVertex(RawShape& sh, Fn fn) {
756775 for(auto it = begin(sh); it != end(sh); ++it) fn(*it);
757776 }
758777
759778 template<class RawShape, class Fn>
760 static inline void foreachHoleVertex(RawShape& sh, Fn fn) {
779 inline void foreachHoleVertex(RawShape& sh, Fn fn) {
761780 for(int i = 0; i < holeCount(sh); ++i) {
762781 auto& h = getHole(sh, i);
763782 for(auto it = begin(h); it != end(h); ++it) fn(*it);
765784 }
766785
767786 template<class RawShape, class Fn>
768 static inline void foreachContourVertex(const RawShape& sh, Fn fn) {
787 inline void foreachContourVertex(const RawShape& sh, Fn fn) {
769788 for(auto it = cbegin(sh); it != cend(sh); ++it) fn(*it);
770789 }
771790
772791 template<class RawShape, class Fn>
773 static inline void foreachHoleVertex(const RawShape& sh, Fn fn) {
792 inline void foreachHoleVertex(const RawShape& sh, Fn fn) {
774793 for(int i = 0; i < holeCount(sh); ++i) {
775794 auto& h = getHole(sh, i);
776795 for(auto it = cbegin(h); it != cend(h); ++it) fn(*it);
778797 }
779798
780799 template<class RawShape, class Fn>
781 static inline void foreachVertex(RawShape& sh, Fn fn) {
800 inline void foreachVertex(RawShape& sh, Fn fn) {
782801 foreachContourVertex(sh, fn);
783802 foreachHoleVertex(sh, fn);
784803 }
785804
786805 template<class RawShape, class Fn>
787 static inline void foreachVertex(const RawShape& sh, Fn fn) {
806 inline void foreachVertex(const RawShape& sh, Fn fn) {
788807 foreachContourVertex(sh, fn);
789808 foreachHoleVertex(sh, fn);
790809 }
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>
793821
794822 }
795823
77 #include <iterator>
88
99 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;
1031
1132 /// The complexity level of a polygon that an NFP implementation can handle.
1233 enum class NfpLevel: unsigned {
1738 BOTH_CONCAVE_WITH_HOLES
1839 };
1940
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
2248
2349 // Shorthand for a pile of polygons
2450 template<class RawShape>
25 using Shapes = typename ShapeLike::Shapes<RawShape>;
51 using Shapes = TMultiShape<RawShape>;
2652
2753 /**
2854 * Merge a bunch of polygons with the specified additional polygon.
3561 * mostly it will be a set containing only one big polygon but if the input
3662 * polygons are disjuct than the resulting set will contain more polygons.
3763 */
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,
4268 "Nfp::merge(shapes, shape) unimplemented!");
4369 }
4470
5480 * polygons are disjuct than the resulting set will contain more polygons.
5581 */
5682 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);
6187 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);
7689 }
7790
7891 /**
8194 * the result will be the leftmost (with the highest X coordinate).
8295 */
8396 template<class RawShape>
84 static TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
97 inline TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
8598 {
8699
87100 // 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;;
92105 }
93106
94107 /**
97110 * the result will be the rightmost (with the lowest X coordinate).
98111 */
99112 template<class RawShape>
100 static TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
113 TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
101114 {
102115
103116 // 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);
120134 }
121135
122136 /**
138152 *
139153 */
140154 template<class RawShape>
141 static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
155 inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
142156 const RawShape& other)
143157 {
144158 using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>;
145 using sl = ShapeLike;
159 namespace sl = shapelike;
146160
147161 RawShape rsh; // Final nfp placeholder
148162 Vertex top_nfp;
186200 sl::addVertex(rsh, edgelist.front().second());
187201
188202 // Sorting function for the nfp reference vertex search
189 auto& cmp = _vsort<RawShape>;
203 auto& cmp = __nfp::_vsort<RawShape>;
190204
191205 // the reference (rightmost top) vertex so far
192206 top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp );
213227 }
214228
215229 template<class RawShape>
216 static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
230 NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
217231 const RawShape& cother)
218232 {
219233
232246 using Vertex = TPoint<RawShape>;
233247 using Coord = TCoord<Vertex>;
234248 using Edge = _Segment<Vertex>;
235 using sl = ShapeLike;
249 namespace sl = shapelike;
236250 using std::signbit;
237251 using std::sort;
238252 using std::vector;
527541 }
528542 };
529543
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 }
551554
552555 }
553556
88 #include <functional>
99
1010 #include "geometry_traits.hpp"
11 #include "optimizer.hpp"
1211
1312 namespace libnest2d {
13
14 namespace sl = shapelike;
15 namespace pl = pointlike;
1416
1517 /**
1618 * \brief An item to be placed on a bin.
2729 using Coord = TCoord<TPoint<RawShape>>;
2830 using Vertex = TPoint<RawShape>;
2931 using Box = _Box<Vertex>;
30 using sl = ShapeLike;
32
33 using VertexConstIterator = typename TContour<RawShape>::const_iterator;
3134
3235 // The original shape that gets encapsulated.
3336 RawShape sh_;
3740 Radians rotation_;
3841 Coord offset_distance_;
3942
40 // Info about whether the tranformations will have to take place
43 // Info about whether the transformations will have to take place
4144 // This is needed because if floating point is used, it is hard to say
4245 // that a zero angle is not a rotation because of testing for equality.
4346 bool has_rotation_ = false, has_translation_ = false, has_offset_ = false;
5760 };
5861
5962 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
6265 mutable bool rmt_valid_ = false, lmb_valid_ = false;
6366 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) {}
6669 } bb_cache_;
6770
6871 public:
7982 * supports. Giving out a non const iterator would make it impossible to
8083 * perform correct cache invalidation.
8184 */
82 using Iterator = TVertexConstIterator<RawShape>;
85 using Iterator = VertexConstIterator;
8386
8487 /**
8588 * @brief Get the orientation of the polygon.
108111 explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {}
109112
110113 /**
111 * @brief Create an item from an initilizer list.
114 * @brief Create an item from an initializer list.
112115 * @param il The initializer list of vertices.
113116 */
114117 inline _Item(const std::initializer_list< Vertex >& il):
158161 }
159162
160163 /**
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.
162165 *
163166 * Note that the vertex considered here is taken from the original shape
164167 * that this item is constructed from. This means that no transformation is
243246 * @param p
244247 * @return
245248 */
246 inline bool isPointInside(const Vertex& p) const
249 inline bool isInside(const Vertex& p) const
247250 {
248251 return sl::isInside(p, transformedShape());
249252 }
306309 {
307310 if(translation_ != tr) {
308311 translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
309 bb_cache_.valid = false;
312 //bb_cache_.valid = false;
310313 }
311314 }
312315
341344
342345 inline Box boundingBox() const {
343346 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 }
346355 bb_cache_.valid = true;
347356 }
348357
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 };
351360 }
352361
353362 inline Vertex referenceVertex() const {
437446 inline _Rectangle(Unit width, Unit height,
438447 // disable this ctor if o != CLOCKWISE
439448 enable_if_t< o == TO::CLOCKWISE, int> = 0 ):
440 _Item<RawShape>( ShapeLike::create<RawShape>( {
449 _Item<RawShape>( sl::create<RawShape>( {
441450 {0, 0},
442451 {0, height},
443452 {width, height},
451460 inline _Rectangle(Unit width, Unit height,
452461 // disable this ctor if o != COUNTER_CLOCKWISE
453462 enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ):
454 _Item<RawShape>( ShapeLike::create<RawShape>( {
463 _Item<RawShape>( sl::create<RawShape>( {
455464 {0, 0},
456465 {width, 0},
457466 {width, height},
472481
473482 template<class RawShape>
474483 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);
476485 }
477486
478487 template<class RawShape> inline bool
479488 _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()};
481510 }
482511
483512 /**
484513 * \brief A wrapper interface (trait) class for any placement strategy provider.
485514 *
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
487516 * specialize this class template and define all the ten methods it has. It can
488517 * use the strategies::PlacerBoilerplace class for creating a new placement
489518 * strategy where only the constructor and the trypack method has to be provided
514543 */
515544 using PackResult = typename PlacementStrategy::PackResult;
516545
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;
519549
520550 /**
521551 * @brief Constructor taking the bin and an optional configuration.
535565 * Note that it depends on the particular placer implementation how it
536566 * reacts to config changes in the middle of a calculation.
537567 *
538 * @param config The configuration object defined by the placement startegy.
568 * @param config The configuration object defined by the placement strategy.
539569 */
540570 inline void configure(const Config& config) { impl_.configure(config); }
541571
542572 /**
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).
561594 *
562595 * If the pack result is a failure the method should ignore it.
563596 * @param r The result of a previous trypack call.
565598 inline void accept(PackResult& r) { impl_.accept(r); }
566599
567600 /**
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.
569602 *
570603 * 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
572605 * 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
574607 * in the implementation.
575608 *
576609 * @param item The item to pack.
577610 * @return Returns true if the item was packed or false if it could not be
578611 * packed.
579612 */
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 }
581620
582621 /// Unpack the last element (remove it from the list of packed items).
583622 inline void unpackLast() { impl_.unpackLast(); }
595634 inline void clearItems() { impl_.clearItems(); }
596635
597636 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
605637
606638 };
607639
627659 * Note that it depends on the particular placer implementation how it
628660 * reacts to config changes in the middle of a calculation.
629661 *
630 * @param config The configuration object defined by the selection startegy.
662 * @param config The configuration object defined by the selection strategy.
631663 */
632664 inline void configure(const Config& config) {
633665 impl_.configure(config);
634666 }
635667
636668 /**
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.
639671 * @param fn A function callback object taking one unsigned integer as the
640672 * number of the remaining items to pack.
641673 */
648680 * placer compatible with the PlacementStrategyLike interface.
649681 *
650682 * \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.
652684 * \param bin. The shape of the bin. It has to be supported by the placement
653685 * strategy.
654686 * \param An optional config object for the placer.
680712 /**
681713 * @brief Get the items for a particular bin.
682714 * @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.
684716 */
685717 inline ItemGroup itemsForBin(size_t binIndex) {
686718 return impl_.itemsForBin(binIndex);
722754 >;
723755
724756 /**
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
726758 * input items and outputs the items with the proper transformations to be
727759 * inside the provided bin.
728760 */
729761 template<class PlacementStrategy, class SelectionStrategy >
730 class Arranger {
762 class Nester {
731763 using TSel = SelectionStrategyLike<SelectionStrategy>;
732764 TSel selector_;
733 bool use_min_bb_rotation_ = false;
734765 public:
735766 using Item = typename PlacementStrategy::Item;
736767 using ItemRef = std::reference_wrapper<Item>;
768799 template<class TBinType = BinType,
769800 class PConf = PlacementConfig,
770801 class SConf = SelectionConfig>
771 Arranger( TBinType&& bin,
802 Nester( TBinType&& bin,
772803 Unit min_obj_distance = 0,
773804 PConf&& pconfig = PConf(),
774805 SConf&& sconfig = SConf()):
801832 * the selection algorithm.
802833 */
803834 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);
807838 }
808839
809840 /**
814845 * input sequence size.
815846 */
816847 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);
820851 }
821852
822853 /// Shorthand to normal arrange method.
823854 template<class TIterator>
824855 inline PackGroup operator() (TIterator from, TIterator to)
825856 {
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)
831862 {
832863 selector_.progressIndicator(func); return *this;
833864 }
841872 return ret;
842873 }
843874
844 inline Arranger& useMinimumBoundigBoxRotation(bool s = true) {
845 use_min_bb_rotation_ = s; return *this;
846 }
847
848875 private:
849876
850877 template<class TIterator,
851878 class IT = remove_cvref_t<typename TIterator::value_type>,
852879
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.
855882 // This way we can use references to input elements as they will
856883 // have to exist for the lifetime of this call.
857884 class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
858885 >
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);
862889 return lastResult();
863890 }
864891
866893 class IT = remove_cvref_t<typename TIterator::value_type>,
867894 class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
868895 >
869 inline PackGroup _arrange(TIterator from, TIterator to, int = false)
896 inline PackGroup _execute(TIterator from, TIterator to, int = false)
870897 {
871898 item_cache_ = {from, to};
872899
873 __arrange(item_cache_.begin(), item_cache_.end());
900 __execute(item_cache_.begin(), item_cache_.end());
874901 return lastResult();
875902 }
876903
877904 template<class TIterator,
878905 class IT = remove_cvref_t<typename TIterator::value_type>,
879906
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.
882909 // This way we can use references to input elements as they will
883910 // have to exist for the lifetime of this call.
884911 class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
885912 >
886 inline IndexedPackGroup _arrangeIndexed(TIterator from,
913 inline IndexedPackGroup _executeIndexed(TIterator from,
887914 TIterator to,
888915 bool = false)
889916 {
890 __arrange(from, to);
917 __execute(from, to);
891918 return createIndexedPackGroup(from, to, selector_);
892919 }
893920
895922 class IT = remove_cvref_t<typename TIterator::value_type>,
896923 class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
897924 >
898 inline IndexedPackGroup _arrangeIndexed(TIterator from,
925 inline IndexedPackGroup _executeIndexed(TIterator from,
899926 TIterator to,
900927 int = false)
901928 {
902929 item_cache_ = {from, to};
903 __arrange(item_cache_.begin(), item_cache_.end());
930 __execute(item_cache_.begin(), item_cache_.end());
904931 return createIndexedPackGroup(from, to, selector_);
905932 }
906933
932959 return pg;
933960 }
934961
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)
955963 {
956964 if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) {
957965 item.addOffset(static_cast<Unit>(std::ceil(min_obj_distance_/2.0)));
958966 });
959967
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
966968 selector_.template packItems<PlacementStrategy>(
967969 from, to, bin_, pconfig_);
968970
6666 // need to wrap that in a type (see metaloop::Int).
6767
6868 /*
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
7070 * because a non type template parameter (such as int) would be prohibited in
7171 * a partial specialization. Also for the same reason we have to use a class
7272 * _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.
7474 */
7575 template<int N> using Int = std::integral_constant<int, N>;
7676
8787 // It takes the real functor that can be specified in-place but only
8888 // with C++14 because the second parameter's type will depend on the
8989 // 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.
9191 inline MapFn(Fn&& fn): fn_(forward<Fn>(fn)) {}
9292
9393 template<class T> void operator ()(T&& pack_element) {
145145 * version of run is called which does not call itself anymore.
146146 *
147147 * If you are utterly annoyed, at least you have learned a super crazy
148 * functional metaprogramming pattern.
148 * functional meta-programming pattern.
149149 */
150150 template<class...Args>
151151 using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>;
100100
101101 /// If the relative value difference between two scores.
102102 double relative_score_difference = std::nan("");
103
104 /// Stop if this value or better is found.
105 double stop_score = std::nan("");
103106
104107 unsigned max_iterations = 0;
105108 };
141141 default: ;
142142 }
143143
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;
146147 if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff);
147148 if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff);
149 if(!std::isnan(stopval)) opt_.set_stopval(stopval);
148150
149151 if(this->stopcr_.max_iterations > 0)
150152 opt_.set_maxeval(this->stopcr_.max_iterations );
44
55 #include "placer_boilerplate.hpp"
66
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 };
820
921 template<class RawShape>
1022 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;
1227 bool allow_rotations = false;
1328 };
1429
2641
2742 explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {}
2843
29 PackResult trypack(Item& item) {
44 template<class Range = ConstItemRange<typename Base::DefaultIter>>
45 PackResult trypack(Item& item,
46 const Range& = Range())
47 {
3048 auto r = _trypack(item);
3149 if(!r && Base::config_.allow_rotations) {
50
3251 item.rotate(Degrees(90));
3352 r =_trypack(item);
3453 }
6483 setInitialPosition(item);
6584
6685 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;
6888 bool can_be_packed = can_move;
6989 bool left = true;
7090
7191 while(can_move) {
7292 if(left) { // write previous down move and go down
73 item.translate({0, -d+1});
93 item.translate({0, -d+eps});
7494 d = availableSpaceLeft(item);
75 can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
95 can_move = d > eps;
7696 left = false;
7797 } else { // write previous left move and go down
78 item.translate({-d+1, 0});
98 item.translate({-d+eps, 0});
7999 d = availableSpaceDown(item);
80 can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
100 can_move = d > eps;
81101 left = true;
82102 }
83103 }
111131 const RawShape& scanpoly)
112132 {
113133 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()) );
118138 }
119139
120140 template<class C = Coord>
125145 {
126146 auto tsh = other.transformedShape();
127147
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());
132152
133153 return ( inters_scanpoly ||
134 ShapeLike::isInside(tsh, scanpoly)) &&
154 sl::isInside(tsh, scanpoly)) &&
135155 ( !inters_item &&
136 !ShapeLike::isInside(tsh, item.rawShape())
156 !sl::isInside(tsh, item.rawShape())
137157 );
138158 }
139159
140 Container itemsInTheWayOf(const Item& item, const Dir dir) {
160 ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) {
141161 // Get the left or down polygon, that has the same area as the shadow
142162 // of input item reflected to the left or downwards
143163 auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) :
144164 downPoly(item);
145165
146 Container ret; // packed items 'in the way' of item
166 ItemGroup ret; // packed items 'in the way' of item
147167 ret.reserve(items_.size());
148168
149169 // Predicate to find items that are 'in the way' for left (down) move
172192
173193 if(dir == Dir::LEFT) {
174194 getCoord = [](const Vertex& v) { return getX(v); };
175 availableDistance = PointLike::horizontalDistance<Vertex>;
195 availableDistance = pointlike::horizontalDistance<Vertex>;
176196 availableDistanceSV = [](const Segment& s, const Vertex& v) {
177 auto ret = PointLike::horizontalDistance<Vertex>(v, s);
197 auto ret = pointlike::horizontalDistance<Vertex>(v, s);
178198 if(ret.second) ret.first = -ret.first;
179199 return ret;
180200 };
181201 }
182202 else {
183203 getCoord = [](const Vertex& v) { return getY(v); };
184 availableDistance = PointLike::verticalDistance<Vertex>;
204 availableDistance = pointlike::verticalDistance<Vertex>;
185205 availableDistanceSV = [](const Segment& s, const Vertex& v) {
186 auto ret = PointLike::verticalDistance<Vertex>(v, s);
206 auto ret = pointlike::verticalDistance<Vertex>(v, s);
187207 if(ret.second) ret.first = -ret.first;
188208 return ret;
189209 };
213233 assert(pleft.vertexCount() > 0);
214234
215235 auto trpleft = pleft.transformedShape();
216 auto first = ShapeLike::begin(trpleft);
236 auto first = sl::begin(trpleft);
217237 auto next = first + 1;
218 auto endit = ShapeLike::end(trpleft);
238 auto endit = sl::end(trpleft);
219239
220240 while(next != endit) {
221241 Segment seg(*(first++), *(next++));
339359
340360 // reserve for all vertices plus 2 for the left horizontal wall, 2 for
341361 // the additional vertices for maintaning min object distance
342 ShapeLike::reserve(rsh, finish-start+4);
362 sl::reserve(rsh, finish-start+4);
343363
344364 /*auto addOthers = [&rsh, finish, start, &item](){
345365 for(size_t i = start+1; i < finish; i++)
346 ShapeLike::addVertex(rsh, item.vertex(i));
366 sl::addVertex(rsh, item.vertex(i));
347367 };*/
348368
349369 auto reverseAddOthers = [&rsh, finish, start, &item](){
350370 for(auto i = finish-1; i > start; i--)
351 ShapeLike::addVertex(rsh, item.vertex(
371 sl::addVertex(rsh, item.vertex(
352372 static_cast<unsigned long>(i)));
353373 };
354374
360380
361381 // Clockwise polygon construction
362382
363 ShapeLike::addVertex(rsh, topleft_vertex);
383 sl::addVertex(rsh, topleft_vertex);
364384
365385 if(dir == Dir::LEFT) reverseAddOthers();
366386 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);
372392
373393 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));
376396 }
377397 else reverseAddOthers();
378398
379399
380400 // Close the polygon
381 ShapeLike::addVertex(rsh, topleft_vertex);
401 sl::addVertex(rsh, topleft_vertex);
382402
383403 return rsh;
384404 }
00 #ifndef NOFITPOLY_HPP
11 #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>
213
314 #ifndef NDEBUG
415 #include <iostream>
617 #include "placer_boilerplate.hpp"
718 #include "../geometry_traits_nfp.hpp"
819 #include "libnest2d/optimizer.hpp"
9 #include <cassert>
1020
1121 #include "tools/svgtools.hpp"
1222
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 {
14139
15140 template<class RawShape>
16141 struct NfpPConfig {
142
143 using ItemGroup = _ItemGroup<_Item<RawShape>>;
17144
18145 enum class Alignment {
19146 CENTER,
44171 * that will optimize for the best pack efficiency. With a custom fitting
45172 * function you can e.g. influence the shape of the arranged pile.
46173 *
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.
76177 *
77178 */
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;
81180
82181 /**
83182 * @brief The quality of search for an optimal placement.
84183 * This is a compromise slider between quality and speed. Zero is the
85184 * fast and poor solution while 1.0 is the slowest but most accurate.
86185 */
87 float accuracy = 1.0;
186 float accuracy = 0.65f;
88187
89188 /**
90189 * @brief If you want to see items inside other item's holes, you have to
94193 * The library has no such implementation right now.
95194 */
96195 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;
97229
98230 NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}),
99231 alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {}
128260
129261 void createCache(const RawShape& sh) {
130262 { // For the contour
131 auto first = ShapeLike::cbegin(sh);
263 auto first = shapelike::cbegin(sh);
132264 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));
136268
137269 while(next != endit) {
138270 contour_.emap.emplace_back(*(first++), *(next++));
141273 }
142274 }
143275
144 for(auto& h : ShapeLike::holes(sh)) { // For the holes
276 for(auto& h : shapelike::holes(sh)) { // For the holes
145277 auto first = h.begin();
146278 auto next = std::next(first);
147279 auto endit = h.end();
160292 }
161293
162294 size_t stride(const size_t N) const {
163 using std::ceil;
164295 using std::round;
165296 using std::pow;
166297
167298 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)))
169300 );
170301 }
171302
176307 const auto S = stride(N);
177308
178309 contour_.corners.reserve(N / S + 1);
310 contour_.corners.emplace_back(0.0);
179311 auto N_1 = N-1;
180312 contour_.corners.emplace_back(0.0);
181313 for(size_t i = 0; i < N_1; i += S) {
189321 if(!hc.corners.empty()) return;
190322
191323 const auto N = hc.distances.size();
324 auto N_1 = N-1;
192325 const auto S = stride(N);
193 auto N_1 = N-1;
194326 hc.corners.reserve(N / S + 1);
195327 hc.corners.emplace_back(0.0);
196328 for(size_t i = 0; i < N_1; i += S) {
289421
290422 };
291423
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; };
294426
295427 template<class RawShape>
296 inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
428 inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
297429 const _Item<RawShape>& stationary,
298430 const _Item<RawShape>& orbiter)
299431 {
313445 auto dtouch = touch_sh - touch_other;
314446 auto top_other = orbiter.rightmostTopVertex() + dtouch;
315447 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);
317449 }
318450
319451 template<class RawShape>
320 inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
452 inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
321453 const RawShape& stationary,
322454 const _Item<RawShape>& orbiter)
323455 {
324 auto touch_sh = Nfp::rightmostUpVertex(stationary);
456 auto touch_sh = nfp::rightmostUpVertex(stationary);
325457 auto touch_other = orbiter.leftmostBottomVertex();
326458 auto dtouch = touch_sh - touch_other;
327459 auto top_other = orbiter.rightmostTopVertex() + dtouch;
328460 auto dnfp = top_other - nfp.second;
329 ShapeLike::translate(nfp.first, dnfp);
461 shapelike::translate(nfp.first, dnfp);
330462 }
331463
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};
373515 }
374516
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);
441520 }
442521
443522 template<class RawShape, class TBin = _Box<TPoint<RawShape>>>
451530
452531 using Box = _Box<TPoint<RawShape>>;
453532
533 using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
534
535 using ItemKeys = std::vector<__itemhash::Key>;
536
537 // Norming factor for the optimization function
454538 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_;
459545
460546 public:
461547
462 using Pile = Nfp::Shapes<RawShape>;
548 using Pile = nfp::Shapes<RawShape>;
463549
464550 inline explicit _NofitPolyPlacer(const BinType& bin):
465551 Base(bin),
466 norm_(std::sqrt(sl::area<RawShape>(bin))),
467 penality_(1e6*norm_) {}
552 norm_(std::sqrt(sl::area(bin))) {}
468553
469554 _NofitPolyPlacer(const _NofitPolyPlacer&) = default;
470555 _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
474559 _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default;
475560 #endif
476561
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);
479564 auto d = bbin.center() - bb.center();
480565 _Rectangle<RawShape> rect(bb.width(), bb.height());
481566 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);
488573 auto d = bbch.center() - bbin.center();
489574 auto chullcpy = chull;
490575 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)
495580 {
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)
501586 {
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)
506596 {
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,
511603 const _Circle<Vertex>& bin)
512604 {
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()) {
517838
518839 PackResult ret;
519840
520841 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);
521846
522847 if(items_.empty()) {
523848 setInitialPosition(item);
524 can_pack = item.isInside(bin_);
849 best_overfit = overfit(item.transformedShape(), bin_);
850 can_pack = best_overfit <= 0;
525851 } else {
526852
527 double global_score = penality_;
853 double global_score = std::numeric_limits<double>::max();
528854
529855 auto initial_tr = item.translation();
530856 auto initial_rot = item.rotation();
531857 Vertex final_tr = {0, 0};
532858 Radians final_rot = initial_rot;
533 Nfp::Shapes<RawShape> nfps;
859 Shapes nfps;
534860
535861 for(auto rot : config_.rotations) {
536862
537863 item.translation(initial_tr);
538864 item.rotation(initial_rot + rot);
865 item.boundingBox(); // fill the bb cache
539866
540867 // 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
542869 placeOutsideOfBin(item);
543870
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();
548874
549875 auto startpos = item.translation();
550876
551 std::vector<EdgeCache<RawShape>> ecache;
877 std::vector<Edges> ecache;
552878 ecache.reserve(nfps.size());
553879
554880 for(auto& nfp : nfps ) {
556882 ecache.back().accuracy(config_.accuracy);
557883 }
558884
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);
567926 };
568927
569928 auto getNfpPoint = [&ecache](const Optimum& opt)
572931 ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
573932 };
574933
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)
595937 {
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) {
632938 auto v = getNfpPoint(o);
633939 auto d = v - iv;
634940 d += startpos;
638944 auto chull = sl::convexHull(merged_pile);
639945 merged_pile.pop_back();
640946
641 return wouldFit(chull, bin_);
947 return overfit(chull, bin);
642948 };
643949
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
649950 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>;
651960
652961 // Local optimization with the four polygon corners as
653962 // starting points
654963 for(unsigned ch = 0; ch < ecache.size(); ch++) {
655964 auto& cache = ecache[ch];
656965
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)
659976 {
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
668987 try {
669 auto result = solver.optimize_min(contour_ofn,
988 results[n] = solver.optimize_min(contour_ofn,
670989 opt::initvals<double>(pos),
671990 opt::bound<double>(0, 1.0)
672991 );
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 }
681992 } catch(std::exception& e) {
682993 derr() << "ERROR: " << e.what() << "\n";
683994 }
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 }
6851015
6861016 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)
6901026 {
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
7011038 try {
702 auto result = solver.optimize_min(hole_ofn,
1039 results[n] = solver.optimize_min(hole_ofn,
7031040 opt::initvals<double>(pos),
7041041 opt::bound<double>(0, 1.0)
7051042 );
7061043
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 }
7151044 } catch(std::exception& e) {
7161045 derr() << "ERROR: " << e.what() << "\n";
7171046 }
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 }
7191064 }
7201065 }
7211066
7351080
7361081 if(can_pack) {
7371082 ret = PackResult(item);
1083 item_keys_.emplace_back(itemhash);
1084 } else {
1085 ret = PackResult(best_overfit);
7381086 }
7391087
7401088 return ret;
7411089 }
7421090
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;
7491099 m.reserve(items_.size());
750
7511100 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);
7531114
7541115 Vertex ci, cb;
755 auto bbin = sl::boundingBox<RawShape>(bin_);
7561116
7571117 switch(config_.alignment) {
7581118 case Config::Alignment::CENTER: {
7841144
7851145 auto d = cb - ci;
7861146 for(Item& item : items_) item.translate(d);
787
788 Base::clearItems();
789 }
790
791 private:
1147 }
7921148
7931149 void setInitialPosition(Item& item) {
7941150 Box&& bb = item.boundingBox();
7951151 Vertex ci, cb;
796 auto bbin = sl::boundingBox<RawShape>(bin_);
1152 auto bbin = sl::boundingBox(bin_);
7971153
7981154 switch(config_.starting_point) {
7991155 case Config::Alignment::CENTER: {
8291185
8301186 void placeOutsideOfBin(Item& item) {
8311187 auto&& bb = item.boundingBox();
832 Box binbb = sl::boundingBox<RawShape>(bin_);
1188 Box binbb = sl::boundingBox(bin_);
8331189
8341190 Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
8351191
22
33 #include "../libnest2d.hpp"
44
5 namespace libnest2d { namespace strategies {
5 namespace libnest2d { namespace placers {
66
77 struct EmptyConfig {};
88
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>
1310 class PlacerBoilerplate {
1411 mutable bool farea_valid_ = false;
1512 mutable double farea_ = 0.0;
2118 using Coord = TCoord<Vertex>;
2219 using Unit = Coord;
2320 using Config = Cfg;
24 using Container = Store;
21 using ItemGroup = _ItemGroup<Item>;
22 using DefaultIter = typename ItemGroup::const_iterator;
2523
2624 class PackResult {
2725 Item *item_ptr_;
2826 Vertex move_;
2927 Radians rot_;
28 double overfit_;
3029 friend class PlacerBoilerplate;
3130 friend Subclass;
31
3232 PackResult(Item& item):
3333 item_ptr_(&item),
3434 move_(item.translation()),
3535 rot_(item.rotation()) {}
36 PackResult(): item_ptr_(nullptr) {}
36
37 PackResult(double overfit = 1.0):
38 item_ptr_(nullptr), overfit_(overfit) {}
39
3740 public:
3841 operator bool() { return item_ptr_ != nullptr; }
42 double overfit() const { return overfit_; }
3943 };
40
41 using ItemGroup = const Container&;
4244
4345 inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin)
4446 {
5557 config_ = config;
5658 }
5759
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);
6064 if(r) {
6165 items_.push_back(*(r.item_ptr_));
6266 farea_valid_ = false;
7882 farea_valid_ = false;
7983 }
8084
81 inline ItemGroup getItems() const { return items_; }
85 inline const ItemGroup& getItems() const { return items_; }
8286
8387 inline void clearItems() {
8488 items_.clear();
8589 farea_valid_ = false;
86 #ifndef NDEBUG
87 debug_items_.clear();
88 #endif
8990 }
9091
9192 inline double filledArea() const {
102103 return farea_;
103104 }
104105
105 #ifndef NDEBUG
106 std::vector<Item> debug_items_;
107 #endif
108
109106 protected:
110107
111108 BinType bin_;
112 Container items_;
109 ItemGroup items_;
113110 Cfg config_;
114111 };
115112
120117 using Base::config_; \
121118 public: \
122119 using typename Base::Item; \
120 using typename Base::ItemGroup; \
123121 using typename Base::BinType; \
124122 using typename Base::Config; \
125123 using typename Base::Vertex; \
127125 using typename Base::PackResult; \
128126 using typename Base::Coord; \
129127 using typename Base::Unit; \
130 using typename Base::Container; \
131128 private:
132129
133130 }
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
77
88 #include "selection_boilerplate.hpp"
99
10 namespace libnest2d { namespace strategies {
10 namespace libnest2d { namespace selections {
1111
1212 /**
1313 * Selection heuristic based on [López-Camacho]\
117117 using Placer = PlacementStrategyLike<TPlacer>;
118118 using ItemList = std::list<ItemRef>;
119119
120 const double bin_area = ShapeLike::area<RawShape>(bin);
120 const double bin_area = sl::area(bin);
121121 const double w = bin_area * config_.waste_increment;
122122
123123 const double INITIAL_FILL_PROPORTION = config_.initial_fill_proportion;
226226 bool ret = false;
227227 auto it = not_packed.begin();
228228
229 auto pack = [&placer, &not_packed](ItemListIt it) {
230 return placer.pack(*it, rem(it, not_packed));
231 };
232
229233 while(it != not_packed.end() && !ret &&
230234 free_area - (item_area = it->get().area()) <= waste)
231235 {
232 if(item_area <= free_area && placer.pack(*it) ) {
236 if(item_area <= free_area && pack(it) ) {
233237 free_area -= item_area;
234238 filled_area = bin_area - free_area;
235239 ret = true;
269273 auto it2 = it;
270274
271275 std::vector<TPair> wrong_pairs;
276 using std::placeholders::_1;
277
278 auto trypack = [&placer, &not_packed](ItemListIt it) {
279 return placer.trypack(*it, rem(it, not_packed));
280 };
272281
273282 while(it != endit && !ret &&
274283 free_area - (item_area = it->get().area()) -
277286 if(item_area + smallestPiece(it, not_packed)->get().area() >
278287 free_area ) { it++; continue; }
279288
280 auto pr = placer.trypack(*it);
289 auto pr = trypack(it);
281290
282291 // First would fit
283292 it2 = not_packed.begin();
293302 }
294303
295304 placer.accept(pr);
296 auto pr2 = placer.trypack(*it2);
305 auto pr2 = trypack(it2);
297306 if(!pr2) {
298307 placer.unpackLast(); // remove first
299308 if(try_reverse) {
300 pr2 = placer.trypack(*it2);
309 pr2 = trypack(it2);
301310 if(pr2) {
302311 placer.accept(pr2);
303 auto pr12 = placer.trypack(*it);
312 auto pr12 = trypack(it);
304313 if(pr12) {
305314 placer.accept(pr12);
306315 ret = true;
364373 return it->get().area();
365374 };
366375
376 auto trypack = [&placer, &not_packed](ItemListIt it) {
377 return placer.trypack(*it, rem(it, not_packed));
378 };
379
380 auto pack = [&placer, &not_packed](ItemListIt it) {
381 return placer.pack(*it, rem(it, not_packed));
382 };
383
367384 while (it != endit && !ret) { // drill down 1st level
368385
369386 // We need to determine in each iteration the largest, second
393410 it++; continue;
394411 }
395412
396 auto pr = placer.trypack(*it);
413 auto pr = trypack(it);
397414
398415 // Check for free area and try to pack the 1st item...
399416 if(!pr) { it++; continue; }
419436 bool can_pack2 = false;
420437
421438 placer.accept(pr);
422 auto pr2 = placer.trypack(*it2);
439 auto pr2 = trypack(it2);
423440 auto pr12 = pr;
424441 if(!pr2) {
425442 placer.unpackLast(); // remove first
426443 if(try_reverse) {
427 pr2 = placer.trypack(*it2);
444 pr2 = trypack(it2);
428445 if(pr2) {
429446 placer.accept(pr2);
430 pr12 = placer.trypack(*it);
447 pr12 = trypack(it);
431448 if(pr12) can_pack2 = true;
432449 placer.unpackLast();
433450 }
462479 if(a3_sum > free_area) { it3++; continue; }
463480
464481 placer.accept(pr12); placer.accept(pr2);
465 bool can_pack3 = placer.pack(*it3);
482 bool can_pack3 = pack(it3);
466483
467484 if(!can_pack3) {
468485 placer.unpackLast();
472489 if(!can_pack3 && try_reverse) {
473490
474491 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](
479496 const decltype(indices)& idx)
480497 {
481498 std::array<bool, 3> packed = {false};
482499
483500 for(auto id : idx) packed.at(id) =
484 placer.pack(candidates[id]);
501 pack(candidates[id]);
485502
486503 bool check =
487504 std::all_of(packed.begin(),
535552 { auto it = store_.begin();
536553 while (it != store_.end()) {
537554 Placer p(bin); p.configure(pconfig);
538 if(!p.pack(*it)) {
555 if(!p.pack(*it, rem(it, store_))) {
539556 it = store_.erase(it);
540557 } else it++;
541558 }
550567 {
551568
552569 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
558571 // TODO here should be a spinlock
559572 slock.lock();
560573 acounter -= packednum;
600613 while(it != not_packed.end() &&
601614 filled_area < INITIAL_FILL_AREA)
602615 {
603 if(placer.pack(*it)) {
616 if(placer.pack(*it, rem(it, not_packed))) {
604617 filled_area += it->get().area();
605618 free_area = bin_area - filled_area;
606619 it = not_packed.erase(it);
22
33 #include "selection_boilerplate.hpp"
44
5 namespace libnest2d { namespace strategies {
5 namespace libnest2d { namespace selections {
66
77 template<class RawShape>
88 class _FillerSelection: public SelectionBoilerplate<RawShape> {
5555
5656 std::sort(store_.begin(), store_.end(), sortfunc);
5757
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
6258 PlacementStrategyLike<TPlacer> placer(bin);
6359 placer.configure(pconfig);
6460
6561 auto it = store_.begin();
6662 while(it != store_.end()) {
67 if(!placer.pack(*it)) {
63 if(!placer.pack(*it, {std::next(it), store_.end()})) {
6864 if(packed_bins_.back().empty()) ++it;
69 // makeProgress(placer);
7065 placer.clearItems();
7166 packed_bins_.emplace_back();
7267 } else {
7570 }
7671 }
7772
78 // if(was_packed) {
79 // packed_bins_.push_back(placer.getItems());
80 // }
8173 }
8274 };
8375
33 #include "../libnest2d.hpp"
44 #include "selection_boilerplate.hpp"
55
6 namespace libnest2d { namespace strategies {
6 namespace libnest2d { namespace selections {
77
88 template<class RawShape>
99 class _FirstFitSelection: public SelectionBoilerplate<RawShape> {
3939 packed_bins_.clear();
4040
4141 std::vector<Placer> placers;
42 placers.reserve(last-first);
4243
4344 std::copy(first, last, std::back_inserter(store_));
4445
6566 }
6667 }
6768
68 for(auto& item : store_ ) {
69 auto it = store_.begin();
70
71 while(it != store_.end()) {
6972 bool was_packed = false;
73 size_t j = 0;
7074 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);
7578 }
7679
7780 if(!was_packed) {
7881 placers.emplace_back(bin);
7982 placers.back().configure(pconfig);
8083 packed_bins_.emplace_back();
84 j = placers.size() - 1;
8185 }
8286 }
87 ++it;
8388 }
8489 }
8590
22
33 #include "../libnest2d.hpp"
44
5 namespace libnest2d {
6 namespace strategies {
5 namespace libnest2d { namespace selections {
76
87 template<class RawShape>
98 class SelectionBoilerplate {
2121 using Coord = TCoord<PointImpl>;
2222 using Box = _Box<PointImpl>;
2323 using Segment = _Segment<PointImpl>;
24 using Circle = _Circle<PointImpl>;
2425
2526 using Item = _Item<PolygonImpl>;
2627 using Rectangle = _Rectangle<PolygonImpl>;
2829 using PackGroup = _PackGroup<PolygonImpl>;
2930 using IndexedPackGroup = _IndexedPackGroup<PolygonImpl>;
3031
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>;
3435
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>;
4038
4139 }
4240
44 #include "printer_parts.h"
55 #include <libnest2d/geometry_traits_nfp.hpp>
66 //#include "../tools/libnfpglue.hpp"
7 //#include "../tools/nfp_svgnest_glue.hpp"
78
89 std::vector<libnest2d::Item>& prusaParts() {
910 static std::vector<libnest2d::Item> ret;
9899
99100 }
100101
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
101139 TEST(GeometryAlgorithms, Distance) {
102140 using namespace libnest2d;
103141
106144 Point p2 = {10, 0};
107145 Point p3 = {10, 10};
108146
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));
111149
112150 Segment seg(p1, p3);
113151
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);
117155
118156 auto check = [](Coord val, Coord expected) {
119157 if(std::is_floating_point<Coord>::value)
126164 ASSERT_TRUE(result.second);
127165 check(result.first, 10);
128166
129 result = PointLike::verticalDistance(p2, seg);
167 result = pointlike::verticalDistance(p2, seg);
130168 ASSERT_TRUE(result.second);
131169 check(result.first, -10);
132170
133 result = PointLike::verticalDistance(Point{10, 20}, seg);
171 result = pointlike::verticalDistance(Point{10, 20}, seg);
134172 ASSERT_TRUE(result.second);
135173 check(result.first, 10);
136174
138176 Point p4 = {80, 0};
139177 Segment seg2 = { {0, 0}, {0, 40} };
140178
141 result = PointLike::horizontalDistance(p4, seg2);
179 result = pointlike::horizontalDistance(p4, seg2);
142180
143181 ASSERT_TRUE(result.second);
144182 check(result.first, 80);
145183
146 result = PointLike::verticalDistance(p4, seg2);
184 result = pointlike::verticalDistance(p4, seg2);
147185 // Point should not be related to the segment
148186 ASSERT_FALSE(result.second);
149187
171209 {61, 97}
172210 };
173211
174 ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 );
212 ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 );
175213 }
176214
177215 TEST(GeometryAlgorithms, IsPointInsidePolygon) {
181219
182220 Point p = {1, 1};
183221
184 ASSERT_TRUE(rect.isPointInside(p));
222 ASSERT_TRUE(rect.isInside(p));
185223
186224 p = {11, 11};
187225
188 ASSERT_FALSE(rect.isPointInside(p));
226 ASSERT_FALSE(rect.isInside(p));
189227
190228
191229 p = {11, 12};
192230
193 ASSERT_FALSE(rect.isPointInside(p));
231 ASSERT_FALSE(rect.isInside(p));
194232
195233
196234 p = {3, 3};
197235
198 ASSERT_TRUE(rect.isPointInside(p));
236 ASSERT_TRUE(rect.isInside(p));
199237
200238 }
201239
249287
250288 Item leftp(placer.leftPoly(item));
251289
252 ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first);
290 ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first);
253291 ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount());
254292
255293 for(unsigned long i = 0; i < leftControl.vertexCount(); i++) {
259297
260298 Item downp(placer.downPoly(item));
261299
262 ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first);
300 ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first);
263301 ASSERT_EQ(downp.vertexCount(), downControl.vertexCount());
264302
265303 for(unsigned long i = 0; i < downControl.vertexCount(); i++) {
296334 {20, 20} };
297335
298336
299 Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
337 Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
300338
301339 auto groups = arrange(rects.begin(), rects.end());
302340
349387
350388 Coord min_obj_distance = 5;
351389
352 Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
390 Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
353391 min_obj_distance);
354392
355393 auto groups = arrange(rects.begin(), rects.end());
400438 setX(v, getX(v)/SCALE);
401439 rbin.setVertex(i, v);
402440 }
403 out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
441 out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
404442 for(Item& sh : r) {
405443 Item tsh(sh.transformedShape());
406444 for(unsigned i = 0; i < tsh.vertexCount(); i++) {
409447 setX(v, getX(v)/SCALE);
410448 tsh.setVertex(i, v);
411449 }
412 out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
450 out << shapelike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
413451 }
414452 out << "\n</svg>" << std::endl;
415453 }
663701 }
664702 };
665703
666 template<NfpLevel lvl, Coord SCALE>
704 template<nfp::NfpLevel lvl, Coord SCALE>
667705 void testNfp(const std::vector<ItemPair>& testdata) {
668706 using namespace libnest2d;
669707
673711
674712 auto& exportfun = exportSVG<SCALE, Box>;
675713
676 auto onetest = [&](Item& orbiter, Item& stationary){
714 auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){
677715 testcase++;
678716
679717 orbiter.translate({210*SCALE, 0});
680718
681 auto&& nfp = Nfp::noFitPolygon<lvl>(stationary.rawShape(),
719 auto&& nfp = nfp::noFitPolygon<lvl>(stationary.rawShape(),
682720 orbiter.transformedShape());
683721
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);
693735
694736 Item infp(nfp.first);
695737
696738 int i = 0;
697739 auto rorbiter = orbiter.transformedShape();
698 auto vo = Nfp::referenceVertex(rorbiter);
740 auto vo = nfp::referenceVertex(rorbiter);
699741
700742 ASSERT_TRUE(stationary.isInside(infp));
701743
709751
710752 bool touching = Item::touches(tmp, stationary);
711753
712 if(!touching) {
754 if(!touching || !valid.first) {
713755 std::vector<std::reference_wrapper<Item>> inp = {
714756 std::ref(stationary), std::ref(tmp), std::ref(infp)
715757 };
721763 }
722764 };
723765
766 unsigned tidx = 0;
724767 for(auto& td : testdata) {
725768 auto orbiter = td.orbiter;
726769 auto stationary = td.stationary;
727 onetest(orbiter, stationary);
728 }
729
770 onetest(orbiter, stationary, tidx++);
771 }
772
773 tidx = 0;
730774 for(auto& td : testdata) {
731775 auto orbiter = td.stationary;
732776 auto stationary = td.orbiter;
733 onetest(orbiter, stationary);
777 onetest(orbiter, stationary, tidx++);
734778 }
735779 }
736780 }
737781
738782 TEST(GeometryAlgorithms, nfpConvexConvex) {
739 testNfp<NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
783 testNfp<nfp::NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
740784 }
741785
742786 //TEST(GeometryAlgorithms, nfpConcaveConcave) {
757801
758802 Rectangle input(10, 10);
759803
760 strategies::EdgeCache<PolygonImpl> ecache(input);
804 placers::EdgeCache<PolygonImpl> ecache(input);
761805
762806 auto first = *input.begin();
763807 ASSERT_TRUE(getX(first) == getX(ecache.coords(0)));
769813
770814 for(int i = 0; i <= 100; i++) {
771815 auto v = ecache.coords(i*(0.01));
772 ASSERT_TRUE(ShapeLike::touches(v, input.transformedShape()));
816 ASSERT_TRUE(shapelike::touches(v, input.transformedShape()));
773817 }
774818 }
775819
783827 rect2.translate({10, 0});
784828 rect3.translate({25, 0});
785829
786 ShapeLike::Shapes<PolygonImpl> pile;
830 shapelike::Shapes<PolygonImpl> pile;
787831 pile.push_back(rect1.transformedShape());
788832 pile.push_back(rect2.transformedShape());
789833
790 auto result = Nfp::merge(pile, rect3.transformedShape());
834 auto result = nfp::merge(pile, rect3.transformedShape());
791835
792836 ASSERT_EQ(result.size(), 1);
793837
794838 Rectangle ref(45, 15);
795839
796 ASSERT_EQ(ShapeLike::area(result.front()), ref.area());
840 ASSERT_EQ(shapelike::area(result.front()), ref.area());
797841 }
798842
799843 int main(int argc, char **argv) {
5555
5656 NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
5757 {
58 using Vertex = PointImpl;
58 namespace sl = shapelike;
5959
6060 NfpR ret;
6161
8484 // this can throw
8585 auto nfp = libnfporb::generateNFP(pstat, porb, true);
8686
87 auto &ct = ShapeLike::getContour(ret.first);
87 auto &ct = sl::getContour(ret.first);
8888 ct.reserve(nfp.front().size()+1);
8989 for(auto v : nfp.front()) {
9090 v = scale(v, refactor);
9393 ct.push_back(ct.front());
9494 std::reverse(ct.begin(), ct.end());
9595
96 auto &rholes = ShapeLike::holes(ret.first);
96 auto &rholes = sl::holes(ret.first);
9797 for(size_t hidx = 1; hidx < nfp.size(); ++hidx) {
9898 if(nfp[hidx].size() >= 3) {
9999 rholes.emplace_back();
109109 }
110110 }
111111
112 ret.second = Nfp::referenceVertex(ret.first);
112 ret.second = nfp::referenceVertex(ret.first);
113113
114114 } catch(std::exception& e) {
115115 std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
116116 // auto ch_stat = ShapeLike::convexHull(sh);
117117 // auto ch_orb = ShapeLike::convexHull(cother);
118 ret = Nfp::nfpConvexOnly(sh, cother);
118 ret = nfp::nfpConvexOnly(sh, cother);
119119 }
120120
121121 return ret;
122122 }
123123
124 NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
124 NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY>::operator()(
125125 const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
126126 {
127127 return _nfp(sh, cother);//nfpConvexOnly(sh, cother);
128128 }
129129
130 NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
130 NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX>::operator()(
131131 const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
132132 {
133133 return _nfp(sh, cother);
134134 }
135135
136 NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
136 NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE>::operator()(
137137 const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
138138 {
139139 return _nfp(sh, cother);
44
55 namespace libnest2d {
66
7 using NfpR = Nfp::NfpResult<PolygonImpl>;
7 using NfpR = nfp::NfpResult<PolygonImpl>;
88
99 NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother);
1010
1111 template<>
12 struct Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
12 struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY> {
1313 NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
1414 };
1515
1616 template<>
17 struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
17 struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX> {
1818 NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
1919 };
2020
2121 template<>
22 struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
22 struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE> {
2323 NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
2424 };
2525
3333 // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
3434 //};
3535
36 template<> struct Nfp::MaxNfpLevel<PolygonImpl> {
36 template<> struct nfp::MaxNfpLevel<PolygonImpl> {
3737 static const BP2D_CONSTEXPR NfpLevel value =
3838 // NfpLevel::CONVEX_ONLY;
3939 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
5555 auto d = static_cast<Coord>(
5656 std::round(conf_.height*conf_.mm_in_coord_units) );
5757
58 auto& contour = ShapeLike::getContour(tsh);
58 auto& contour = shapelike::getContour(tsh);
5959 for(auto& v : contour) setY(v, -getY(v) + d);
6060
61 auto& holes = ShapeLike::holes(tsh);
61 auto& holes = shapelike::holes(tsh);
6262 for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d);
6363
6464 }
65 currentLayer() += ShapeLike::serialize<Formats::SVG>(tsh,
65 currentLayer() += shapelike::serialize<Formats::SVG>(tsh,
6666 1.0/conf_.mm_in_coord_units) + "\n";
6767 }
6868
11701170 // Allow DynamicConfig to be instantiated on ints own without a definition.
11711171 // If the definition is not defined, the method requiring the definition will throw NoDefinitionException.
11721172 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(); }
11731174 template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
11741175 { return dynamic_cast<T*>(this->option(opt_key, create)); }
11751176 template<class T> const T* opt(const t_config_option_key &opt_key) const
12131214 bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
12141215 bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
12151216
1216 private:
1217 protected:
12171218 typedef std::map<t_config_option_key,ConfigOption*> t_options_map;
12181219 t_options_map options;
12191220 };
88 #endif /* SLIC3R_GUI */
99
1010 #include "libslic3r.h"
11 #include "ClipperUtils.hpp"
1112 #include "EdgeGrid.hpp"
13 #include "SVG.hpp"
1214
1315 #if 0
1416 // Enable debugging and assert in this file.
755757 float search_radius = float(m_resolution<<1);
756758 m_signed_distance_field.assign(nrows * ncols, search_radius);
757759 // 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) {
760762 const Cell &cell = m_cells[r * m_cols + c];
761763 // For each segment in the cell:
762764 for (size_t i = cell.begin; i != cell.end; ++ i) {
841843 #if 0
842844 static int iRun = 0;
843845 ++ iRun;
846 if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
847 wxImage::AddHandler(new wxPNGHandler);
844848 //#ifdef SLIC3R_GUI
845849 {
846850 wxImage img(ncols, nrows);
12241228 return true;
12251229 }
12261230
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
12291235 typedef std::unordered_multimap<Point, int, PointHash> EndPointMapType;
12301236 // 0) Prepare a binary grid.
12311237 size_t cell_rows = m_rows + 2;
12361242 cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1);
12371243 // Fill in empty cells, which have a left / right neighbor filled.
12381244 // Fill in empty cells, which have the top / bottom neighbor filled.
1239 {
1245 if (fill_holes) {
12401246 std::vector<char> cell_inside2(cell_inside);
12411247 for (int r = 1; r + 1 < int(cell_rows); ++ r) {
12421248 for (int c = 1; c + 1 < int(cell_cols); ++ c) {
13531359 return out;
13541360 }
13551361
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
13561451 #if 0
13571452 void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path)
13581453 {
1454 if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
1455 wxImage::AddHandler(new wxPNGHandler);
1456
13591457 unsigned int w = (bbox.max.x - bbox.min.x + resolution - 1) / resolution;
13601458 unsigned int h = (bbox.max.y - bbox.min.y + resolution - 1) / resolution;
13611459 wxImage img(w, h);
14471545 }
14481546 #endif /* SLIC3R_GUI */
14491547
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
14501603 } // namespace Slic3r
5757 const size_t cols() const { return m_cols; }
5858
5959 // 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;
6166
6267 protected:
6368 struct Cell {
112117 #endif /* SLIC3R_GUI */
113118
114119 } // 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
115127 } // namespace Slic3r
116128
117129 #endif /* slic3r_EdgeGrid_hpp_ */
6060 }
6161
6262 template <class T>
63 bool
64 ExPolygonCollection::contains(const T &item) const
63 bool ExPolygonCollection::contains(const T &item) const
6564 {
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;
6968 return false;
7069 }
7170 template bool ExPolygonCollection::contains<Point>(const Point &item) const;
9090 // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
9191 virtual double min_mm3_per_mm() const = 0;
9292 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; }
9395 virtual double length() const = 0;
9496 virtual double total_volume() const = 0;
9597 };
122124
123125 ExtrusionPath* clone() const { return new ExtrusionPath (*this); }
124126 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(); }
127132 // Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection.
128133 // Currently not used.
129134 void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
132137 void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
133138 void clip_end(double distance);
134139 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; }
137142 // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
138143 // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
139144 void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
148153 // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
149154 double min_mm3_per_mm() const { return this->mm3_per_mm; }
150155 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()); }
152158
153159 private:
154160 void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
177183 bool can_reverse() const { return true; }
178184 ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); }
179185 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(); }
184190 // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
185191 // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
186192 void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
195201 // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
196202 double min_mm3_per_mm() const;
197203 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; }
199206 };
200207
201208 // Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
217224 bool make_clockwise();
218225 bool make_counter_clockwise();
219226 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(); }
222229 Polygon polygon() const;
223 virtual double length() const;
230 double length() const override;
224231 bool split_at_vertex(const Point &point);
225232 void split_at(const Point &point, bool prefer_non_overhang);
226233 void clip_end(double distance, ExtrusionPaths* paths) const;
227234 // Test, whether the point is extruded by a bridging flow.
228235 // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead.
229236 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; }
232239 // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
233240 // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
234241 void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
243250 // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
244251 double min_mm3_per_mm() const;
245252 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; }
247255
248256 private:
249257 ExtrusionLoopRole m_loop_role;
2323 explicit operator ExtrusionPaths() const;
2424
2525 bool is_collection() const { return true; };
26 virtual ExtrusionRole role() const {
26 ExtrusionRole role() const override {
2727 ExtrusionRole out = erNone;
2828 for (const ExtrusionEntity *ee : entities) {
2929 ExtrusionRole er = ee->role();
6565 Point last_point() const { return this->entities.back()->last_point(); }
6666 // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
6767 // 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;
6969 // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
7070 // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
7171 // 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;
7373 Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
7474 { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
7575 Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
7878 void flatten(ExtrusionEntityCollection* retval) const;
7979 ExtrusionEntityCollection flatten() const;
8080 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; }
8282
8383 // Following methods shall never be called on an ExtrusionEntityCollection.
8484 Polyline as_polyline() const {
8585 CONFESS("Calling as_polyline() on a ExtrusionEntityCollection");
8686 return Polyline();
8787 };
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 {
8995 CONFESS("Calling length() on a ExtrusionEntityCollection");
9096 return 0.;
9197 }
130130 // no rotation is supported for this infill pattern (yet)
131131 BoundingBox bb = expolygon.contour.bounding_box();
132132 // 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);
134134 // Distance between the gyroid waves in scaled coordinates.
135135 coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
136136
1313 virtual Fill* clone() const { return new FillGyroid(*this); }
1414
1515 // 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; }
1717
1818 protected:
1919 virtual void _fill_surface_single(
8585 Polylines paths;
8686 {
8787 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);
9090 paths = intersection_pl(p, to_polygons(expolygon));
9191 }
9292
114114 // 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.
115115 float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
116116 (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value),
117 false);
117 // bridge_flow_ratio
118 0.f);
118119 }
119120
120121 Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
126127 (width.value > 0) ? width : object->config.extrusion_width,
127128 float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
128129 (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);
130132 }
131133
132134 Flow support_material_interface_flow(const PrintObject *object, float layer_height)
138140 // 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.
139141 float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)),
140142 (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value),
141 false);
143 // bridge_flow_ratio
144 0.f);
142145 }
143146
144147 }
7070
7171 const char* NAME_KEY = "name";
7272 const char* MODIFIER_KEY = "modifier";
73 const char* VOLUME_TYPE_KEY = "volume_type";
7374
7475 const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
7576 const char* VALID_OBJECT_TYPES[] =
602603
603604 if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr))
604605 return false;
605
606 object.second->center_around_origin();
607606 }
608607
609608 // fixes the min z of the model if negative
15001499 if (metadata.key == NAME_KEY)
15011500 volume->name = metadata.value;
15021501 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));
15041505 else
15051506 volume->config.set_deserialize(metadata.key, metadata.value);
15061507 }
20162017 if (!volume->name.empty())
20172018 stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
20182019
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())
20212022 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";
20222026
20232027 // stores volume's config data
20242028 for (const std::string& key : volume->config.keys())
457457 p = end + 1;
458458 }
459459 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 }
463468 }
464469 } else if (m_path.size() == 3) {
465470 if (m_path[1] == NODE_TYPE_MATERIAL) {
780785 stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n";
781786 if (!volume->name.empty())
782787 stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
783 if (volume->modifier)
788 if (volume->is_modifier())
784789 stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
790 stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
785791 for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
786792 stream << " <triangle>\n";
787793 for (int j = 0; j < 3; ++j)
154154 // the wipe tower has been completely covered by the tool change extrusions,
155155 // or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
156156 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;
157163 };
158164
159165 }; // namespace Slic3r
110110 const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
111111 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); }
112112 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; }
113114
114115 // 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)
116117 {
117118 if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate))
118119 // Neither extrusion nor a travel move.
121122 float dx = x - m_current_pos.x;
122123 float dy = y - m_current_pos.y;
123124 double len = sqrt(dx*dx+dy*dy);
125 if (record_length)
126 m_used_filament_length += e;
124127
125128
126129 // Now do the "internal rotation" with respect to the wipe tower center
161164 return *this;
162165 }
163166
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); }
166169
167170 // Travel to a new XY position. f=0 means use the current value.
168171 Writer& travel(float x, float y, float f = 0.f)
176179 {
177180 float dx = x - m_current_pos.x;
178181 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);
180183 }
181184
182185 Writer& extrude(const WipeTower::xy &dest, const float f = 0.f)
258261 // extrude quickly amount e to x2 with feed f.
259262 Writer& ram(float x1, float x2, float dy, float e0, float e, float f)
260263 {
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);
263266 return *this;
264267 }
265268
403406 float m_last_fan_speed = 0.f;
404407 int current_temp = -1;
405408 const float m_default_analyzer_line_width;
409 float m_used_filament_length = 0.f;
406410
407411 std::string set_format_X(float x)
408412 {
524528 ++ m_num_tool_changes;
525529 }
526530
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
527534 // Reset the extruder current to a normal value.
528535 writer.set_extruder_trimpot(550)
529536 .feedrate(6000)
535542
536543 // so that tool_change() will know to extrude the wipe tower brim:
537544 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();
538548
539549 ToolChangeResult result;
540550 result.priming = true;
605615 toolchange_Load(writer, cleaning_box);
606616 writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
607617 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;
608619 } else
609620 toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
610621
611 ++ m_num_tool_changes;
612622 m_depth_traversed += wipe_area;
613623
614624 if (last_change_in_layer) {// draw perimeter line
631641 ";------------------\n"
632642 "\n\n");
633643
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
634648 ToolChangeResult result;
635649 result.priming = false;
636650 result.print_z = this->m_z_pos;
681695 ";-----------------------------------\n");
682696
683697 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();
684701
685702 ToolChangeResult result;
686703 result.priming = false;
803820 .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed)
804821 .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/
805822 .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).
808826 writer.set_extruder_temp(new_temperature, false);
809827 m_old_temperature = new_temperature;
810828 }
848866 const unsigned int new_tool,
849867 material_type new_material)
850868 {
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
851872 // Speed override for the material. Go slow for flex and soluble materials.
852873 int speed_override;
853874 switch (new_material) {
910931 const float& xl = cleaning_box.ld.x;
911932 const float& xr = cleaning_box.rd.x;
912933
913
914934 // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
915935 // the ordered volume, even if it means violating the box. This can later be removed and simply
916936 // wipe until the end of the assigned area.
925945 m_left_to_right = !m_left_to_right;
926946 }
927947
928
929948 // now the wiping itself:
930949 for (int i = 0; true; ++i) {
931950 if (i!=0) {
934953 else if (wipe_speed < 2210.f) wipe_speed = 4200.f;
935954 else wipe_speed = std::min(4800.f, wipe_speed + 50.f);
936955 }
937
956
938957 float traversed_x = writer.x();
939958 if (m_left_to_right)
940959 writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff);
10491068
10501069 m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
10511070
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
10521075 ToolChangeResult result;
10531076 result.priming = false;
10541077 result.print_z = this->m_z_pos;
11441167 }
11451168 }
11461169
1147
11481170 // Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
11491171 // Resulting ToolChangeResults are appended into vector "result"
11501172 void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
11661188
11671189 m_layer_info = m_plan.begin();
11681190 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;
11691193
11701194 std::vector<WipeTower::ToolChangeResult> layer_result;
11711195 for (auto layer : m_plan)
12071231 }
12081232 }
12091233
1210
1211
1212
12131234 void WipeTowerPrusaMM::make_wipe_tower_square()
12141235 {
12151236 const float width = m_wipe_tower_width - 3 * m_perimeter_width;
12331254 plan_tower(); // propagates depth downwards again (width has changed)
12341255 for (auto& lay : m_plan) // depths set, now the spacing
12351256 lay.extra_spacing = lay.depth / lay.toolchanges_depth();
1236
1237 }
1238
1239
1257 }
12401258
12411259 }; // namespace Slic3r
4545 WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction,
4646 float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging,
4747 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),
4949 m_wipe_tower_width(width),
5050 m_wipe_tower_rotation_angle(rotation_angle),
5151 m_y_shift(0.f),
9393 m_filpar[idx].ramming_step_multiplicator /= 100;
9494 while (stream >> speed)
9595 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
9698 }
9799
98100
170172 virtual bool layer_finished() const {
171173 return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed);
172174 }
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; }
173178
174179
175180 private:
330335 std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
331336 std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
332337
338 // Stores information about used filament length per extruder:
339 std::vector<float> m_used_filament_length;
340
333341
334342 // Returns gcode for wipe tower brim
335343 // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower
273273 }
274274 return gcode_out;
275275 }
276
277276
278277
279278 std::string WipeTowerIntegration::prime(GCode &gcodegen)
664663 _write_format(file, "\n");
665664 }
666665
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
667674 // Prepare the helper object for replacing placeholders in custom G-code and output filename.
668675 m_placeholder_parser = print.placeholder_parser;
669676 m_placeholder_parser.update_timestamp();
723730 m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
724731 m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming);
725732 std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id);
726
727733 // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
728734 this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
729735 // Set extruder(s) temperature before and after start G-code.
959965
960966 // Get filament stats.
961967 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.;
966974 print.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms();
967975 print.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
968976 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
971979 double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
972980 double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
981
973982 print.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament));
974983 _write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001);
975984 if (filament_weight > 0.) {
980989 _write_format(file, "; filament cost = %.1lf\n", filament_cost);
981990 }
982991 }
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.;
985996 }
986997 _write_format(file, "; total filament cost = %.1lf\n", print.total_cost);
987998 _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
9797 void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
9898 std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
9999 std::string finalize(GCode &gcodegen);
100 std::vector<float> used_filament_length() const;
100101
101102 private:
102103 WipeTowerIntegration& operator=(const WipeTowerIntegration&);
167167 }
168168 #endif // ENABLE_MOVE_STATS
169169
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
170173 GCodeTimeEstimator::GCodeTimeEstimator(EMode mode)
171174 : _mode(mode)
172175 {
293296 throw std::runtime_error(std::string("Remaining times export failed.\nError while reading from file.\n"));
294297 }
295298
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";
297308
298309 // add remaining time lines where needed
299310 _parser.parse_line(gcode_line,
1616 class GCodeTimeEstimator
1717 {
1818 public:
19 static const std::string Normal_First_M73_Output_Placeholder_Tag;
20 static const std::string Silent_First_M73_Output_Placeholder_Tag;
21
1922 enum EMode : unsigned char
2023 {
2124 Normal,
2020 friend class Layer;
2121
2222 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; }
2727
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;
3630
3731 // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
3832 // and for re-starting of infills.
39 ExPolygons fill_expolygons;
33 ExPolygons fill_expolygons;
4034 // 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;
4240
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;
5144
5245 // collection of polylines representing the unsupported bridge edges
53 PolylineCollection unsupported_bridge_edges;
46 PolylineCollection unsupported_bridge_edges;
5447
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;
6254
6355 Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
6456 void slices_to_fill_surfaces_clipped();
1414
1515 namespace Slic3r {
1616
17 Flow
18 LayerRegion::flow(FlowRole role, bool bridge, double width) const
17 Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
1918 {
2019 return this->_region->flow(
2120 role,
5049 }
5150 }
5251
53 void
54 LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
52 void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
5553 {
5654 this->perimeters.clear();
5755 this->thin_fills.clear();
339337 #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
340338 }
341339
342 void
343 LayerRegion::prepare_fill_surfaces()
340 void LayerRegion::prepare_fill_surfaces()
344341 {
345342 #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
346343 export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial");
381378 #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
382379 }
383380
384 double
385 LayerRegion::infill_area_threshold() const
381 double LayerRegion::infill_area_threshold() const
386382 {
387383 double ss = this->flow(frSolidInfill).scaled_spacing();
388384 return ss*ss;
454454 unsigned int Model::get_auto_extruder_id(unsigned int max_extruders)
455455 {
456456 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.
459460 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 }
461465 return id;
462466 }
463467
608612 if (! m_bounding_box_valid) {
609613 BoundingBoxf3 raw_bbox;
610614 for (const ModelVolume *v : this->volumes)
611 if (! v->modifier)
615 if (v->is_model_part())
616 // mesh.bounding_box() returns a cached value.
612617 raw_bbox.merge(v->mesh.bounding_box());
613618 BoundingBoxf3 bb;
614619 for (const ModelInstance *i : this->instances)
639644 {
640645 TriangleMesh mesh;
641646 for (const ModelVolume *v : this->volumes)
642 if (! v->modifier)
647 if (v->is_model_part())
643648 mesh.merge(v->mesh);
644649 return mesh;
645650 }
650655 {
651656 BoundingBoxf3 bb;
652657 for (const ModelVolume *v : this->volumes)
653 if (! v->modifier) {
658 if (v->is_model_part()) {
654659 if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances");
655660 bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true));
656661 }
662667 {
663668 BoundingBoxf3 bb;
664669 for (ModelVolume *v : this->volumes)
665 if (! v->modifier)
670 if (v->is_model_part())
666671 bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate));
667672 return bb;
668673 }
673678 // center this object around the origin
674679 BoundingBoxf3 bb;
675680 for (ModelVolume *v : this->volumes)
676 if (! v->modifier)
681 if (v->is_model_part())
677682 bb.merge(v->mesh.bounding_box());
678683
679684 // first align to origin on XYZ
777782 {
778783 size_t num = 0;
779784 for (const ModelVolume *v : this->volumes)
780 if (! v->modifier)
785 if (v->is_model_part())
781786 num += v->mesh.stl.stats.number_of_facets;
782787 return num;
783788 }
785790 bool ModelObject::needed_repair() const
786791 {
787792 for (const ModelVolume *v : this->volumes)
788 if (! v->modifier && v->mesh.needed_repair())
793 if (v->is_model_part() && v->mesh.needed_repair())
789794 return true;
790795 return false;
791796 }
801806 lower->input_file = "";
802807
803808 for (ModelVolume *volume : this->volumes) {
804 if (volume->modifier) {
809 if (! volume->is_model_part()) {
805810 // don't cut modifiers
806811 upper->add_volume(*volume);
807812 lower->add_volume(*volume);
853858 ModelVolume* new_volume = new_object->add_volume(*mesh);
854859 new_volume->name = volume->name;
855860 new_volume->config = volume->config;
856 new_volume->modifier = volume->modifier;
861 new_volume->set_type(volume->type());
857862 new_volume->material_id(volume->material_id());
858863
859864 new_objects->push_back(new_object);
867872 {
868873 for (const ModelVolume* vol : this->volumes)
869874 {
870 if (!vol->modifier)
875 if (vol->is_model_part())
871876 {
872877 for (ModelInstance* inst : this->instances)
873878 {
971976 return m_convex_hull;
972977 }
973978
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
9741015 // Split this volume, append the result to the object owning this volume.
9751016 // Return the number of volumes created from this one.
9761017 // This is useful to assign different materials to different volumes of an object.
164164 // Configuration parameters specific to an object model geometry or a modifier volume,
165165 // overriding the global Slic3r settings and the ModelObject settings.
166166 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
170176 // 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; }
172184 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);
176188 // Split this volume, append the result to the object owning this volume.
177189 // Return the number of volumes created from this one.
178190 // This is useful to assign different materials to different volumes of an object.
182194
183195 void calculate_convex_hull();
184196 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);
185202
186203 private:
187204 // 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)
192211 {
193212 if (mesh.stl.stats.number_of_facets > 1)
194213 calculate_convex_hull();
195214 }
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) {}
197216 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)
199218 {
200219 this->material_id(other.material_id());
201220 }
202221 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)
204223 {
205224 this->material_id(other.material_id());
206225 if (mesh.stl.stats.number_of_facets > 1)
9898
9999 using SpatElement = std::pair<Box, unsigned>;
100100 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 }
101121
102122 std::tuple<double /*score*/, Box /*farthest point from bin center*/>
103123 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,
107127 const Item &item,
128 double bin_area,
108129 double norm, // A norming factor for physical dimensions
109 std::vector<double>& areacache, // pile item areas will be cached
110130 // 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
112134 )
113135 {
114 using pl = PointLike;
115 using sl = ShapeLike;
116
117 static const double BIG_ITEM_TRESHOLD = 0.2;
136 using Coord = TCoord<PointImpl>;
137
118138 static const double ROUNDNESS_RATIO = 0.5;
119139 static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
120140
121141 // 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 };
141145
142146 // Candidate item bounding box
143 auto ibb = item.boundingBox();
147 auto ibb = sl::boundingBox(item.transformedShape());
144148
145149 // 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);
149151
150152 // The bounding box of the big items (they will accumulate in the center
151153 // of the pile
156158 boost::geometry::convert(boostbb, bigbb);
157159 }
158160
159 // The size indicator of the candidate item. This is not the area,
160 // but almost...
161 double item_normarea = normarea(item.area());
162
163161 // Will hold the resulting score
164162 double score = 0;
165163
166 if(item_normarea > BIG_ITEM_TRESHOLD) {
164 if(isBig(item.area()) || spatindex.empty()) {
167165 // 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.
172166
173167 auto minc = ibb.minCorner(); // bottom left corner
174168 auto maxc = ibb.maxCorner(); // top right corner
189183
190184 // The smalles distance from the arranged pile center:
191185 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;
192188
193189 // 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;
220247 }
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;
234248 } else {
235249 // Here there are the small items that should be placed around the
236250 // already processed bigger items.
258272
259273 // The accuracy of optimization.
260274 // 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;
262278 }
263279
264280 template<class TBin>
267283 template<class TBin>
268284 class _ArrBase {
269285 protected:
270 using Placer = strategies::_NofitPolyPlacer<PolygonImpl, TBin>;
286
287 using Placer = TPacker<TBin>;
271288 using Selector = FirstFitSelection;
272 using Packer = Arranger<Placer, Selector>;
289 using Packer = Nester<Placer, Selector>;
273290 using PConfig = typename Packer::PlacementConfig;
274291 using Distance = TCoord<PointImpl>;
275 using Pile = ShapeLike::Shapes<PolygonImpl>;
292 using Pile = sl::Shapes<PolygonImpl>;
276293
277294 Packer pck_;
278295 PConfig pconf_; // Placement configuration
279296 double bin_area_;
280 std::vector<double> areacache_;
281297 SpatIndex rtree_;
298 SpatIndex smallsrtree_;
299 double norm_;
300 Pile merged_pile_;
301 Box pilebb_;
302 ItemGroup remaining_;
303 ItemGroup items_;
282304 public:
283305
284306 _ArrBase(const TBin& bin, Distance dist,
285307 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)))
287310 {
288311 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
289339 pck_.progressIndicator(progressind);
290340 }
291341
292342 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)...);
295345 }
296346 };
297347
303353 std::function<void(unsigned)> progressind):
304354 _ArrBase<Box>(bin, dist, progressind)
305355 {
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
315370 double score = std::get<0>(result);
316371 auto& fullbb = std::get<1>(result);
317372
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 }
322421
323422 return score;
324423 };
334433 std::function<void(unsigned)> progressind):
335434 _ArrBase<PolygonImpl>(bin, dist, progressind)
336435 {
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_);
347449 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;
357450
358451 return score;
359452 };
369462 AutoArranger(Distance dist, std::function<void(unsigned)> progressind):
370463 _ArrBase<Box>(Box(0, 0), dist, progressind)
371464 {
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_);
381477 return std::get<0>(result);
382478 };
383479
439535 return ret;
440536 }
441537
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 {
443552 BOX,
444553 CIRCLE,
445554 IRREGULAR,
446555 WHO_KNOWS
447556 };
448557
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
450634 // Determine the bed shape by hand
451 return BOX;
635 return ret;
452636 }
453637
454638 void applyResult(
524708 });
525709
526710 IndexedPackGroup result;
527 BoundingBox bbb(bed.points);
711
712 if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed);
713
714 BoundingBox bbb(bed);
528715
529716 auto binbb = Box({
530717 static_cast<libnest2d::Coord>(bbb.min.x),
535722 static_cast<libnest2d::Coord>(bbb.max.y)
536723 });
537724
538 switch(bedhint) {
539 case BOX: {
725 switch(bedhint.type) {
726 case BedShapeType::BOX: {
540727
541728 // Create the arranger for the box shaped bed
542729 AutoArranger<Box> arrange(binbb, min_obj_distance, progressind);
546733 result = arrange(shapes.begin(), shapes.end());
547734 break;
548735 }
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());
550743 break;
551 case IRREGULAR:
552 case WHO_KNOWS: {
744 }
745 case BedShapeType::IRREGULAR:
746 case BedShapeType::WHO_KNOWS: {
747
553748 using P = libnest2d::PolygonImpl;
554749
555750 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));
559752
560753 AutoArranger<P> arrange(irrbed, min_obj_distance, progressind);
561754
565758 break;
566759 }
567760 };
761
762 if(result.empty()) return false;
568763
569764 if(first_bin_only) {
570765 applyResult(result.front(), 0, shapemap);
3333 Point first_point() const;
3434 virtual Point last_point() const = 0;
3535 virtual Lines lines() const = 0;
36 size_t size() const { return points.size(); }
37 bool empty() const { return points.empty(); }
3638 double length() const;
37 bool is_valid() const { return this->points.size() >= 2; }
39 bool is_valid() const { return this->points.size() >= 2; }
3840
3941 int find_point(const Point &point) const;
4042 bool has_boundary_point(const Point &point) const;
102104 extern BoundingBox get_extents_rotated(const std::vector<Point> &points, double angle);
103105 extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
104106
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
105124 } // namespace Slic3r
106125
107126 #endif
149149 m_map.emplace(std::make_pair(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2), std::move(value)));
150150 }
151151
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
152170 // Return a pair of <ValueType*, distance_squared>
153171 std::pair<const ValueType*, double> find(const Point &pt) {
154172 // Iterate over 4 closest grid cells around pt,
176194 }
177195 }
178196 }
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)) ?
180198 std::make_pair(value_min, dist_min) :
181199 std::make_pair(nullptr, std::numeric_limits<double>::max());
182200 }
102102 p.rotate(cos_angle, sin_angle);
103103 }
104104
105 inline void polygons_reverse(Polygons &polys)
106 {
107 for (Polygon &p : polys)
108 p.reverse();
109 }
110
105111 inline Points to_points(const Polygon &poly)
106112 {
107113 return poly.points;
192192 }
193193 }
194194
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.)
201200 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;
207204 return true;
208205 }
209206
210 std::string
211 Polyline::wkt() const
207 std::string Polyline::wkt() const
212208 {
213209 std::ostringstream wkt;
214210 wkt << "LINESTRING((";
1818 Polyline() {};
1919 Polyline(const Polyline &other) : MultiPoint(other.points) {}
2020 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)) {}
2123 Polyline& operator=(const Polyline &other) { points = other.points; return *this; }
2224 Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; }
2325 static Polyline new_scale(std::vector<Pointf> points) {
7880
7981 inline double total_length(const Polylines &polylines) {
8082 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();
8385 return total;
8486 }
8587
361361 // Invalidate all print steps.
362362 this->invalidate_all_steps();
363363
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;
365368 // 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);
367370 // Find an existing print region with the same config.
368371 size_t region_id = size_t(-1);
369372 for (size_t i = 0; i < this->regions.size(); ++ i)
378381 }
379382 // Assign volume to a region.
380383 object->add_region_volume(region_id, volume_id);
384 ++ volume_id;
381385 }
382386
383387 // Apply config to print object.
852856 for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
853857 ModelVolume *volume = model_object->volumes[volume_id];
854858 //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"))
856860 volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1);
857861 }
858862 }
11921196 }
11931197 m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
11941198 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();
11951202 }
11961203
11971204 std::string Print::output_filename()
7979
8080 Print* print() { return this->_print; }
8181 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.
8283 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;
8386
8487 private:
8588 Print* _print;
210213
211214 bool is_printable() const { return !this->_shifted_copies.empty(); }
212215
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
213220 private:
214221 Print* _print;
215222 ModelObject* _model_object;
221228 ~PrintObject() {}
222229
223230 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;
224232 };
225233
226234 typedef std::vector<PrintObject*> PrintObjectPtrs;
239247 // TODO: status_cb
240248 std::string estimated_normal_print_time;
241249 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;
243251 std::map<size_t, float> filament_stats;
244252 PrintState<PrintStep, psCount> state;
245253
308316 std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_priming;
309317 std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes;
310318 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;
311321
312322 std::string output_filename();
313323 std::string output_filepath(const std::string &path);
00 #include "PrintConfig.hpp"
11 #include "I18N.hpp"
22
3 #include <algorithm>
34 #include <set>
45 #include <boost/algorithm/string/replace.hpp>
56 #include <boost/algorithm/string/case_conv.hpp>
122123 def->tooltip = L("Speed for printing bridges.");
123124 def->sidetext = L("mm/s");
124125 def->cli = "bridge-speed=f";
125 def->aliases.push_back("bridge_feed_rate");
126 def->aliases = { "bridge_feed_rate" };
126127 def->min = 0;
127128 def->default_value = new ConfigOptionFloat(60);
128129
235236 def->tooltip = L("Distance used for the auto-arrange feature of the plater.");
236237 def->sidetext = L("mm");
237238 def->cli = "duplicate-distance=f";
238 def->aliases.push_back("multiply_distance");
239 def->aliases = { "multiply_distance" };
239240 def->min = 0;
240241 def->default_value = new ConfigOptionFloat(6);
241242
296297 def->enum_labels.push_back(L("Archimedean Chords"));
297298 def->enum_labels.push_back(L("Octagram Spiral"));
298299 // 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" };
300301 def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
301302
302303 def = this->add("external_perimeter_extrusion_width", coFloatOrPercent);
884885 def->tooltip = L("Speed for printing the internal fill. Set to zero for auto.");
885886 def->sidetext = L("mm/s");
886887 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" };
889889 def->min = 0;
890890 def->default_value = new ConfigOptionFloat(80);
891891
12501250 def->category = L("Extruders");
12511251 def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1.");
12521252 def->cli = "perimeter-extruder=i";
1253 def->aliases.push_back("perimeters_extruder");
1253 def->aliases = { "perimeters_extruder" };
12541254 def->min = 1;
12551255 def->default_value = new ConfigOptionInt(1);
12561256
12631263 "If expressed as percentage (for example 200%) it will be computed over layer height.");
12641264 def->sidetext = L("mm or % (leave 0 for default)");
12651265 def->cli = "perimeter-extrusion-width=s";
1266 def->aliases.push_back("perimeters_extrusion_width");
1266 def->aliases = { "perimeters_extrusion_width" };
12671267 def->default_value = new ConfigOptionFloatOrPercent(0, false);
12681268
12691269 def = this->add("perimeter_speed", coFloat);
12721272 def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto.");
12731273 def->sidetext = L("mm/s");
12741274 def->cli = "perimeter-speed=f";
1275 def->aliases.push_back("perimeter_feed_rate");
1275 def->aliases = { "perimeter_feed_rate" };
12761276 def->min = 0;
12771277 def->default_value = new ConfigOptionFloat(60);
12781278
12851285 "if the Extra Perimeters option is enabled.");
12861286 def->sidetext = L("(minimum)");
12871287 def->cli = "perimeters=i";
1288 def->aliases.push_back("perimeter_offsets");
1288 def->aliases = { "perimeter_offsets" };
12891289 def->min = 0;
12901290 def->default_value = new ConfigOptionInt(3);
12911291
16131613 def->sidetext = L("mm/s or %");
16141614 def->cli = "solid-infill-speed=s";
16151615 def->ratio_over = "infill_speed";
1616 def->aliases.push_back("solid_infill_feed_rate");
1616 def->aliases = { "solid_infill_feed_rate" };
16171617 def->min = 0;
16181618 def->default_value = new ConfigOptionFloatOrPercent(20, false);
16191619
16951695 def->cli = "support-material!";
16961696 def->default_value = new ConfigOptionBool(false);
16971697
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
16981706 def = this->add("support_material_xy_spacing", coFloatOrPercent);
16991707 def->label = L("XY separation between an object and its support");
17001708 def->category = L("Support material");
17331741 "for the first object layer.");
17341742 def->sidetext = L("mm");
17351743 def->cli = "support-material-contact-distance=f";
1736 def->min = 0;
1744 // def->min = 0;
17371745 def->enum_values.push_back("0");
17381746 def->enum_values.push_back("0.2");
17391747 def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str());
19591967 def->tooltip = L("Speed for travel moves (jumps between distant extrusion points).");
19601968 def->sidetext = L("mm/s");
19611969 def->cli = "travel-speed=f";
1962 def->aliases.push_back("travel_feed_rate");
1970 def->aliases = { "travel_feed_rate" };
19631971 def->min = 1;
19641972 def->default_value = new ConfigOptionFloat(130);
19651973
22332241 return fpc.validate();
22342242 }
22352243
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
22362262 double PrintConfig::min_object_distance() const
22372263 {
22382264 return PrintConfig::min_object_distance(static_cast<const ConfigBase*>(this));
166166
167167 // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned.
168168 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);
169173
170174 // Verify whether the opt_key has not been obsoleted or renamed.
171175 // Both opt_key and value may be modified by handle_legacy().
322326 ConfigOptionFloatOrPercent extrusion_width;
323327 ConfigOptionFloatOrPercent first_layer_height;
324328 ConfigOptionBool infill_only_where_needed;
329 // Force the generation of solid shells between adjacent materials/volumes.
325330 ConfigOptionBool interface_shells;
326331 ConfigOptionFloat layer_height;
327332 ConfigOptionInt raft_layers;
329334 // ConfigOptionFloat seam_preferred_direction;
330335 // ConfigOptionFloat seam_preferred_direction_jitter;
331336 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).
332340 ConfigOptionFloat support_material_angle;
333341 ConfigOptionBool support_material_buildplate_only;
334342 ConfigOptionFloat support_material_contact_distance;
338346 ConfigOptionBool support_material_interface_contact_loops;
339347 ConfigOptionInt support_material_interface_extruder;
340348 ConfigOptionInt support_material_interface_layers;
349 // Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
341350 ConfigOptionFloat support_material_interface_spacing;
342351 ConfigOptionFloatOrPercent support_material_interface_speed;
343352 ConfigOptionEnum<SupportMaterialPattern> support_material_pattern;
353 // Spacing between support material lines (the hatching distance).
344354 ConfigOptionFloat support_material_spacing;
345355 ConfigOptionFloat support_material_speed;
346356 ConfigOptionBool support_material_synchronize_layers;
357 // Overhang angle threshold.
347358 ConfigOptionInt support_material_threshold;
348359 ConfigOptionBool support_material_with_sheath;
349360 ConfigOptionFloatOrPercent support_material_xy_spacing;
366377 // OPT_PTR(seam_preferred_direction);
367378 // OPT_PTR(seam_preferred_direction_jitter);
368379 OPT_PTR(support_material);
380 OPT_PTR(support_material_auto);
369381 OPT_PTR(support_material_angle);
370382 OPT_PTR(support_material_buildplate_only);
371383 OPT_PTR(support_material_contact_distance);
413425 ConfigOptionInt infill_every_layers;
414426 ConfigOptionFloatOrPercent infill_overlap;
415427 ConfigOptionFloat infill_speed;
428 // Detect bridging perimeters
416429 ConfigOptionBool overhangs;
417430 ConfigOptionInt perimeter_extruder;
418431 ConfigOptionFloatOrPercent perimeter_extrusion_width;
419432 ConfigOptionFloat perimeter_speed;
433 // Total number of perimeters.
420434 ConfigOptionInt perimeters;
421435 ConfigOptionFloatOrPercent small_perimeter_speed;
422436 ConfigOptionFloat solid_infill_below_area;
424438 ConfigOptionFloatOrPercent solid_infill_extrusion_width;
425439 ConfigOptionInt solid_infill_every_layers;
426440 ConfigOptionFloatOrPercent solid_infill_speed;
441 // Detect thin walls.
427442 ConfigOptionBool thin_walls;
428443 ConfigOptionFloatOrPercent top_infill_extrusion_width;
429444 ConfigOptionInt top_solid_layers;
171171 steps.emplace_back(posSlice);
172172 } else if (
173173 opt_key == "support_material"
174 || opt_key == "support_material_auto"
174175 || opt_key == "support_material_angle"
175176 || opt_key == "support_material_buildplate_only"
176177 || opt_key == "support_material_enforce_layers"
913914 #if 1
914915 // Intentionally inflate a bit more than how much the region has been shrunk,
915916 // 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);
917918 if (shell.empty())
918919 continue;
919920 #else
13191320
13201321 std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier)
13211322 {
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 {
13221362 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);
13451379 }
13461380 }
13471381 return layers;
5656 print_config.nozzle_diameter.get_at(this->config.solid_infill_extruder.value - 1)) / 3.;
5757 }
5858
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);
5962 }
63
64 }
223223 // 1) Initialize the SlicingAdaptive class with the object meshes.
224224 SlicingAdaptive as;
225225 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);
229229 as.prepare();
230230
231231 // 2) Generate layers using the algorithm of @platsch
247247 #ifdef SLIC3R_DEBUG
248248 static int iRun = 0;
249249 iRun ++;
250 for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it)
250 for (const MyLayer *layer : top_contacts)
251251 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));
254254 #endif /* SLIC3R_DEBUG */
255255
256256 BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts";
281281 MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers(
282282 object, bottom_contacts, top_contacts, layer_storage);
283283
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
285295
286296 BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers";
287297
419429 {
420430 // 1) Count the new polygons first.
421431 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 &region = *(*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)
427434 if (surface.surface_type == surface_type)
428435 n_polygons_new += surface.expolygon.holes.size() + 1;
429 }
430 }
431
432436 // 2) Collect the new polygons.
433437 Polygons out;
434438 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 &region = *(*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)
440441 if (surface.surface_type == surface_type)
441442 polygons_append(out, surface.expolygon);
442 }
443 }
444
445443 return out;
446444 }
447445
451449 {
452450 Polygons out;
453451 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);
456454 return out;
457455 }
458456
459457 class SupportGridPattern
460458 {
461459 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!
462462 SupportGridPattern(
463 // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy)
463464 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()
465468 coordf_t support_spacing,
466469 coordf_t support_angle) :
467470 m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons),
483486 bbox.align_to_grid(grid_resolution);
484487 m_grid.set_bbox(bbox);
485488 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
486501 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.
488504 m_island_samples = island_samples(*m_support_polygons);
489505 }
490506
492508 // and trim the extracted polygons by trimming_polygons.
493509 // Trimming by the trimming_polygons may split the extracted polygons into pieces.
494510 // 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)
496512 {
497513 // 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
501521
502522 // Extract polygons, which contain some of the m_island_samples.
503523 Polygons out;
504 std::vector<std::pair<Point,bool>> samples_inside;
505
506524 for (ExPolygon &island : islands) {
507525 BoundingBox bbox = get_extents(island.contour);
526 // Samples are sorted lexicographically.
508527 auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), bbox.min - Point(1, 1));
509528 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;
511530 for (auto it = it_lower; it != it_upper; ++ it)
512531 if (bbox.contains(*it))
513532 samples_inside.push_back(std::make_pair(*it, false));
548567 bbox.merge(get_extents(islands));
549568 if (!out.empty())
550569 bbox.merge(get_extents(out));
570 if (!support_polygons_simplified.empty())
571 bbox.merge(get_extents(support_polygons_simplified));
551572 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);
552574 svg.draw(islands, "red", 0.5f);
553575 svg.draw(union_ex(out), "green", 0.5f);
554576 svg.draw(union_ex(*m_support_polygons), "blue", 0.5f);
565587 return out;
566588 }
567589
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
568703 private:
704 SupportGridPattern() {}
569705 SupportGridPattern& operator=(const SupportGridPattern &rhs);
570706
707 #if 0
571708 // Get some internal point of an expolygon, to be used as a representative
572709 // sample to test, whether this island is inside another island.
710 //FIXME this was quick, but not sufficiently robust.
573711 static Point island_sample(const ExPolygon &expoly)
574712 {
575713 // Find the lowest point lexicographically.
590728 double coef = 20. / sqrt(l2);
591729 return Point(p2.x + coef * v.x, p2.y + coef * v.y);
592730 }
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.
594735 static Points island_samples(const ExPolygons &expolygons)
595736 {
596737 Points pts;
628769 coordf_t m_support_spacing;
629770
630771 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.
631774 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 */
632781 };
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 */
633958
634959 // Generate top contact layers supporting overhangs.
635960 // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
642967 ++ iRun;
643968 #endif /* SLIC3R_DEBUG */
644969
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
645974 // Output layers, sorted by top Z.
646975 MyLayersPtr contact_out;
647976
977 const bool support_auto = m_object_config->support_material_auto.value;
648978 // If user specified a custom angle threshold, convert it to radians.
649979 // Zero means automatic overhang detection.
650980 const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ?
6791009 // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers.
6801010 // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers.
6811011 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);
6831015 tbb::spin_mutex layer_storage_mutex;
6841016 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) {
6861019 for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id)
6871020 {
6881021 const Layer &layer = *object.layers[layer_id];
6931026 Polygons contact_polygons;
6941027 Polygons slices_margin_cached;
6951028 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;
6961032 if (layer_id == 0) {
6971033 // This is the first object layer, so the object is being printed on a raft and
6981034 // we're here just to get the object footprint for the raft.
7071043 // Extrusion width accounts for the roundings of the extrudates.
7081044 // It is the maximum widh of the extrudate.
7091045 float fw = float(layerm->flow(frExternalPerimeter).scaled_width());
1046 no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw);
7101047 float lower_layer_offset =
7111048 (layer_id < this->m_object_config->support_material_enforce_layers.value) ?
7121049 // Enforce a full possible support, ignore the overhang angle.
7191056 // Overhang polygons for this layer and region.
7201057 Polygons diff_polygons;
7211058 Polygons layerm_polygons = to_polygons(layerm->slices);
722 Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons);
7231059 if (lower_layer_offset == 0.f) {
7241060 // Support everything.
7251061 diff_polygons = diff(layerm_polygons, lower_layer_polygons);
7291065 diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]);
7301066 }
7311067 } 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 }
7421096 }
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]));
7491118 }
7501119 if (diff_polygons.empty())
7511120 continue;
7521121
753 #ifdef SLIC3R_DEBUG
1122 #ifdef SLIC3R_DEBUG
7541123 {
7551124 ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg",
7561125 iRun, layer_id,
7611130 }
7621131 #endif /* SLIC3R_DEBUG */
7631132
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);
8311136
8321137 if (diff_polygons.empty())
8331138 continue;
8411146 union_ex(diff_polygons, false));
8421147 #endif /* SLIC3R_DEBUG */
8431148
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.
8451152 polygons_append(overhang_polygons, diff_polygons);
8461153
8471154 // Let's define the required contact area by using a max gap of half the upper
8501157 // on the other side of the object (if it's very thin).
8511158 {
8521159 //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!
8531163 float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy)));
8541164 if (slices_margin_cached_offset != slices_margin_offset) {
8551165 slices_margin_cached_offset = slices_margin_offset;
8561166 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);
8591169 if (! buildplate_covered.empty()) {
8601170 // Trim the inflated contact surfaces by the top surfaces as well.
8611171 polygons_append(slices_margin_cached, buildplate_covered[layer_id]);
8781188 } // for each layer.region
8791189 } // end of Generate overhang/contact_polygons for non-raft layers.
8801190
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.
8821192 if (! contact_polygons.empty()) {
883 // get the average nozzle diameter used on this layer
8841193 MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact);
8851194 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) {
8871203 // 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;
8931224 } 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.
8981227 }
899 } else {
1228
9001229 // Contact layer will be printed with a normal flow, but
9011230 // 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 }
9331257 }
9341258 }
9351259 }
9361260
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!
9371263 SupportGridPattern support_grid_pattern(
9381264 // Support islands, to be stretched into a grid.
9391265 contact_polygons,
9401266 // Trimming polygons, to trim the stretched support islands.
9411267 slices_margin_cached,
942 // How much to offset the extracted contour outside of the grid.
1268 // Grid resolution.
9431269 m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
9441270 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 */
9491326
9501327 // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded.
9511328 // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons.
9521329
9531330 // Store the overhang polygons.
9541331 // 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.
9561333 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 }
9581341 }
9591342 }
9601343 });
1344
9611345 // Compress contact_out, remove the nullptr items.
9621346 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
9631405 BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end";
9641406
9651407 return contact_out;
9951437 BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id;
9961438 const Layer &layer = *object.get_layer(layer_id);
9971439 // 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) {
9991441 Polygons polygons_new;
10001442 // Contact surfaces are expanded away from the object, trimmed by the object.
10011443 // Use a slight positive offset to overlap the touching regions.
10031445 // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form.
10041446 polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON));
10051447 #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.
10071450 polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons));
10081451 #endif
10091452 // These are the overhang surfaces. They are touching the object and they are not expanded away from the object.
10151458 continue;
10161459 Polygons projection_raw = union_(projection);
10171460
1018 // Top surfaces of this layer, to be used to stop the surface volume from growing down.
10191461 tbb::task_group task_group;
10201462 if (! m_object_config->support_material_buildplate_only)
1463 // Find the bottom contact layers above the top surfaces of this layer.
10211464 task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] {
10221465 Polygons top = collect_region_slices_by_type(layer, stTop);
10231466 #ifdef SLIC3R_DEBUG
10451488 // Grow top surfaces so that interface and support generation are generated
10461489 // with some spacing from object - it looks we don't need the actual
10471490 // 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.
10481493 layer_new.height = m_slicing_params.soluble_interface ?
10491494 // Align the interface layer with the object's layer height.
10501495 object.layers[layer_id + 1]->height :
10511496 // 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?
10521500 m_support_material_interface_flow.nozzle_diameter;
10531501 layer_new.print_z = m_slicing_params.soluble_interface ? object.layers[layer_id + 1]->print_z :
10541502 layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value;
10551503 layer_new.bottom_z = layer.print_z;
10561504 layer_new.idx_object_layer_below = layer_id;
10571505 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.
10591508 layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
10601509 if (! m_slicing_params.soluble_interface) {
10611510 // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
10621511 // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min.
10631512 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;
10651514 ++ 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) {
10671516 // A top layer has been found, which is close to the new bottom layer.
10681517 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);
10701519 if (diff > 0.) {
10711520 // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer.
10721521 assert(diff < layer_new.height + EPSILON);
10901539 union_ex(layer_new.polygons, false));
10911540 #endif /* SLIC3R_DEBUG */
10921541 // 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?
10931543 touching = offset(touching, float(SCALED_EPSILON));
10941544 for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
10951545 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)
10971547 break;
10981548 if (! layer_support_areas[layer_id_above].empty()) {
10991549 #ifdef SLIC3R_DEBUG
11461596 projection,
11471597 // Trimming polygons, to trim the stretched support islands.
11481598 trimming,
1149 // How much to offset the extracted contour outside of the grid.
1599 // Grid spacing.
11501600 m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
11511601 Geometry::deg2rad(m_object_config->support_material_angle.value));
11521602 tbb::task_group task_group_inner;
11571607 , &layer
11581608 #endif /* SLIC3R_DEBUG */
11591609 ] {
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);
11611611 #ifdef SLIC3R_DEBUG
11621612 Slic3r::SVG::export_expolygons(
11631613 debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z),
11711621 , &layer
11721622 #endif /* SLIC3R_DEBUG */
11731623 ] {
1174 projection_new = support_grid_pattern.extract_support(-5);
1624 projection_new = support_grid_pattern.extract_support(-5, true);
11751625 #ifdef SLIC3R_DEBUG
11761626 Slic3r::SVG::export_expolygons(
11771627 debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z),
11841634 task_group.wait();
11851635 }
11861636 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
11881642 } // ! top_contacts.empty()
11891643
11901644 return bottom_contacts;
15011955 assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z);
15021956
15031957 // 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
15071958 // New polygons for layer_intermediate.
15081959 Polygons polygons_new;
15091960
15221973 // 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.
15231974 // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen.
15241975 // 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; });
15291978 // 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) {
15311980 MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping];
15321981 if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON)
15331982 break;
16072056 ++ iRun;
16082057 #endif /* SLIC3R_DEBUG */
16092058
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);
16112063 }
16122064
16132065 void PrintObjectSupportMaterial::trim_support_layers_by_object(
16522104 const Layer &object_layer = *object.layers[i];
16532105 if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
16542106 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));
16562108 }
16572109 if (! this->m_slicing_params.soluble_interface) {
16582110 // Collect all bottom surfaces, which will be extruded with a bridging flow.
16592111 for (; i < object.layers.size(); ++ i) {
16602112 const Layer &object_layer = *object.layers[i];
16612113 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)
16652117 break;
16662118 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);
16682124 }
16692125 if (! some_region_overlaps)
16702126 break;
16742130 // perimeter's width. $support contains the full shape of support
16752131 // material, thus including the width of its foremost extrusion.
16762132 // 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);
16802134 }
16812135 });
16822136 BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end";
17992253 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;
18002254 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;
18012255 // 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
18032257 // Collect the top contact areas above this intermediate layer, below top_z.
18042258 Polygons polygons_top_contact_projected;
18052259 for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) {
18062260 const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
2261 //FIXME maybe this adds one interface layer in excess?
18072262 if (top_contact_layer.bottom_z - EPSILON > top_z)
18082263 break;
18092264 polygons_append(polygons_top_contact_projected, top_contact_layer.polygons);
18602315 fill_params.density = density;
18612316 fill_params.complete = true;
18622317 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);
18652320 extrusion_entities_append_paths(
18662321 dst,
18672322 filler->fill_surface(&surface, fill_params),
18822337 fill_params.density = density;
18832338 fill_params.complete = true;
18842339 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));
18872342 extrusion_entities_append_paths(
18882343 dst,
18892344 filler->fill_surface(&surface, fill_params),
23582813 (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back());
23592814 }
23602815 private:
2361 ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&);
2816 ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {}
23622817 const std::vector<ExtrusionPathFragment> &m_path_fragments;
23632818 };
23642819 const coord_t search_radius = 7;
27103165 continue;
27113166 //FIXME When paralellizing, each thread shall have its own copy of the fillers.
27123167 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)
27133170 Flow interface_flow(
27143171 float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)),
27153172 float(layer_ex.layer->height),
1111 class PrintObjectConfig;
1212
1313 // how much we extend support around the actual contact area
14 //FIXME this should be dependent on the nozzle diameter!
1415 #define SUPPORT_MATERIAL_MARGIN 1.5
1516
1617 // This class manages raft and supports for a single PrintObject.
7071 overhang_polygons = nullptr;
7172 }
7273
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
7389 bool operator==(const MyLayer &layer2) const {
7490 return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
7591 }
3636
3737 void clear() { surfaces.clear(); }
3838 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 }
3944
4045 void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; }
4146 void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); }
00 #include "TriangleMesh.hpp"
11 #include "ClipperUtils.hpp"
22 #include "Geometry.hpp"
3 #include "MultiPoint.hpp"
34 #include "qhull/src/libqhullcpp/Qhull.h"
45 #include "qhull/src/libqhullcpp/QhullFacetList.h"
56 #include "qhull/src/libqhullcpp/QhullVertexSet.h"
2021
2122 #include <Eigen/Dense>
2223
24 // for SLIC3R_DEBUG_SLICE_PROCESSING
25 #include "libslic3r.h"
26
2327 #if 0
2428 #define DEBUG
2529 #define _DEBUG
2630 #undef NDEBUG
31 #define SLIC3R_DEBUG
32 // #define SLIC3R_TRIANGLEMESH_DEBUG
2733 #endif
2834
2935 #include <assert.h>
3036
31 #ifdef SLIC3R_DEBUG
32 // #define SLIC3R_TRIANGLEMESH_DEBUG
37 #if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
3338 #include "SVG.hpp"
3439 #endif
3540
202207 }
203208
204209 // 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.
205213 if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
206214 stl_fill_holes(&stl);
207215 stl_clear_error(&stl);
208216 }
217 #endif
209218
210219 // normal_directions
211220 stl_fix_normal_directions(&stl);
223232
224233 BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
225234 }
226
227235
228236 float TriangleMesh::volume()
229237 {
439447 facet_visited[facet_idx] = true;
440448 for (int j = 0; j < 3; ++ j) {
441449 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])
443451 facet_queue[facet_queue_cnt ++] = neighbor_idx;
444452 }
445453 }
482490 facet_visited[facet_idx] = true;
483491 for (int j = 0; j < 3; ++ j) {
484492 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])
486494 facet_queue[facet_queue_cnt ++] = neighbor_idx;
487495 }
488496 }
491499 return num_bodies;
492500 }
493501
494 TriangleMeshPtrs
495 TriangleMesh::split() const
502 TriangleMeshPtrs TriangleMesh::split() const
496503 {
497504 TriangleMeshPtrs meshes;
498505 std::set<int> seen_facets;
544551 return meshes;
545552 }
546553
547 void
548 TriangleMesh::merge(const TriangleMesh &mesh)
554 void TriangleMesh::merge(const TriangleMesh &mesh)
549555 {
550556 // reset stats and metadata
551557 int number_of_facets = this->stl.stats.number_of_facets;
599605 return Slic3r::Geometry::convex_hull(pp);
600606 }
601607
602 BoundingBoxf3
603 TriangleMesh::bounding_box() const
608 BoundingBoxf3 TriangleMesh::bounding_box() const
604609 {
605610 BoundingBoxf3 bb;
606611 bb.defined = true;
747752 return stl.facet_start ? &stl.facet_start->vertex[0].x : nullptr;
748753 }
749754
750 void
751 TriangleMesh::require_shared_vertices()
755 void TriangleMesh::require_shared_vertices()
752756 {
753757 BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start";
754758 if (!this->repaired)
757761 BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices";
758762 stl_generate_shared_vertices(&(this->stl));
759763 }
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 */
760778 BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
761779 }
762
763780
764781 TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
765782 mesh(_mesh)
846863 }
847864 }
848865
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
851867 {
852868 BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice";
853869
909925 {
910926 static int iRun = 0;
911927 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.
914946 for (Polygon &poly : polygons) {
915947 for (size_t i = 1; i < poly.points.size(); ++ i)
916948 assert(poly.points[i-1] != poly.points[i]);
917949 assert(poly.points.front() != poly.points.back());
918950 }
951 #endif
919952 }
920953 ++ iRun;
921954 }
931964 const float min_z = fminf(facet.vertex[0].z, fminf(facet.vertex[1].z, facet.vertex[2].z));
932965 const float max_z = fmaxf(facet.vertex[0].z, fmaxf(facet.vertex[1].z, facet.vertex[2].z));
933966
934 #ifdef SLIC3R_DEBUG
967 #ifdef SLIC3R_TRIANGLEMESH_DEBUG
935968 printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx,
936969 facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z,
937970 facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z,
938971 facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z);
939972 printf("z: min = %.2f, max = %.2f\n", min_z, max_z);
940 #endif
973 #endif /* SLIC3R_TRIANGLEMESH_DEBUG */
941974
942975 // find layer extents
943976 std::vector<float>::const_iterator min_layer, max_layer;
944977 min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z
945978 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
947980 printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin()));
948 #endif
981 #endif /* SLIC3R_TRIANGLEMESH_DEBUG */
949982
950983 for (std::vector<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) {
951984 std::vector<float>::size_type layer_idx = it - z.begin();
952985 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) {
954987 boost::lock_guard<boost::mutex> l(*lines_mutex);
955988 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.
974990 } 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
982997 {
983998 std::vector<Polygons> layers_p;
984999 this->slice(z, &layers_p);
9991014 }
10001015
10011016 // 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(
10031018 float slice_z, const stl_facet &facet, const int facet_idx,
10041019 const float min_z, const float max_z,
10051020 IntersectionLine *line_out) const
10061021 {
10071022 IntersectionPoint points[3];
10081023 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);
10111025
10121026 // Reorder vertices so that the first one is the one with lowest Z.
10131027 // This is needed to get all intersection lines in a consistent order
10141028 // (external on the right of the line)
1029 const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
10151030 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
10171032 int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)];
1018 const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
10191033 int a_id = vertices[j % 3];
10201034 int b_id = vertices[(j+1) % 3];
10211035 const stl_vertex *a = &this->v_scaled_shared[a_id];
10271041 const stl_vertex &v0 = this->v_scaled_shared[vertices[0]];
10281042 const stl_vertex &v1 = this->v_scaled_shared[vertices[1]];
10291043 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];
10301048 if (min_z == max_z) {
10311049 // All three vertices are aligned with slice_z.
10321050 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) {
10341053 // If normal points downwards this is a bottom horizontal facet so we reverse its point order.
10351054 std::swap(a, b);
10361055 std::swap(a_id, b_id);
10371056 }
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);
10431057 } 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;
10461074 }
10471075 line_out->a.x = a->x;
10481076 line_out->a.y = a->y;
10501078 line_out->b.y = b->y;
10511079 line_out->a_id = a_id;
10521080 line_out->b_id = b_id;
1053 return true;
1081 assert(line_out->a != line_out->b);
1082 return result;
10541083 }
10551084
10561085 if (a->z == slice_z) {
10571086 // 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 }
10631094 } else if (b->z == slice_z) {
10641095 // 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 }
10701103 } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) {
10711104 // 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);
10971138 if (num_points == 2) {
1098 line_out->edge_type = feNone;
1139 line_out->edge_type = feGeneral;
10991140 line_out->a = (Point)points[1];
11001141 line_out->b = (Point)points[0];
11011142 line_out->a_id = points[1].point_id;
11021143 line_out->b_id = points[0].point_id;
11031144 line_out->edge_a_id = points[1].edge_id;
11041145 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;
11321307 break;
11331308 }
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;
11391319 break;
11401320 }
1141 }
11421321 }
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; }
11571382 };
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);
11791408 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)
13501451 // The closed polygon is patched from pieces with messed up orientation, therefore
13511452 // the orientation of the patched up polygon is not known.
13521453 // 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));
13621456 }
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 */
13671659 }
13681660
13691661 // Only used to cut the mesh into two halves.
15431835
15441836 // intersect facet with cutting plane
15451837 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) {
15471839 // Save intersection lines for generating correct triangulations.
15481840 if (line.edge_type == feTop) {
15491841 lower_lines.push_back(line);
8181
8282 enum FacetEdgeType {
8383 // A general case, the cutting plane intersect a face at two different edges.
84 feNone,
84 feGeneral,
8585 // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
8686 feTop,
8787 // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
115115 class IntersectionLine : public Line
116116 {
117117 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
118126 // Inherits Point a, b
119127 // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1.
120128 // Vertex indices of the line end points.
123131 // Source mesh edges of the line end points.
124132 int edge_a_id;
125133 int edge_b_id;
126 // feNone, feTop, feBottom, feHorizontal
134 // feGeneral, feTop, feBottom, feHorizontal
127135 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;
131151 };
132152 typedef std::vector<IntersectionLine> IntersectionLines;
133153 typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
138158 TriangleMeshSlicer(TriangleMesh* _mesh);
139159 void slice(const std::vector<float> &z, std::vector<Polygons>* layers) const;
140160 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,
142167 const float min_z, const float max_z, IntersectionLine *line_out) const;
143168 void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const;
144169
88
99 extern void set_logging_level(unsigned int level);
1010 extern void trace(unsigned int level, const char *message);
11 extern void disable_multi_threading();
1112
1213 // Set a path with GUI resource files.
1314 void set_var_dir(const std::string &path);
4142 extern local_encoded_string encode_path(const char *src);
4243 extern std::string decode_path(const char *src);
4344 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);
4450
4551 // File path / name / extension splitting utilities, working with UTF-8,
4652 // to be published to Perl.
1313 #include <boost/thread.hpp>
1414
1515 #define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
16 #define SLIC3R_VERSION "1.41.0"
16 #define SLIC3R_VERSION "1.41.2-beta"
1717 #define SLIC3R_BUILD "UNKNOWN"
1818
1919 typedef int32_t coord_t;
2222 #include <boost/nowide/fstream.hpp>
2323 #include <boost/nowide/integration/filesystem.hpp>
2424 #include <boost/nowide/convert.hpp>
25 #include <boost/nowide/cstdio.hpp>
26
27 #include <tbb/task_scheduler_init.h>
2528
2629 namespace Slic3r {
2730
8184 (::boost::log::keywords::severity = severity)) << message;
8285 }
8386
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
8495 static std::string g_var_dir;
8596
8697 void set_var_dir(const std::string &dir)
136147 const std::string& data_dir()
137148 {
138149 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;
139212 }
140213
141214 } // namespace Slic3r
4242 PresetBundle* get_preset_bundle();
4343 }
4444
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
5445 AppControllerBoilerplate::ProgresIndicatorPtr
5546 AppControllerBoilerplate::global_progress_indicator() {
5647 ProgresIndicatorPtr ret;
7061 pri_data_->m.unlock();
7162 }
7263
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, ...) {
26066 std::stringstream ss;
26167 va_list args;
26268 va_start(args, fmtstr);
320126 for(auto& v : bedpoints)
321127 bed.append(Point::new_scale(v.x, v.y));
322128
323 if(pind) pind->update(0, _(L("Arranging objects...")));
129 if(pind) pind->update(0, L("Arranging objects..."));
324130
325131 try {
132 arr::BedShapeHint hint;
133 // TODO: from Sasha from GUI
134 hint.type = arr::BedShapeType::WHO_KNOWS;
135
326136 arr::arrange(*model_,
327137 min_obj_distance,
328138 bed,
329 arr::BOX,
139 hint,
330140 false, // create many piles not just one pile
331141 [pind, count](unsigned rem) {
332142 if(pind)
333 pind->update(count - rem, _(L("Arranging objects...")));
143 pind->update(count - rem, L("Arranging objects..."));
334144 });
335145 } catch(std::exception& e) {
336146 std::cerr << e.what() << std::endl;
337147 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"));
341151 }
342152
343153 // Restore previous max value
344154 if(pind) {
345155 pind->max(pmax);
346 pind->update(0, _(L("Arranging done.")));
156 pind->update(0, L("Arranging done."));
347157 }
348158 });
349159
66 #include <atomic>
77 #include <iostream>
88
9 #include "IProgressIndicator.hpp"
9 #include "ProgressIndicator.hpp"
1010
1111 namespace Slic3r {
1212
1414 class Print;
1515 class PrintObject;
1616 class PrintConfig;
17
17 class ProgressStatusBar;
18 class DynamicPrintConfig;
1819
1920 /**
2021 * @brief A boilerplate class for creating application logic. It should provide
3233 public:
3334
3435 /// A Progress indicator object smart pointer
35 using ProgresIndicatorPtr = std::shared_ptr<IProgressIndicator>;
36 using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>;
3637
3738 private:
3839 class PriData; // Some structure to store progress indication data
4546 AppControllerBoilerplate();
4647 ~AppControllerBoilerplate();
4748
48 using Path = string;
49 using Path = std::string;
4950 using PathList = std::vector<Path>;
5051
5152 /// Common runtime issue types
6667 * @return Returns a list of paths choosed by the user.
6768 */
6869 PathList query_destination_paths(
69 const string& title,
70 const std::string& title,
7071 const std::string& extensions) const;
7172
7273 /**
7374 * @brief Same as query_destination_paths but works for directories only.
7475 */
7576 PathList query_destination_dirs(
76 const string& title) const;
77 const std::string& title) const;
7778
7879 /**
7980 * @brief Same as query_destination_paths but returns only one path.
8081 */
8182 Path query_destination_path(
82 const string& title,
83 const std::string& title,
8384 const std::string& extensions,
8485 const std::string& hint = "") const;
8586
9495 * title.
9596 */
9697 bool report_issue(IssueType issuetype,
97 const string& description,
98 const string& brief);
98 const std::string& description,
99 const std::string& brief);
99100
100101 bool report_issue(IssueType issuetype,
101 const string& description);
102 const std::string& description);
102103
103104 /**
104105 * @brief Return the global progress indicator for the current controller.
149150 */
150151 ProgresIndicatorPtr create_progress_indicator(
151152 unsigned statenum,
152 const string& title,
153 const string& firstmsg) const;
153 const std::string& title,
154 const std::string& firstmsg) const;
154155
155156 ProgresIndicatorPtr create_progress_indicator(
156157 unsigned statenum,
157 const string& title) const;
158 const std::string& title) const;
158159
159160 // This is a global progress indicator placeholder. In the Slic3r UI it can
160161 // contain the progress indicator on the statusbar.
166167 */
167168 class PrintController: public AppControllerBoilerplate {
168169 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
187170 public:
188171
189172 // Must be public for perl to use it
197180 inline static Ptr create(Print *print) {
198181 return PrintController::Ptr( new PrintController(print) );
199182 }
200
201 /**
202 * @brief Slice the loaded print scene.
203 */
204 void slice();
205183
206184 const PrintConfig& config() const;
207185 };
247225 * In perl we have a progress indicating status bar on the bottom of the
248226 * window which is defined and created in perl. We can pass the ID-s of the
249227 * 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++.
251229 *
252230 * This function should be called from perl.
253231 *
3131
3232 AppControllerBoilerplate::PathList
3333 AppControllerBoilerplate::query_destination_paths(
34 const string &title,
34 const std::string &title,
3535 const std::string &extensions) const
3636 {
3737
38 wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
38 wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
3939 dlg.SetWildcard(extensions);
4040
4141 dlg.ShowModal();
5151
5252 AppControllerBoilerplate::Path
5353 AppControllerBoilerplate::query_destination_path(
54 const string &title,
54 const std::string &title,
5555 const std::string &extensions,
5656 const std::string& hint) const
5757 {
58 wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
58 wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
5959 dlg.SetWildcard(extensions);
6060
6161 dlg.SetFilename(hint);
7070 }
7171
7272 bool AppControllerBoilerplate::report_issue(IssueType issuetype,
73 const string &description,
74 const string &brief)
73 const std::string &description,
74 const std::string &brief)
7575 {
7676 auto icon = wxICON_INFORMATION;
7777 auto style = wxOK|wxCENTRE;
8383 case IssueType::FATAL: icon = wxICON_ERROR;
8484 }
8585
86 auto ret = wxMessageBox(description, brief, icon | style);
86 auto ret = wxMessageBox(_(description), _(brief), icon | style);
8787 return ret != wxCANCEL;
8888 }
8989
9090 bool AppControllerBoilerplate::report_issue(
9191 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());
9595 }
9696
9797 wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
103103 * the main thread as well.
104104 */
105105 class GuiProgressIndicator:
106 public IProgressIndicator, public wxEvtHandler {
106 public ProgressIndicator, public wxEvtHandler {
107107
108108 wxProgressDialog gauge_;
109 using Base = IProgressIndicator;
109 using Base = ProgressIndicator;
110110 wxString message_;
111111 int range_; wxString title_;
112112 bool is_asynch_ = false;
135135 /// Get the mode of parallel operation.
136136 inline bool asynch() const { return is_asynch_; }
137137
138 inline GuiProgressIndicator(int range, const string& title,
139 const string& firstmsg) :
138 inline GuiProgressIndicator(int range, const wxString& title,
139 const wxString& firstmsg) :
140140 gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(),
141141 wxPD_APP_MODAL | wxPD_AUTO_HIDE),
142142 message_(firstmsg),
148148 Bind(PROGRESS_STATUS_UPDATE_EVENT,
149149 &GuiProgressIndicator::_state,
150150 this, id_);
151 }
152
153 virtual void cancel() override {
154 update(max(), "Abort");
155 IProgressIndicator::cancel();
156151 }
157152
158153 virtual void state(float val) override {
169164 } else _state(st);
170165 }
171166
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, ...) {
177172 va_list arglist;
178173 va_start(arglist, fmt);
179 message_ = wxString::Format(wxString(fmt), arglist);
174 message_ = wxString::Format(_(fmt), arglist);
180175 va_end(arglist);
181176 }
182177
183 virtual void title(const string & title) override {
184 title_ = title;
178 virtual void title(const std::string & title) override {
179 title_ = _(title);
185180 }
186181 };
187182 }
188183
189184 AppControllerBoilerplate::ProgresIndicatorPtr
190185 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
192189 {
193190 auto pri =
194191 std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg);
201198 }
202199
203200 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());
208205 }
209206
210207 namespace {
211208
212209 // 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 {
214211 wxGauge *gauge_;
215212 wxStatusBar *stbar_;
216 using Base = IProgressIndicator;
217 std::string message_;
213 using Base = ProgressIndicator;
214 wxString message_;
218215 AppControllerBoilerplate& ctl_;
219216
220217 void showProgress(bool show = true) {
222219 }
223220
224221 void _state(unsigned st) {
225 if( st <= IProgressIndicator::max() ) {
222 if( st <= ProgressIndicator::max() ) {
226223 Base::state(st);
227224
228225 if(!gauge_->IsShown()) showProgress(true);
265262 virtual void max(float val) override {
266263 if(val > 1.0) {
267264 gauge_->SetRange(static_cast<int>(val));
268 IProgressIndicator::max(val);
265 ProgressIndicator::max(val);
269266 }
270267 }
271268
279276 }
280277 }
281278
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 {
287284 va_list arglist;
288285 va_start(arglist, fmt);
289 message_ = wxString::Format(fmt, arglist);
286 message_ = wxString::Format(_(fmt), arglist);
290287 va_end(arglist);
291288 }
292289
293 virtual void title(const string & /*title*/) override {}
290 virtual void title(const std::string & /*title*/) override {}
294291
295292 };
296293 }
621621 const ModelVolume *model_volume = model_object->volumes[volume_idx];
622622
623623 int extruder_id = -1;
624 if (!model_volume->modifier)
624 if (model_volume->is_model_part())
625625 {
626626 extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0;
627627 if (extruder_id == 0)
634634 volumes_idx.push_back(int(this->volumes.size()));
635635 float color[4];
636636 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;
638647 this->volumes.emplace_back(new GLVolume(color));
639648 GLVolume &v = *this->volumes.back();
640649 if (use_VBOs)
657666 else if (drag_by == "instance")
658667 v.drag_group_id = obj_idx * 1000 + instance_idx;
659668
660 if (!model_volume->modifier)
669 if (model_volume->is_model_part())
661670 {
662671 v.set_convex_hull(model_volume->get_convex_hull());
663672 v.layer_height_texture = layer_height_texture;
664673 if (extruder_id != -1)
665674 v.extruder_id = extruder_id;
666675 }
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();
669678 v.set_origin(Pointf3(instance->offset.x, instance->offset.y, 0.0));
670679 v.set_angle_z(instance->rotation);
671680 v.set_scale_factor(instance->scaling_factor);
11811190 b1_prev = b1;
11821191 v_prev = v;
11831192
1184 if (bottom_z_different)
1193 if (bottom_z_different && (closed || (!is_first && !is_last)))
11851194 {
11861195 // Found a change of the layer thickness -> Add a cap at the beginning of this segment.
11871196 volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]);
11891198
11901199 if (! closed) {
11911200 // Terminate open paths with caps.
1192 if (is_first && !bottom_z_different)
1201 if (is_first)
11931202 volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]);
11941203 // 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)
11961205 volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]);
11971206 }
11981207
1515 #include <boost/property_tree/ini_parser.hpp>
1616 #include <boost/property_tree/ptree.hpp>
1717 #include <boost/algorithm/string/predicate.hpp>
18 #include <boost/format.hpp>
19
1820
1921 namespace Slic3r {
2022
5961
6062 if (get("remember_output_path").empty())
6163 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");
6272 }
6373
6474 void AppConfig::load()
116126
117127 void AppConfig::save()
118128 {
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
119135 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);
121137 c << "# " << Slic3r::header_slic3r_generated() << std::endl;
122138 // Make sure the "no" category is written first.
123139 for (const std::pair<std::string, std::string> &kvp : m_storage[""])
146162 }
147163 }
148164 c.close();
165
166 rename_file(path_pid, path);
167
149168 m_dirty = false;
150169 }
151170
7171 bool has(const std::string &key) const
7272 { return this->has("", key); }
7373
74 void erase(const std::string &section, 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
7482 void clear_section(const std::string &section)
7583 { m_storage[section].clear(); }
7684
408408
409409 void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
410410 {
411 ConfigOptionEnum<GCodeFlavor> opt;
412
413411 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);
416415 }
417416 }
418417
870869 // If the screen is smaller, resize wizrad to match, which will enable scrollbars.
871870 auto wizard_size = GetSize();
872871 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 }
877877 Fit();
878878
879879 p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });
223223 }), temp->GetId());
224224 #endif // __WXGTK__
225225
226 temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent)
226 temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt)
227227 {
228228 #ifdef __WXGTK__
229 bChangedValueEvent = true;
230 #else
229 if (bChangedValueEvent)
230 #endif //__WXGTK__
231231 on_change_field();
232 #endif //__WXGTK__
233232 }), temp->GetId());
234233
235234 #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);
244240 #endif //__WXGTK__
245241
246242 // select all text using Ctrl+A
265261
266262 void TextCtrl::enable() { dynamic_cast<wxTextCtrl*>(window)->Enable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(true); }
267263 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__
268273
269274 void CheckBox::BUILD() {
270275 auto size = wxSize(wxDefaultSize);
221221 class TextCtrl : public Field {
222222 using Field::Field;
223223 #ifdef __WXGTK__
224 bool bChangedValueEvent = false;
224 bool bChangedValueEvent = true;
225 void change_field_value(wxEvent& event);
225226 #endif //__WXGTK__
226227 public:
227228 TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
366366
367367 auto ports = Utils::scan_serial_ports_extended();
368368 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;
370370 }), ports.end());
371371
372372 if (ports.size() == 1) {
389389
390390 void FirmwareDialog::priv::lookup_port_mmu()
391391 {
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
392396 BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
393397
394398 auto ports = Utils::scan_serial_ports_extended();
395399 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 ||
397401 port.id_product != USB_PID_MMU_BOOT &&
398402 port.id_product != USB_PID_MMU_APP;
399403 }), ports.end());
400404
401405 if (ports.size() == 0) {
402406 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)));
409408 wait_for_mmu_bootloader(30);
410409 } else if (ports.size() > 1) {
411410 BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
416415 BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
417416 mmu_reboot(ports[0]);
418417 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 }
419425 } else {
420426 port = ports[0];
421427 }
701707 panel->SetSizer(vsizer);
702708
703709 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|*.*");
705712
706713 auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
707714 p->port_picker = new wxComboBox(panel, wxID_ANY);
34353435 {
34363436 const Size& cnv_size = get_canvas_size();
34373437 _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 }
34403446 }
34413447 }
34423448
66 #include <boost/lexical_cast.hpp>
77 #include <boost/algorithm/string.hpp>
88 #include <boost/format.hpp>
9 #include <boost/lexical_cast.hpp>
910
1011 #if __APPLE__
1112 #import <IOKit/pwr_mgt/IOPMLib.h>
825826 double brim_width = config->opt_float("brim_width");
826827 if (boost::any_cast<bool>(value) == true)
827828 {
828 new_val = m_brim_width == 0.0 ? 10 :
829 new_val = m_brim_width == 0.0 ? 5 :
829830 m_brim_width < 0.0 ? m_brim_width * (-1) :
830831 m_brim_width;
831832 }
973974
974975 }
975976
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);
979985 const auto disp_size = display.GetClientArea();
980986 width = disp_size.GetWidth();
981987 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
9831036
9841037 void about()
9851038 {
2323 class wxFlexGridSizer;
2424 class wxButton;
2525 class wxFileDialog;
26 class wxTopLevelWindow;
2627
2728 namespace Slic3r {
2829
181182 int get_export_option(wxFileDialog* dlg);
182183
183184 // 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);
185191
186192 // Display an About dialog
187193 extern void about();
291291 "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
292292 "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
293293 "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",
295295 "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
296296 "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
297297 "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",
416416 try {
417417 Preset preset(m_type, name, false);
418418 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);
420427 m_presets.emplace_back(preset);
421428 } catch (const std::runtime_error &err) {
422429 errors_cummulative += err.what();
918918 DynamicPrintConfig config(default_config);
919919 for (auto &kvp : section.second)
920920 config.set_deserialize(kvp.first, kvp.second.data());
921 Preset::normalize(config);
922921 // Report configuration fields, which are misplaced into a wrong group.
923922 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)
937924 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);
939927 if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) {
940928 // Filter out printer presets, which are not mentioned in the vendor profile.
941929 // These presets are considered not installed.
846846 page = add_options_page(_(L("Support material")), "building.png");
847847 optgroup = page->new_optgroup(_(L("Support material")));
848848 optgroup->append_single_option_line("support_material");
849 optgroup->append_single_option_line("support_material_auto");
849850 optgroup->append_single_option_line("support_material_threshold");
850851 optgroup->append_single_option_line("support_material_enforce_layers");
851852
11821183
11831184 bool have_raft = m_config->opt_int("raft_layers") > 0;
11841185 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");
11851187 bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0;
11861188 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",
11881190 "support_material_spacing", "support_material_angle", "support_material_interface_layers",
11891191 "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance",
11901192 "support_material_xy_spacing" })
11911193 get_field(el)->toggle(have_support_material);
1194 get_field("support_material_threshold")->toggle(have_support_material_auto);
11921195
11931196 for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder",
11941197 "support_material_interface_speed", "support_material_interface_contact_loops" })
+0
-71
xs/src/slic3r/IProgressIndicator.hpp less more
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
-10
xs/src/slic3r/Strings.hpp less more
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
196196 {
197197 return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
198198 % get_base_url()
199 % filename
199 % Http::url_encode(filename)
200200 % timestamp_str()).str();
201201 }
202202
229229 auto tm = *std::localtime(&t);
230230
231231 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);
233233
234234 return std::string(buffer);
235235 }
247247 bool Duet::start_print(wxString &msg, const std::string &filename) const
248248 {
249249 bool res = false;
250
250251 auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"")
251252 % get_base_url()
252 % filename).str();
253 % Http::url_encode(filename)).str();
253254
254255 auto http = Http::get(std::move(url));
255256 http.on_error([&](std::string body, std::string error, unsigned status) {
274275 return root.get<int>("err", 0);
275276 }
276277
277
278 }
278 }
420420 return res;
421421 }
422422
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
423438 std::ostream& operator<<(std::ostream &os, const Http::Progress &progress)
424439 {
425440 os << "Http::Progress("
9797
9898 // Tells whether current backend supports seting up a CA file using ca_file()
9999 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);
100103 private:
101104 Http(const std::string &url);
102105
230230 spi.port = path;
231231 #ifdef __linux__
232232 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 }
234239 auto vid = sysfs_tty_prop_hex(name, "idVendor");
235240 auto pid = sysfs_tty_prop_hex(name, "idProduct");
236241 if (vid && pid) {
7878 my $m = Slic3r::TriangleMesh->new;
7979 $m->ReadFromPerl($cube->{vertices}, $cube->{facets});
8080 $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);
8284 my $result = $m->slice(\@z);
8385 my $SCALING_FACTOR = 0.000001;
8486 for my $i (0..$#z) {
104106 # this second test also checks that performing a second slice on a mesh after
105107 # a transformation works properly (shared_vertices is correctly invalidated);
106108 # 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 ]);
108112 is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area';
109113 }
110114 }
77 %}
88
99 %name{Slic3r::PrintController} class PrintController {
10
1110 PrintController(Print *print);
12
13 void slice();
1411 };
1512
1613 %name{Slic3r::AppController} class AppController {
110110 void deregister_on_request_update_callback()
111111 %code%{ Slic3r::GUI::g_on_request_update_callback.deregister_callback(); %};
112112
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
1616 %code%{ RETVAL = &THIS->thin_fills; %};
1717 Ref<SurfaceCollection> fill_surfaces()
1818 %code%{ RETVAL = &THIS->fill_surfaces; %};
19 Ref<SurfaceCollection> perimeter_surfaces()
20 %code%{ RETVAL = &THIS->perimeter_surfaces; %};
2119 Polygons bridged()
2220 %code%{ RETVAL = THIS->bridged; %};
2321 Ref<PolylineCollection> unsupported_bridge_edges()
337337 %code%{ RETVAL = &THIS->config; %};
338338 Ref<TriangleMesh> mesh()
339339 %code%{ RETVAL = &THIS->mesh; %};
340 Ref<TriangleMesh> convex_hull()
341 %code%{ RETVAL = &THIS->get_convex_hull(); %};
340342
341343 bool modifier()
342 %code%{ RETVAL = THIS->modifier; %};
344 %code%{ RETVAL = THIS->is_modifier(); %};
343345 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); %};
345357
346358 size_t split(unsigned int max_extruders);
347359
276276 }
277277 RETVAL = THIS->total_cost;
278278 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:
279309 RETVAL
280310 %}
281311 };
4646 char *message;
4747 CODE:
4848 Slic3r::trace(level, message);
49
50 void
51 disable_multi_threading()
52 CODE:
53 Slic3r::disable_multi_threading();
4954
5055 void
5156 set_var_dir(dir)