Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/supermerill/SuperSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsupermerill <merill@fr.fr>2018-10-31 16:54:11 +0300
committersupermerill <merill@fr.fr>2018-10-31 16:54:11 +0300
commite7cfb20838a37e11bd395b3ae230d9d7318783a6 (patch)
treec691f5424e97cb7724ac4568cf15e7e4eb662028
parent280bb5dc158f6f10257e5fe6f883d8799ecb4495 (diff)
parentb8dca4cb9d8f154846ed433108540916484ad613 (diff)
Merge branch 'master_slic3rPE_PR'1.41.1-51.41.1-1
-rw-r--r--lib/Slic3r/GUI.pm24
-rw-r--r--lib/Slic3r/GUI/MainFrame.pm4
-rw-r--r--lib/Slic3r/GUI/Plater.pm31
-rw-r--r--lib/Slic3r/GUI/Plater/ObjectCutDialog.pm2
-rw-r--r--lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm93
-rw-r--r--lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm4
-rw-r--r--lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm52
-rw-r--r--resources/icons/printers/PrusaResearch_MK2.5MMU2.pngbin0 -> 71328 bytes
-rw-r--r--resources/icons/support_blocker.pngbin0 -> 656 bytes
-rw-r--r--resources/icons/support_enforcer.pngbin0 -> 509 bytes
-rw-r--r--resources/profiles/PrusaResearch.idx13
-rw-r--r--resources/profiles/PrusaResearch.ini219
-rw-r--r--xs/CMakeLists.txt4
-rw-r--r--xs/src/libnest2d/CMakeLists.txt30
-rw-r--r--xs/src/libnest2d/README.md18
-rw-r--r--xs/src/libnest2d/cmake_modules/FindTBB.cmake322
-rw-r--r--xs/src/libnest2d/examples/main.cpp668
-rw-r--r--xs/src/libnest2d/libnest2d.h14
-rw-r--r--xs/src/libnest2d/libnest2d/boost_alg.hpp133
-rw-r--r--xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp142
-rw-r--r--xs/src/libnest2d/libnest2d/geometry_traits.hpp288
-rw-r--r--xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp127
-rw-r--r--xs/src/libnest2d/libnest2d/libnest2d.hpp216
-rw-r--r--xs/src/libnest2d/libnest2d/metaloop.hpp8
-rw-r--r--xs/src/libnest2d/libnest2d/optimizer.hpp3
-rw-r--r--xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp6
-rw-r--r--xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp92
-rw-r--r--xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp920
-rw-r--r--xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp39
-rw-r--r--xs/src/libnest2d/libnest2d/rotfinder.hpp41
-rw-r--r--xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp59
-rw-r--r--xs/src/libnest2d/libnest2d/selections/filler.hpp12
-rw-r--r--xs/src/libnest2d/libnest2d/selections/firstfit.hpp17
-rw-r--r--xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp3
-rw-r--r--xs/src/libnest2d/tests/test.cpp120
-rw-r--r--xs/src/libnest2d/tools/libnfpglue.cpp16
-rw-r--r--xs/src/libnest2d/tools/libnfpglue.hpp10
-rw-r--r--xs/src/libnest2d/tools/nfp_svgnest.hpp1018
-rw-r--r--xs/src/libnest2d/tools/nfp_svgnest_glue.hpp75
-rw-r--r--xs/src/libnest2d/tools/svgtools.hpp6
-rw-r--r--xs/src/libslic3r/Config.hpp3
-rw-r--r--xs/src/libslic3r/EdgeGrid.cpp161
-rw-r--r--xs/src/libslic3r/EdgeGrid.hpp14
-rw-r--r--xs/src/libslic3r/ExPolygonCollection.cpp9
-rw-r--r--xs/src/libslic3r/ExtrusionEntity.hpp40
-rw-r--r--xs/src/libslic3r/ExtrusionEntityCollection.hpp16
-rw-r--r--xs/src/libslic3r/Fill/FillGyroid.cpp2
-rw-r--r--xs/src/libslic3r/Fill/FillHoneycomb.cpp4
-rw-r--r--xs/src/libslic3r/Flow.cpp9
-rw-r--r--xs/src/libslic3r/Format/3mf.cpp14
-rw-r--r--xs/src/libslic3r/Format/AMF.cpp14
-rw-r--r--xs/src/libslic3r/GCode.cpp31
-rw-r--r--xs/src/libslic3r/GCode.hpp1
-rw-r--r--xs/src/libslic3r/GCode/WipeTower.hpp6
-rw-r--r--xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp51
-rw-r--r--xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp10
-rw-r--r--xs/src/libslic3r/GCodeTimeEstimator.cpp13
-rw-r--r--xs/src/libslic3r/GCodeTimeEstimator.hpp3
-rw-r--r--xs/src/libslic3r/Layer.hpp54
-rw-r--r--xs/src/libslic3r/LayerRegion.cpp12
-rw-r--r--xs/src/libslic3r/Model.cpp57
-rw-r--r--xs/src/libslic3r/Model.hpp45
-rw-r--r--xs/src/libslic3r/ModelArrange.hpp469
-rw-r--r--xs/src/libslic3r/MultiPoint.hpp21
-rw-r--r--xs/src/libslic3r/PerimeterGenerator.cpp88
-rw-r--r--xs/src/libslic3r/Point.hpp20
-rw-r--r--xs/src/libslic3r/Polygon.hpp6
-rw-r--r--xs/src/libslic3r/Polyline.cpp20
-rw-r--r--xs/src/libslic3r/Polyline.hpp6
-rw-r--r--xs/src/libslic3r/Print.cpp13
-rw-r--r--xs/src/libslic3r/Print.hpp12
-rw-r--r--xs/src/libslic3r/PrintConfig.cpp104
-rw-r--r--xs/src/libslic3r/PrintConfig.hpp15
-rw-r--r--xs/src/libslic3r/PrintObject.cpp80
-rw-r--r--xs/src/libslic3r/PrintRegion.cpp5
-rw-r--r--xs/src/libslic3r/Slicing.cpp6
-rw-r--r--xs/src/libslic3r/SupportMaterial.cpp877
-rw-r--r--xs/src/libslic3r/SupportMaterial.hpp16
-rw-r--r--xs/src/libslic3r/SurfaceCollection.hpp5
-rw-r--r--xs/src/libslic3r/TriangleMesh.cpp940
-rw-r--r--xs/src/libslic3r/TriangleMesh.hpp37
-rw-r--r--xs/src/libslic3r/Utils.hpp6
-rw-r--r--xs/src/libslic3r/libslic3r.h2
-rw-r--r--xs/src/libslic3r/utils.cpp73
-rw-r--r--xs/src/slic3r/AppController.cpp216
-rw-r--r--xs/src/slic3r/AppController.hpp52
-rw-r--r--xs/src/slic3r/AppControllerWx.cpp73
-rw-r--r--xs/src/slic3r/GUI/3DScene.cpp25
-rw-r--r--xs/src/slic3r/GUI/AppConfig.cpp21
-rw-r--r--xs/src/slic3r/GUI/AppConfig.hpp8
-rw-r--r--xs/src/slic3r/GUI/ConfigWizard.cpp16
-rw-r--r--xs/src/slic3r/GUI/FirmwareDialog.cpp25
-rw-r--r--xs/src/slic3r/GUI/GUI.cpp59
-rw-r--r--xs/src/slic3r/GUI/GUI.hpp8
-rw-r--r--xs/src/slic3r/GUI/Preset.cpp11
-rw-r--r--xs/src/slic3r/GUI/PresetBundle.cpp18
-rw-r--r--xs/src/slic3r/GUI/Tab.cpp5
-rw-r--r--xs/src/slic3r/ProgressIndicator.hpp (renamed from xs/src/slic3r/IProgressIndicator.hpp)33
-rw-r--r--xs/src/slic3r/Strings.hpp10
-rw-r--r--xs/src/slic3r/Utils/Duet.cpp6
-rw-r--r--xs/src/slic3r/Utils/Http.cpp15
-rw-r--r--xs/src/slic3r/Utils/Http.hpp3
-rw-r--r--xs/src/slic3r/Utils/Serial.cpp7
-rw-r--r--xs/t/01_trianglemesh.t8
-rw-r--r--xs/xsp/AppController.xsp3
-rw-r--r--xs/xsp/GUI.xsp6
-rw-r--r--xs/xsp/Layer.xsp2
-rw-r--r--xs/xsp/Model.xsp16
-rw-r--r--xs/xsp/Print.xsp30
-rw-r--r--xs/xsp/XS.xsp5
110 files changed, 6004 insertions, 2916 deletions
diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm
index 2340d8731..eb104eac3 100644
--- a/lib/Slic3r/GUI.pm
+++ b/lib/Slic3r/GUI.pm
@@ -356,28 +356,4 @@ sub set_menu_item_icon {
}
}
-sub save_window_pos {
- my ($self, $window, $name) = @_;
-
- $self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY);
- $self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH);
- $self->{app_config}->set("${name}_maximized", $window->IsMaximized);
- $self->{app_config}->save;
-}
-
-sub restore_window_pos {
- my ($self, $window, $name) = @_;
- if ($self->{app_config}->has("${name}_pos")) {
- my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ];
- $window->SetSize($size);
-
- my $display = Wx::Display->new->GetClientArea();
- my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ];
- if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) {
- $window->Move($pos);
- }
- $window->Maximize(1) if $self->{app_config}->get("${name}_maximized");
- }
-}
-
1;
diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index 1807bd3bb..7838e75f1 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -88,7 +88,7 @@ sub new {
$self->Fit;
$self->SetMinSize([760, 490]);
$self->SetSize($self->GetMinSize);
- wxTheApp->restore_window_pos($self, "main_frame");
+ Slic3r::GUI::restore_window_size($self, "main_frame");
$self->Show;
$self->Layout;
}
@@ -101,7 +101,7 @@ sub new {
return;
}
# save window size
- wxTheApp->save_window_pos($self, "main_frame");
+ Slic3r::GUI::save_window_size($self, "main_frame");
# Save the slic3r.ini. Usually the ini file is saved from "on idle" callback,
# but in rare cases it may not have been called yet.
wxTheApp->{app_config}->save;
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index ee2689d16..48b82237f 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -767,6 +767,15 @@ sub load_files {
$model->convert_multipart_object(scalar(@$nozzle_dmrs)) if $dialog->ShowModal() == wxID_YES;
}
+ # objects imported from 3mf require a call to center_around_origin to have gizmos working properly and this call
+ # need to be done after looks_like_multipart_object detection
+ if ($input_file =~ /.3[mM][fF]$/)
+ {
+ foreach my $model_object (@{$model->objects}) {
+ $model_object->center_around_origin; # also aligns object to Z = 0
+ }
+ }
+
if ($one_by_one) {
push @obj_idx, $self->load_model_objects(@{$model->objects});
} else {
@@ -1646,20 +1655,37 @@ sub print_info_box_show {
$grid_sizer->AddGrowableCol(1, 1);
$grid_sizer->AddGrowableCol(3, 1);
$print_info_sizer->Add($grid_sizer, 0, wxEXPAND);
+ my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0;
my @info = (
L("Used Filament (m)")
- => sprintf("%.2f" , $self->{print}->total_used_filament / 1000),
+ => $is_wipe_tower ?
+ sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_used_filament / 1000,
+ ($self->{print}->total_used_filament - $self->{print}->total_wipe_tower_filament) / 1000,
+ L("objects"),
+ $self->{print}->total_wipe_tower_filament / 1000,
+ L("wipe tower")) :
+ sprintf("%.2f" , $self->{print}->total_used_filament / 1000),
+
L("Used Filament (mm³)")
=> sprintf("%.2f" , $self->{print}->total_extruded_volume),
L("Used Filament (g)"),
=> sprintf("%.2f" , $self->{print}->total_weight),
L("Cost"),
- => sprintf("%.2f" , $self->{print}->total_cost),
+ => $is_wipe_tower ?
+ sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_cost,
+ ($self->{print}->total_cost - $self->{print}->total_wipe_tower_cost),
+ L("objects"),
+ $self->{print}->total_wipe_tower_cost,
+ L("wipe tower")) :
+ sprintf("%.2f" , $self->{print}->total_cost),
L("Estimated printing time (normal mode)")
=> $self->{print}->estimated_normal_print_time,
L("Estimated printing time (silent mode)")
=> $self->{print}->estimated_silent_print_time
);
+ # if there is a wipe tower, insert number of toolchanges info into the array:
+ splice (@info, 8, 0, L("Number of tool changes") => sprintf("%.d", $self->{print}->m_wipe_tower_number_of_toolchanges)) if ($is_wipe_tower);
+
while ( my $label = shift @info) {
my $value = shift @info;
next if $value eq "N/A";
@@ -1674,6 +1700,7 @@ sub print_info_box_show {
$scrolled_window_sizer->Show(2, $show);
$scrolled_window_panel->Layout;
+ $self->Layout;
}
sub do_print {
diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
index 26a6fdec3..77efbb29b 100644
--- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
@@ -238,7 +238,7 @@ sub _update {
my @expolygons = ();
foreach my $volume (@{$self->{model_object}->volumes}) {
next if !$volume->mesh;
- next if $volume->modifier;
+ next if !$volume->model_part;
my $expp = $volume->mesh->slice([ $z_cut ])->[0];
push @expolygons, @$expp;
}
diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
index 783c1a9f5..13c1b6945 100644
--- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
@@ -16,6 +16,8 @@ use base 'Wx::Panel';
use constant ICON_OBJECT => 0;
use constant ICON_SOLIDMESH => 1;
use constant ICON_MODIFIERMESH => 2;
+use constant ICON_SUPPORT_ENFORCER => 3;
+use constant ICON_SUPPORT_BLOCKER => 4;
sub new {
my ($class, $parent, %params) = @_;
@@ -35,7 +37,7 @@ sub new {
y => 0,
z => 0,
};
-
+
# create TreeCtrl
my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100],
wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT
@@ -46,6 +48,8 @@ sub new {
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH
+ $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_enforcer.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_ENFORCER
+ $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_blocker.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_BLOCKER
my $rootId = $tree->AddRoot("Object", ICON_OBJECT);
$tree->SetPlData($rootId, { type => 'object' });
@@ -89,7 +93,14 @@ sub new {
$self->{btn_move_down}->SetFont($Slic3r::GUI::small_font);
# part settings panel
- $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; $self->_update_canvas; });
+ $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub {
+ my ($key, $value) = @_;
+ wxTheApp->CallAfter(sub {
+ $self->set_part_type($value) if ($key eq "part_type");
+ $self->{part_settings_changed} = 1;
+ $self->_update_canvas;
+ });
+ });
my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL);
$settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
@@ -225,8 +236,11 @@ sub reload_tree {
my $selectedId = $rootId;
foreach my $volume_id (0..$#{$object->volumes}) {
my $volume = $object->volumes->[$volume_id];
-
- my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH;
+ my $icon =
+ $volume->modifier ? ICON_MODIFIERMESH :
+ $volume->support_enforcer ? ICON_SUPPORT_ENFORCER :
+ $volume->support_blocker ? ICON_SUPPORT_BLOCKER :
+ ICON_SOLIDMESH;
my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
if ($volume_id == $selected_volume_idx) {
$selectedId = $itemId;
@@ -288,6 +302,8 @@ sub selection_changed {
if (my $itemData = $self->get_selection) {
my ($config, @opt_keys);
+ my $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_OBJECT;
+ my $support = 0;
if ($itemData->{type} eq 'volume') {
# select volume in 3D preview
if ($self->{canvas}) {
@@ -301,16 +317,24 @@ sub selection_changed {
# attach volume config to settings panel
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
- if ($volume->modifier) {
+ if (! $volume->model_part) {
$self->{optgroup_movers}->enable;
+ if ($volume->support_enforcer || $volume->support_blocker) {
+ $support = 1;
+ $type = $volume->support_enforcer ?
+ Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER :
+ Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER;
+ } else {
+ $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER;
+ }
} else {
+ $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART;
$self->{optgroup_movers}->disable;
}
$config = $volume->config;
$self->{staticbox}->SetLabel('Part Settings');
-
# get default values
- @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys};
+ @opt_keys = $support ? () : @{Slic3r::Config::PrintRegion->new->get_keys};
} elsif ($itemData->{type} eq 'object') {
# select nothing in 3D preview
@@ -323,33 +347,54 @@ sub selection_changed {
# get default values
my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys);
- # decide which settings will be shown by default
+ # decide which settings will be shown by default
if ($itemData->{type} eq 'object') {
$config->set_ifndef('wipe_into_objects', 0);
$config->set_ifndef('wipe_into_infill', 0);
}
# append default extruder
- push @opt_keys, 'extruder';
- $default_config->set('extruder', 0);
- $config->set_ifndef('extruder', 0);
+ if (! $support) {
+ push @opt_keys, 'extruder';
+ $default_config->set('extruder', 0);
+ $config->set_ifndef('extruder', 0);
+ }
+ $self->{settings_panel}->set_type($type);
$self->{settings_panel}->set_default_config($default_config);
$self->{settings_panel}->set_config($config);
$self->{settings_panel}->set_opt_keys(\@opt_keys);
# disable minus icon to remove the settings
- if ($itemData->{type} eq 'object') {
- $self->{settings_panel}->set_fixed_options([qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)]);
- } else {
- $self->{settings_panel}->set_fixed_options([qw(extruder)]);
- }
-
+ my $fixed_options =
+ ($itemData->{type} eq 'object') ? [qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)] :
+ $support ? [] : [qw(extruder)];
+ $self->{settings_panel}->set_fixed_options($fixed_options);
$self->{settings_panel}->enable;
}
Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas};
}
+sub set_part_type
+{
+ my ($self, $part_type) = @_;
+ if (my $itemData = $self->get_selection) {
+ if ($itemData->{type} eq 'volume') {
+ my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
+ if ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER ||
+ $part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART) {
+ $volume->set_modifier($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER);
+ } elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER) {
+ $volume->set_support_enforcer;
+ } elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER) {
+ $volume->set_support_blocker;
+ }
+ # We want the icon of the selected item to be changed as well.
+ $self->reload_tree($itemData->{volume_id});
+ }
+ }
+}
+
sub on_btn_load {
my ($self, $is_modifier) = @_;
@@ -362,13 +407,25 @@ sub on_btn_load {
}
foreach my $object (@{$model->objects}) {
+ my $delta_x = 0.0;
+ my $delta_y = 0.0;
+ my $delta_z = 0.0;
+ 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)) {
+ $object->center_around_origin;
+ $delta_x = $self->{model_object}->origin_translation->x - $object->origin_translation->x;
+ $delta_y = $self->{model_object}->origin_translation->y - $object->origin_translation->y;
+ $delta_z = $self->{model_object}->origin_translation->z - $object->origin_translation->z;
+ }
foreach my $volume (@{$object->volumes}) {
my $new_volume = $self->{model_object}->add_volume($volume);
$new_volume->set_modifier($is_modifier);
$new_volume->set_name(basename($input_file));
# apply the same translation we applied to the object
- $new_volume->mesh->translate(@{$self->{model_object}->origin_translation});
+ if (($delta_x != 0.0) || ($delta_y != 0.0) || ($delta_z != 0.0)) {
+ $new_volume->mesh->translate($delta_x, $delta_y, $delta_z);
+ $new_volume->convex_hull->translate($delta_x, $delta_y, $delta_z);
+ }
# set a default extruder value, since user can't add it manually
$new_volume->config->set_ifndef('extruder', 0);
diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
index 3befba708..3ccf1d7f8 100644
--- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
@@ -33,7 +33,7 @@ sub new {
$self->{layers}->Closing;
# save window size
- wxTheApp->save_window_pos($self, "object_settings");
+ Slic3r::GUI::save_window_size($self, "object_settings");
$self->EndModal(wxID_OK);
$self->{parts}->Destroy;
@@ -49,7 +49,7 @@ sub new {
$self->Layout;
- wxTheApp->restore_window_pos($self, "object_settings");
+ Slic3r::GUI::restore_window_size($self, "object_settings");
return $self;
}
diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm
index 3b10ed99f..b085871f0 100644
--- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm
+++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm
@@ -7,15 +7,20 @@ use warnings;
use utf8;
use List::Util qw(first);
-use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG
- wxTheApp);
-use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU);
+use Wx qw(:misc :sizer :button :combobox wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp);
+use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_LEFT_DOWN EVT_MENU);
use base 'Wx::ScrolledWindow';
use constant ICON_MATERIAL => 0;
use constant ICON_SOLIDMESH => 1;
use constant ICON_MODIFIERMESH => 2;
+use constant TYPE_OBJECT => -1;
+use constant TYPE_PART => 0;
+use constant TYPE_MODIFIER => 1;
+use constant TYPE_SUPPORT_ENFORCER => 2;
+use constant TYPE_SUPPORT_BLOCKER => 3;
+
my %icons = (
'Advanced' => 'wand.png',
'Extruders' => 'funnel.png',
@@ -36,13 +41,14 @@ sub new {
$self->{config} = Slic3r::Config->new;
# On change callback.
$self->{on_change} = $params{on_change};
+ $self->{type} = TYPE_OBJECT;
$self->{fixed_options} = {};
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0);
-
+
# option selector
{
# create the button
@@ -110,6 +116,16 @@ sub set_opt_keys {
$self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ];
}
+sub set_type {
+ my ($self, $type) = @_;
+ $self->{type} = $type;
+ if ($type == TYPE_SUPPORT_ENFORCER || $type == TYPE_SUPPORT_BLOCKER) {
+ $self->{btn_add}->Hide;
+ } else {
+ $self->{btn_add}->Show;
+ }
+}
+
sub set_fixed_options {
my ($self, $opt_keys) = @_;
$self->{fixed_options} = { map {$_ => 1} @$opt_keys };
@@ -121,12 +137,28 @@ sub update_optgroup {
$self->{options_sizer}->Clear(1);
return if !defined $self->{config};
-
+
+ if ($self->{type} != TYPE_OBJECT) {
+ my $label = Wx::StaticText->new($self, -1, "Type:"),
+ my $selection = [ "Part", "Modifier", "Support Enforcer", "Support Blocker" ];
+ my $field = Wx::ComboBox->new($self, -1, $selection->[$self->{type}], wxDefaultPosition, Wx::Size->new(160, -1), $selection, wxCB_READONLY);
+ my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
+ $sizer->Add($label, 1, wxEXPAND | wxALL, 5);
+ $sizer->Add($field, 0, wxALL, 5);
+ EVT_COMBOBOX($self, $field, sub {
+ my $idx = $field->GetSelection; # get index of selected value
+ $self->{on_change}->("part_type", $idx) if $self->{on_change};
+ });
+ $self->{options_sizer}->Add($sizer, 0, wxEXPAND | wxBOTTOM, 0);
+ }
+
my %categories = ();
- foreach my $opt_key (@{$self->{config}->get_keys}) {
- my $category = $Slic3r::Config::Options->{$opt_key}{category};
- $categories{$category} ||= [];
- push @{$categories{$category}}, $opt_key;
+ if ($self->{type} != TYPE_SUPPORT_ENFORCER && $self->{type} != TYPE_SUPPORT_BLOCKER) {
+ foreach my $opt_key (@{$self->{config}->get_keys}) {
+ my $category = $Slic3r::Config::Options->{$opt_key}{category};
+ $categories{$category} ||= [];
+ push @{$categories{$category}}, $opt_key;
+ }
}
foreach my $category (sort keys %categories) {
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
@@ -136,7 +168,7 @@ sub update_optgroup {
full_labels => 1,
label_font => $Slic3r::GUI::small_font,
sidetext_font => $Slic3r::GUI::small_font,
- label_width => 120,
+ label_width => 150,
on_change => sub { $self->{on_change}->() if $self->{on_change} },
extra_column => sub {
my ($line) = @_;
diff --git a/resources/icons/printers/PrusaResearch_MK2.5MMU2.png b/resources/icons/printers/PrusaResearch_MK2.5MMU2.png
new file mode 100644
index 000000000..eb5dccf08
--- /dev/null
+++ b/resources/icons/printers/PrusaResearch_MK2.5MMU2.png
Binary files differ
diff --git a/resources/icons/support_blocker.png b/resources/icons/support_blocker.png
new file mode 100644
index 000000000..8a66c73f4
--- /dev/null
+++ b/resources/icons/support_blocker.png
Binary files differ
diff --git a/resources/icons/support_enforcer.png b/resources/icons/support_enforcer.png
new file mode 100644
index 000000000..c0bef8d5e
--- /dev/null
+++ b/resources/icons/support_enforcer.png
Binary files differ
diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx
index 63a81db49..f6befe005 100644
--- a/resources/profiles/PrusaResearch.idx
+++ b/resources/profiles/PrusaResearch.idx
@@ -1,4 +1,17 @@
+min_slic3r_version = 1.41.1
+0.3.2 New MK2.5 and MK3 FW versions
+0.3.1 New MK2.5 and MK3 FW versions
+0.3.0 New MK2.5 and MK3 FW version
min_slic3r_version = 1.41.0-alpha
+0.2.9 New MK2.5 and MK3 FW versions
+0.2.8 New MK2.5 and MK3 FW version
+min_slic3r_version = 1.41.1
+0.2.7 New MK2.5 and MK3 FW version
+0.2.6 Added MMU2 MK2.5 settings
+min_slic3r_version = 1.41.0-alpha
+0.2.5 Prusament is out - added prusament settings
+0.2.4 Added soluble support profiles for MMU2
+0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit
0.2.2 Edited MMU2 Single mode purge line
0.2.1 Added PET and BVOH settings for MMU2
0.2.0-beta5 Fixed MMU1 ramming parameters
diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini
index 6375c33b3..bcffd1308 100644
--- a/resources/profiles/PrusaResearch.ini
+++ b/resources/profiles/PrusaResearch.ini
@@ -5,7 +5,7 @@
name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the Slic3r configuration to be downgraded.
-config_version = 0.2.2
+config_version = 0.3.2
# Where to get the updates from?
config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/
@@ -35,6 +35,10 @@ variants = 0.4
name = Original Prusa i3 MK2/S MMU 1.0
variants = 0.4; 0.6
+[printer_model:MK2.5MMU2]
+name = Original Prusa i3 MK2.5 MMU 2.0
+variants = 0.4
+
# All presets starting with asterisk, for example *common*, are intermediate and they will
# not make it into the user interface.
@@ -184,7 +188,6 @@ support_material_interface_spacing = 0.1
support_material_synchronize_layers = 1
support_material_threshold = 80
support_material_with_sheath = 1
-wipe_tower = 1
# XXXXXXXXXXXXXXXXXXXX
# XXX--- 0.05mm ---XXX
@@ -332,7 +335,7 @@ top_solid_layers = 7
[print:0.15mm 100mms Linear Advance]
inherits = *0.15mm*
bridge_flow_ratio = 0.95
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4
external_perimeter_speed = 50
infill_speed = 100
max_print_speed = 150
@@ -344,7 +347,7 @@ top_solid_infill_speed = 70
[print:0.15mm OPTIMAL]
inherits = *0.15mm*
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4
top_infill_extrusion_width = 0.45
[print:0.15mm OPTIMAL 0.25 nozzle]
@@ -377,16 +380,33 @@ perimeter_speed = 45
solid_infill_speed = 200
top_solid_infill_speed = 50
+[print:0.15mm OPTIMAL MK3 SOLUBLE FULL]
+inherits = 0.15mm OPTIMAL MK3; *soluble_support*
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1
+notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder
+support_material_extruder = 5
+support_material_interface_extruder = 5
+perimeter_speed = 40
+solid_infill_speed = 40
+top_infill_extrusion_width = 0.45
+top_solid_infill_speed = 30
+
+[print:0.15mm OPTIMAL MK3 SOLUBLE INTERFACE]
+inherits = 0.15mm OPTIMAL MK3 SOLUBLE FULL
+notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder
+support_material_extruder = 0
+support_material_interface_layers = 3
+support_material_with_sheath = 0
+
[print:0.15mm OPTIMAL SOLUBLE FULL]
inherits = *0.15mm*; *soluble_support*
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1
external_perimeter_speed = 25
notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder
perimeter_speed = 40
solid_infill_speed = 40
top_infill_extrusion_width = 0.45
top_solid_infill_speed = 30
-wipe_tower = 1
[print:0.15mm OPTIMAL SOLUBLE INTERFACE]
inherits = 0.15mm OPTIMAL SOLUBLE FULL
@@ -407,6 +427,7 @@ max_print_speed = 200
perimeter_speed = 45
solid_infill_speed = 200
top_solid_infill_speed = 50
+
[print:*0.20mm*]
inherits = *common*
bottom_solid_layers = 4
@@ -439,7 +460,7 @@ top_solid_infill_speed = 50
[print:0.20mm 100mms Linear Advance]
inherits = *0.20mm*
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4
external_perimeter_speed = 50
infill_speed = 100
max_print_speed = 150
@@ -461,9 +482,27 @@ perimeter_speed = 45
solid_infill_speed = 200
top_solid_infill_speed = 50
+[print:0.20mm FAST MK3 SOLUBLE FULL]
+inherits = 0.20mm FAST MK3; *soluble_support*
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1
+notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder
+support_material_extruder = 5
+support_material_interface_extruder = 5
+perimeter_speed = 40
+solid_infill_speed = 40
+top_infill_extrusion_width = 0.45
+top_solid_infill_speed = 30
+
+[print:0.20mm FAST MK3 SOLUBLE INTERFACE]
+inherits = 0.20mm FAST MK3 SOLUBLE FULL
+notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder
+support_material_extruder = 0
+support_material_interface_layers = 3
+support_material_with_sheath = 0
+
[print:0.20mm NORMAL]
inherits = *0.20mm*
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4
[print:0.20mm NORMAL 0.6 nozzle]
inherits = *0.20mm*; *0.6nozzle*
@@ -471,7 +510,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and
[print:0.20mm NORMAL SOLUBLE FULL]
inherits = *0.20mm*; *soluble_support*
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1
external_perimeter_speed = 30
notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder
perimeter_speed = 40
@@ -522,7 +561,7 @@ top_solid_layers = 4
[print:0.35mm FAST]
inherits = *0.35mm*
bridge_flow_ratio = 0.95
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4
first_layer_extrusion_width = 0.42
perimeter_extrusion_width = 0.43
solid_infill_extrusion_width = 0.7
@@ -550,6 +589,53 @@ support_material_interface_layers = 2
support_material_with_sheath = 0
support_material_xy_spacing = 150%
+# XXXXXXXXXXXXXXXXXXXXXX
+# XXX----- MK2.5 ----XXX
+# XXXXXXXXXXXXXXXXXXXXXX
+
+[print:0.15mm 100mms Linear Advance MK2.5]
+inherits = 0.15mm 100mms Linear Advance
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4
+single_extruder_multi_material_priming = 0
+
+[print:0.15mm OPTIMAL MK2.5]
+inherits = 0.15mm OPTIMAL
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4
+single_extruder_multi_material_priming = 0
+
+[print:0.15mm OPTIMAL SOLUBLE FULL MK2.5]
+inherits = 0.15mm OPTIMAL SOLUBLE FULL
+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
+
+[print:0.15mm OPTIMAL SOLUBLE INTERFACE MK2.5]
+inherits = 0.15mm OPTIMAL SOLUBLE INTERFACE
+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
+
+[print:0.20mm 100mms Linear Advance MK2.5]
+inherits = 0.20mm 100mms Linear Advance
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4
+single_extruder_multi_material_priming = 0
+
+[print:0.20mm NORMAL MK2.5]
+inherits = 0.20mm NORMAL
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4
+single_extruder_multi_material_priming = 0
+
+[print:0.20mm NORMAL SOLUBLE FULL MK2.5]
+inherits = 0.20mm NORMAL SOLUBLE FULL
+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
+single_extruder_multi_material_priming = 0
+
+[print:0.20mm NORMAL SOLUBLE INTERFACE MK2.5]
+inherits = 0.20mm NORMAL SOLUBLE INTERFACE
+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
+single_extruder_multi_material_priming = 0
+
+[print:0.35mm FAST MK2.5]
+inherits = 0.35mm FAST
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4
+single_extruder_multi_material_priming = 0
+
# XXXXXXxxXXXXXXXXXXXXXX
# XXX--- filament ---XXX
# XXXXXXXXxxXXXXXXXXXXXX
@@ -558,7 +644,7 @@ support_material_xy_spacing = 150%
cooling = 1
compatible_printers =
# For now, all but selected filaments are disabled for the MMU 2.0
-compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material)
+compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
end_filament_gcode = "; Filament-specific end gcode"
extrusion_multiplier = 1
filament_loading_speed = 28
@@ -658,7 +744,7 @@ temperature = 240
[filament:ColorFabb Brass Bronze]
inherits = *PLA*
# For now, all but selected filaments are disabled for the MMU 2.0
-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)
+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)
extrusion_multiplier = 1.2
filament_cost = 80.65
filament_density = 4
@@ -690,7 +776,7 @@ filament_density = 1.24
[filament:ColorFabb Woodfil]
inherits = *PLA*
# For now, all but selected filaments are disabled for the MMU 2.0
-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)
+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)
extrusion_multiplier = 1.2
filament_cost = 62.9
filament_density = 1.15
@@ -792,7 +878,7 @@ temperature = 275
[filament:Fillamentum Timberfil]
inherits = *PLA*
# For now, all but selected filaments are disabled for the MMU 2.0
-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)
+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)
extrusion_multiplier = 1.2
filament_cost = 68
filament_density = 1.15
@@ -854,7 +940,7 @@ filament_notes = "List of materials tested with standart ABS print settings for
[filament:*ABS MMU2*]
inherits = Prusa ABS
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material
filament_cooling_final_speed = 50
filament_cooling_initial_speed = 10
filament_cooling_moves = 5
@@ -892,7 +978,7 @@ filament_notes = "List of manufacturers tested with standart PET print settings
[filament:*PET MMU2*]
inherits = Prusa PET
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material
temperature = 230
first_layer_temperature = 230
filament_cooling_final_speed = 1
@@ -918,9 +1004,16 @@ filament_cost = 25.4
filament_density = 1.24
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"
+[filament:Prusament PLA]
+inherits = *PLA*
+temperature = 215
+filament_cost = 24.99
+filament_density = 1.24
+filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa"
+
[filament:*PLA MMU2*]
inherits = Prusa PLA
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material
temperature = 205
filament_cooling_final_speed = 1
filament_cooling_initial_speed = 2
@@ -937,6 +1030,9 @@ inherits = *PLA MMU2*
[filament:Prusa PLA MMU2]
inherits = *PLA MMU2*
+[filament:Prusament PLA MMU2]
+inherits = *PLA MMU2*
+
[filament:SemiFlex or Flexfill 98A]
inherits = *FLEX*
filament_cost = 82
@@ -1001,7 +1097,7 @@ temperature = 210
[filament:Verbatim BVOH MMU2]
inherits = Verbatim BVOH
-compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material
+compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material
temperature = 195
filament_notes = BVOH
fan_always_on = 1
@@ -1101,7 +1197,7 @@ z_offset = 0
printer_model = MK2S
printer_variant = 0.4
default_print_profile = 0.15mm OPTIMAL
-default_filament_profile = Prusa PLA
+default_filament_profile = Prusament PLA
[printer:*multimaterial*]
inherits = *common*
@@ -1196,19 +1292,83 @@ default_print_profile = 0.20mm NORMAL 0.6 nozzle
inherits = Original Prusa i3 MK2
printer_model = MK2.5
remaining_times = 1
-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
+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
+
+[printer:Original Prusa i3 MK2.5 MMU2 Single]
+inherits = Original Prusa i3 MK2.5; *mm2*
+printer_model = MK2.5MMU2
+single_extruder_multi_material = 0
+max_print_height = 200
+remaining_times = 1
+silent_mode = 0
+retract_lift_below = 199
+machine_max_acceleration_e = 10000
+machine_max_acceleration_extruding = 2000
+machine_max_acceleration_retracting = 1500
+machine_max_acceleration_x = 9000
+machine_max_acceleration_y = 9000
+machine_max_acceleration_z = 500
+machine_max_feedrate_e = 120
+machine_max_feedrate_x = 500
+machine_max_feedrate_y = 500
+machine_max_feedrate_z = 12
+machine_max_jerk_e = 2.5
+machine_max_jerk_x = 10
+machine_max_jerk_y = 10
+machine_max_jerk_z = 0.2
+machine_min_extruding_rate = 0
+machine_min_travel_rate = 0
+default_print_profile = 0.15mm OPTIMAL MK2.5
+default_filament_profile = Prusament PLA
+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
+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
+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
+
+[printer:Original Prusa i3 MK2.5 MMU2]
+inherits = Original Prusa i3 MK2.5; *mm2*
+printer_model = MK2.5MMU2
+max_print_height = 200
+remaining_times = 1
+silent_mode = 0
+retract_lift_below = 199
+machine_max_acceleration_e = 10000
+machine_max_acceleration_extruding = 2000
+machine_max_acceleration_retracting = 1500
+machine_max_acceleration_x = 9000
+machine_max_acceleration_y = 9000
+machine_max_acceleration_z = 500
+machine_max_feedrate_e = 120
+machine_max_feedrate_x = 500
+machine_max_feedrate_y = 500
+machine_max_feedrate_z = 12
+machine_max_jerk_e = 2.5
+machine_max_jerk_x = 10
+machine_max_jerk_y = 10
+machine_max_jerk_z = 0.2
+machine_min_extruding_rate = 0
+machine_min_travel_rate = 0
+default_print_profile = 0.15mm OPTIMAL MK2.5
+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
+single_extruder_multi_material = 1
+# The 5x nozzle diameter defines the number of extruders. Other extruder parameters
+# (for example the retract values) are duplicaed from the first value, so they do not need
+# to be defined explicitely.
+nozzle_diameter = 0.4,0.4,0.4,0.4,0.4
+extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F
+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
+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
[printer:Original Prusa i3 MK2.5 0.25 nozzle]
inherits = Original Prusa i3 MK2 0.25 nozzle
printer_model = MK2.5
remaining_times = 1
-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
+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
[printer:Original Prusa i3 MK2.5 0.6 nozzle]
inherits = Original Prusa i3 MK2 0.6 nozzle
printer_model = MK2.5
remaining_times = 1
-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
+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
# XXXXXXXXXXXXXXXXX
# XXX--- MK3 ---XXX
@@ -1224,8 +1384,8 @@ machine_max_acceleration_x = 1000,960
machine_max_acceleration_y = 1000,960
machine_max_acceleration_z = 1000,1000
machine_max_feedrate_e = 120,120
-machine_max_feedrate_x = 200,172
-machine_max_feedrate_y = 200,172
+machine_max_feedrate_x = 200,100
+machine_max_feedrate_y = 200,100
machine_max_feedrate_z = 12,12
machine_max_jerk_e = 1.5,1.5
machine_max_jerk_x = 8,8
@@ -1238,7 +1398,7 @@ remaining_times = 1
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
retract_lift_below = 209
max_print_height = 210
-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}
+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}
printer_model = MK3
default_print_profile = 0.15mm OPTIMAL MK3
@@ -1268,11 +1428,12 @@ retract_length_toolchange = 3
extra_loading_move = -13
printer_model = MK3MMU2
default_print_profile = 0.15mm OPTIMAL MK3
-default_filament_profile = Prusa PLA MMU2
+default_filament_profile = Prusament PLA MMU2
[printer:Original Prusa i3 MK3 MMU2 Single]
inherits = *mm2*
-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
+single_extruder_multi_material = 0
+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
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
[printer:Original Prusa i3 MK3 MMU2]
@@ -1283,10 +1444,10 @@ inherits = *mm2*
machine_max_acceleration_e = 8000,8000
nozzle_diameter = 0.4,0.4,0.4,0.4,0.4
extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F
-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
+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
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
# 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.
[obsolete_presets]
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"
-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"
+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" \ No newline at end of file
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index ec24d5fee..aec0a704c 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -271,11 +271,10 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/Utils/Time.hpp
${LIBDIR}/slic3r/Utils/HexFile.cpp
${LIBDIR}/slic3r/Utils/HexFile.hpp
- ${LIBDIR}/slic3r/IProgressIndicator.hpp
+ ${LIBDIR}/slic3r/ProgressIndicator.hpp
${LIBDIR}/slic3r/AppController.hpp
${LIBDIR}/slic3r/AppController.cpp
${LIBDIR}/slic3r/AppControllerWx.cpp
- ${LIBDIR}/slic3r/Strings.hpp
)
add_library(admesh STATIC
@@ -752,6 +751,7 @@ add_custom_target(pot
set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
add_subdirectory(${LIBDIR}/libnest2d)
+target_compile_definitions(libslic3r PUBLIC -DUSE_TBB)
target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
target_include_directories(libslic3r_gui PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt
index 835e8311d..f81355012 100644
--- a/xs/src/libnest2d/CMakeLists.txt
+++ b/xs/src/libnest2d/CMakeLists.txt
@@ -31,6 +31,7 @@ set(LIBNEST2D_SRCFILES
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/rotfinder.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp
@@ -89,14 +90,39 @@ if(LIBNEST2D_UNITTESTS)
endif()
if(LIBNEST2D_BUILD_EXAMPLES)
+
add_executable(example examples/main.cpp
# tools/libnfpglue.hpp
# tools/libnfpglue.cpp
+ tools/nfp_svgnest.hpp
+ tools/nfp_svgnest_glue.hpp
tools/svgtools.hpp
tests/printer_parts.cpp
tests/printer_parts.h
- ${LIBNEST2D_SRCFILES})
-
+ ${LIBNEST2D_SRCFILES}
+ )
+ set(TBB_STATIC ON)
+ find_package(TBB QUIET)
+ if(TBB_FOUND)
+ message(STATUS "Parallelization with Intel TBB")
+ target_include_directories(example PUBLIC ${TBB_INCLUDE_DIRS})
+ target_compile_definitions(example PUBLIC ${TBB_DEFINITIONS} -DUSE_TBB)
+ if(MSVC)
+ # Suppress implicit linking of the TBB libraries by the Visual Studio compiler.
+ target_compile_definitions(example PUBLIC -D__TBB_NO_IMPLICIT_LINKAGE)
+ endif()
+ # The Intel TBB library will use the std::exception_ptr feature of C++11.
+ target_compile_definitions(example PUBLIC -DTBB_USE_CAPTURED_EXCEPTION=1)
+
+ target_link_libraries(example ${TBB_LIBRARIES})
+ else()
+ find_package(OpenMP QUIET)
+ if(OpenMP_CXX_FOUND)
+ message(STATUS "Parallelization with OpenMP")
+ target_include_directories(example PUBLIC OpenMP::OpenMP_CXX)
+ target_link_libraries(example OpenMP::OpenMP_CXX)
+ endif()
+ endif()
target_link_libraries(example ${LIBNEST2D_LIBRARIES})
target_include_directories(example PUBLIC ${LIBNEST2D_HEADERS})
diff --git a/xs/src/libnest2d/README.md b/xs/src/libnest2d/README.md
index 3508801a8..61a7ac7d0 100644
--- a/xs/src/libnest2d/README.md
+++ b/xs/src/libnest2d/README.md
@@ -9,18 +9,28 @@ with templated geometry types. These geometries can have custom or already
existing implementation to avoid copying or having unnecessary dependencies.
A default backend is provided if the user of the library just wants to use it
-out of the box without additional integration. The default backend is reasonably
+out of the box without additional integration. This backend is reasonably
fast and robust, being built on top of boost geometry and the
[polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of
-this default backend implies the dependency on these packages as well as the
-compilation of the backend itself (The default backend is not yet header only).
+this default backend implies the dependency on these packages but its header
+only as well.
This software is currently under construction and lacks a throughout
documentation and some essential algorithms as well. At this stage it works well
for rectangles and convex closed polygons without considering holes and
concavities.
-Holes and non-convex polygons will be usable in the near future as well.
+Holes and non-convex polygons will be usable in the near future as well. The
+no fit polygon based placer module combined with the first fit selection
+strategy is now used in the [Slic3r](https://github.com/prusa3d/Slic3r)
+application's arrangement feature. It uses local optimization techniques to find
+the best placement of each new item based on some features of the arrangement.
+
+In the near future I would like to use machine learning to evaluate the
+placements and (or) the order if items in which they are placed and see what
+results can be obtained. This is a different approach than that of SVGnest which
+uses genetic algorithms to find better and better selection orders. Maybe the
+two approaches can be combined as well.
# References
- [SVGNest](https://github.com/Jack000/SVGnest)
diff --git a/xs/src/libnest2d/cmake_modules/FindTBB.cmake b/xs/src/libnest2d/cmake_modules/FindTBB.cmake
new file mode 100644
index 000000000..8b498d3ab
--- /dev/null
+++ b/xs/src/libnest2d/cmake_modules/FindTBB.cmake
@@ -0,0 +1,322 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2015 Justus Calvin
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+#
+# FindTBB
+# -------
+#
+# Find TBB include directories and libraries.
+#
+# Usage:
+#
+# find_package(TBB [major[.minor]] [EXACT]
+# [QUIET] [REQUIRED]
+# [[COMPONENTS] [components...]]
+# [OPTIONAL_COMPONENTS components...])
+#
+# where the allowed components are tbbmalloc and tbb_preview. Users may modify
+# the behavior of this module with the following variables:
+#
+# * TBB_ROOT_DIR - The base directory the of TBB installation.
+# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files.
+# * TBB_LIBRARY - The directory that contains the TBB library files.
+# * TBB_<library>_LIBRARY - The path of the TBB the corresponding TBB library.
+# These libraries, if specified, override the
+# corresponding library search results, where <library>
+# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug,
+# tbb_preview, or tbb_preview_debug.
+# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will
+# be used instead of the release version.
+# * TBB_STATIC - Static linking of libraries with a _static suffix.
+# For example, on Windows a tbb_static.lib will be searched for
+# instead of tbb.lib.
+#
+# Users may modify the behavior of this module with the following environment
+# variables:
+#
+# * TBB_INSTALL_DIR
+# * TBBROOT
+# * LIBRARY_PATH
+#
+# This module will set the following variables:
+#
+# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or
+# don’t want to use TBB.
+# * TBB_<component>_FOUND - If False, optional <component> part of TBB sytem is
+# not available.
+# * TBB_VERSION - The full version string
+# * TBB_VERSION_MAJOR - The major version
+# * TBB_VERSION_MINOR - The minor version
+# * TBB_INTERFACE_VERSION - The interface version number defined in
+# tbb/tbb_stddef.h.
+# * TBB_<library>_LIBRARY_RELEASE - The path of the TBB release version of
+# <library>, where <library> may be tbb, tbb_debug,
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
+# tbb_preview_debug.
+# * TBB_<library>_LIBRARY_DEGUG - The path of the TBB release version of
+# <library>, where <library> may be tbb, tbb_debug,
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
+# tbb_preview_debug.
+#
+# The following varibles should be used to build and link with TBB:
+#
+# * TBB_INCLUDE_DIRS - The include directory for TBB.
+# * TBB_LIBRARIES - The libraries to link against to use TBB.
+# * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB.
+# * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB.
+# * TBB_DEFINITIONS - Definitions to use when compiling code that uses
+# TBB.
+# * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that
+# uses TBB.
+# * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that
+# uses TBB.
+#
+# This module will also create the "tbb" target that may be used when building
+# executables and libraries.
+
+include(FindPackageHandleStandardArgs)
+
+if(NOT TBB_FOUND)
+
+ ##################################
+ # Check the build type
+ ##################################
+
+ if(NOT DEFINED TBB_USE_DEBUG_BUILD)
+ if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)")
+ set(TBB_BUILD_TYPE DEBUG)
+ else()
+ set(TBB_BUILD_TYPE RELEASE)
+ endif()
+ elseif(TBB_USE_DEBUG_BUILD)
+ set(TBB_BUILD_TYPE DEBUG)
+ else()
+ set(TBB_BUILD_TYPE RELEASE)
+ endif()
+
+ ##################################
+ # Set the TBB search directories
+ ##################################
+
+ # Define search paths based on user input and environment variables
+ set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
+
+ # Define the search directories based on the current platform
+ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
+ "C:/Program Files (x86)/Intel/TBB")
+
+ # Set the target architecture
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(TBB_ARCHITECTURE "intel64")
+ else()
+ set(TBB_ARCHITECTURE "ia32")
+ endif()
+
+ # Set the TBB search library path search suffix based on the version of VC
+ if(WINDOWS_STORE)
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui")
+ elseif(MSVC14)
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14")
+ elseif(MSVC12)
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12")
+ elseif(MSVC11)
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11")
+ elseif(MSVC10)
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10")
+ endif()
+
+ # Add the library path search suffix for the VC independent version of TBB
+ list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt")
+
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ # OS X
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
+
+ # TODO: Check to see which C++ library is being used by the compiler.
+ if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
+ # The default C++ library on OS X 10.9 and later is libc++
+ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
+ else()
+ set(TBB_LIB_PATH_SUFFIX "lib")
+ endif()
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ # Linux
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
+
+ # TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or
+ # <arch>/gcc4.1. For now, assume that the compiler is more recent than
+ # gcc 4.4.x or later.
+ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4")
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
+ set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4")
+ endif()
+ endif()
+
+ ##################################
+ # Find the TBB include dir
+ ##################################
+
+ find_path(TBB_INCLUDE_DIRS tbb/tbb.h
+ HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR}
+ PATH_SUFFIXES include)
+
+ ##################################
+ # Set version strings
+ ##################################
+
+ if(TBB_INCLUDE_DIRS)
+ file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
+ string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
+ TBB_VERSION_MAJOR "${_tbb_version_file}")
+ string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
+ TBB_VERSION_MINOR "${_tbb_version_file}")
+ string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
+ TBB_INTERFACE_VERSION "${_tbb_version_file}")
+ set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
+ endif()
+
+ ##################################
+ # Find TBB components
+ ##################################
+
+ if(TBB_VERSION VERSION_LESS 4.3)
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
+ else()
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
+ endif()
+
+ if(TBB_STATIC)
+ set(TBB_STATIC_SUFFIX "_static")
+ endif()
+
+ # Find each component
+ foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
+ if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
+
+ # Search for the libraries
+ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
+
+ find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
+
+ if(TBB_${_comp}_LIBRARY_DEBUG)
+ list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
+ endif()
+ if(TBB_${_comp}_LIBRARY_RELEASE)
+ list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
+ endif()
+ if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
+ set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
+ endif()
+
+ if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
+ set(TBB_${_comp}_FOUND TRUE)
+ else()
+ set(TBB_${_comp}_FOUND FALSE)
+ endif()
+
+ # Mark internal variables as advanced
+ mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
+ mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
+ mark_as_advanced(TBB_${_comp}_LIBRARY)
+
+ endif()
+ endforeach()
+
+ unset(TBB_STATIC_SUFFIX)
+
+ ##################################
+ # Set compile flags and libraries
+ ##################################
+
+ set(TBB_DEFINITIONS_RELEASE "")
+ set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1")
+
+ if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
+ set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}")
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
+ elseif(TBB_LIBRARIES_RELEASE)
+ set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}")
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}")
+ elseif(TBB_LIBRARIES_DEBUG)
+ set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}")
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}")
+ endif()
+
+ find_package_handle_standard_args(TBB
+ REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
+ HANDLE_COMPONENTS
+ VERSION_VAR TBB_VERSION)
+
+ ##################################
+ # Create targets
+ ##################################
+
+ if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
+ add_library(tbb SHARED IMPORTED)
+ set_target_properties(tbb PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
+ IMPORTED_LOCATION ${TBB_LIBRARIES})
+ if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
+ set_target_properties(tbb PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:TBB_USE_DEBUG=1>"
+ IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
+ IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG}
+ IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
+ IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
+ )
+ elseif(TBB_LIBRARIES_RELEASE)
+ set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE})
+ else()
+ set_target_properties(tbb PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}"
+ IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG}
+ )
+ endif()
+ endif()
+
+ mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
+
+ unset(TBB_ARCHITECTURE)
+ unset(TBB_BUILD_TYPE)
+ unset(TBB_LIB_PATH_SUFFIX)
+ unset(TBB_DEFAULT_SEARCH_DIR)
+
+ if(TBB_DEBUG)
+ message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
+ message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
+ message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
+ message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
+ message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
+ message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
+ message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
+ endif()
+
+endif()
diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp
index d6b2ccc34..ebc3fb15c 100644
--- a/xs/src/libnest2d/examples/main.cpp
+++ b/xs/src/libnest2d/examples/main.cpp
@@ -1,7 +1,6 @@
#include <iostream>
#include <string>
#include <fstream>
-
//#define DEBUG_EXPORT_NFP
#include <libnest2d.h>
@@ -9,7 +8,11 @@
#include "tests/printer_parts.h"
#include "tools/benchmark.h"
#include "tools/svgtools.hpp"
+#include "libnest2d/rotfinder.hpp"
+
//#include "tools/libnfpglue.hpp"
+//#include "tools/nfp_svgnest_glue.hpp"
+
using namespace libnest2d;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
@@ -50,499 +53,57 @@ void arrangeRectangles() {
using namespace libnest2d;
const int SCALE = 1000000;
-// const int SCALE = 1;
- std::vector<Rectangle> rects = {
- {80*SCALE, 80*SCALE},
- {60*SCALE, 90*SCALE},
- {70*SCALE, 30*SCALE},
- {80*SCALE, 60*SCALE},
- {60*SCALE, 60*SCALE},
- {60*SCALE, 40*SCALE},
- {40*SCALE, 40*SCALE},
- {10*SCALE, 10*SCALE},
- {10*SCALE, 10*SCALE},
- {10*SCALE, 10*SCALE},
- {10*SCALE, 10*SCALE},
- {10*SCALE, 10*SCALE},
- {5*SCALE, 5*SCALE},
- {5*SCALE, 5*SCALE},
- {5*SCALE, 5*SCALE},
- {5*SCALE, 5*SCALE},
- {5*SCALE, 5*SCALE},
- {5*SCALE, 5*SCALE},
- {5*SCALE, 5*SCALE},
- {20*SCALE, 20*SCALE}
- };
-
-// std::vector<Rectangle> rects = {
-// {20*SCALE, 10*SCALE},
-// {20*SCALE, 10*SCALE},
-// {20*SCALE, 20*SCALE},
-// };
-// std::vector<Item> input {
-// {{0, 0}, {0, 20*SCALE}, {10*SCALE, 0}, {0, 0}}
-// };
-
- std::vector<Item> crasher =
- {
- {
- {-5000000, 8954050},
- {5000000, 8954050},
- {5000000, -45949},
- {4972609, -568549},
- {3500000, -8954050},
- {-3500000, -8954050},
- {-4972609, -568549},
- {-5000000, -45949},
- {-5000000, 8954050},
- },
- {
- {-5000000, 8954050},
- {5000000, 8954050},
- {5000000, -45949},
- {4972609, -568549},
- {3500000, -8954050},
- {-3500000, -8954050},
- {-4972609, -568549},
- {-5000000, -45949},
- {-5000000, 8954050},
- },
- {
- {-5000000, 8954050},
- {5000000, 8954050},
- {5000000, -45949},
- {4972609, -568549},
- {3500000, -8954050},
- {-3500000, -8954050},
- {-4972609, -568549},
- {-5000000, -45949},
- {-5000000, 8954050},
- },
- {
- {-5000000, 8954050},
- {5000000, 8954050},
- {5000000, -45949},
- {4972609, -568549},
- {3500000, -8954050},
- {-3500000, -8954050},
- {-4972609, -568549},
- {-5000000, -45949},
- {-5000000, 8954050},
- },
- {
- {-5000000, 8954050},
- {5000000, 8954050},
- {5000000, -45949},
- {4972609, -568549},
- {3500000, -8954050},
- {-3500000, -8954050},
- {-4972609, -568549},
- {-5000000, -45949},
- {-5000000, 8954050},
- },
- {
- {-5000000, 8954050},
- {5000000, 8954050},
- {5000000, -45949},
- {4972609, -568549},
- {3500000, -8954050},
- {-3500000, -8954050},
- {-4972609, -568549},
- {-5000000, -45949},
- {-5000000, 8954050},
- },
- {
- {-9945219, -3065619},
- {-9781479, -2031780},
- {-9510560, -1020730},
- {-9135450, -43529},
- {-2099999, 14110899},
- {2099999, 14110899},
- {9135450, -43529},
- {9510560, -1020730},
- {9781479, -2031780},
- {9945219, -3065619},
- {10000000, -4110899},
- {9945219, -5156179},
- {9781479, -6190020},
- {9510560, -7201069},
- {9135450, -8178270},
- {8660249, -9110899},
- {8090169, -9988750},
- {7431449, -10802200},
- {6691309, -11542300},
- {5877850, -12201100},
- {5000000, -12771100},
- {4067369, -13246399},
- {3090169, -13621500},
- {2079119, -13892399},
- {1045279, -14056099},
- {0, -14110899},
- {-1045279, -14056099},
- {-2079119, -13892399},
- {-3090169, -13621500},
- {-4067369, -13246399},
- {-5000000, -12771100},
- {-5877850, -12201100},
- {-6691309, -11542300},
- {-7431449, -10802200},
- {-8090169, -9988750},
- {-8660249, -9110899},
- {-9135450, -8178270},
- {-9510560, -7201069},
- {-9781479, -6190020},
- {-9945219, -5156179},
- {-10000000, -4110899},
- {-9945219, -3065619},
- },
- {
- {-9945219, -3065619},
- {-9781479, -2031780},
- {-9510560, -1020730},
- {-9135450, -43529},
- {-2099999, 14110899},
- {2099999, 14110899},
- {9135450, -43529},
- {9510560, -1020730},
- {9781479, -2031780},
- {9945219, -3065619},
- {10000000, -4110899},
- {9945219, -5156179},
- {9781479, -6190020},
- {9510560, -7201069},
- {9135450, -8178270},
- {8660249, -9110899},
- {8090169, -9988750},
- {7431449, -10802200},
- {6691309, -11542300},
- {5877850, -12201100},
- {5000000, -12771100},
- {4067369, -13246399},
- {3090169, -13621500},
- {2079119, -13892399},
- {1045279, -14056099},
- {0, -14110899},
- {-1045279, -14056099},
- {-2079119, -13892399},
- {-3090169, -13621500},
- {-4067369, -13246399},
- {-5000000, -12771100},
- {-5877850, -12201100},
- {-6691309, -11542300},
- {-7431449, -10802200},
- {-8090169, -9988750},
- {-8660249, -9110899},
- {-9135450, -8178270},
- {-9510560, -7201069},
- {-9781479, -6190020},
- {-9945219, -5156179},
- {-10000000, -4110899},
- {-9945219, -3065619},
- },
- {
- {-9945219, -3065619},
- {-9781479, -2031780},
- {-9510560, -1020730},
- {-9135450, -43529},
- {-2099999, 14110899},
- {2099999, 14110899},
- {9135450, -43529},
- {9510560, -1020730},
- {9781479, -2031780},
- {9945219, -3065619},
- {10000000, -4110899},
- {9945219, -5156179},
- {9781479, -6190020},
- {9510560, -7201069},
- {9135450, -8178270},
- {8660249, -9110899},
- {8090169, -9988750},
- {7431449, -10802200},
- {6691309, -11542300},
- {5877850, -12201100},
- {5000000, -12771100},
- {4067369, -13246399},
- {3090169, -13621500},
- {2079119, -13892399},
- {1045279, -14056099},
- {0, -14110899},
- {-1045279, -14056099},
- {-2079119, -13892399},
- {-3090169, -13621500},
- {-4067369, -13246399},
- {-5000000, -12771100},
- {-5877850, -12201100},
- {-6691309, -11542300},
- {-7431449, -10802200},
- {-8090169, -9988750},
- {-8660249, -9110899},
- {-9135450, -8178270},
- {-9510560, -7201069},
- {-9781479, -6190020},
- {-9945219, -5156179},
- {-10000000, -4110899},
- {-9945219, -3065619},
- },
- {
- {-9945219, -3065619},
- {-9781479, -2031780},
- {-9510560, -1020730},
- {-9135450, -43529},
- {-2099999, 14110899},
- {2099999, 14110899},
- {9135450, -43529},
- {9510560, -1020730},
- {9781479, -2031780},
- {9945219, -3065619},
- {10000000, -4110899},
- {9945219, -5156179},
- {9781479, -6190020},
- {9510560, -7201069},
- {9135450, -8178270},
- {8660249, -9110899},
- {8090169, -9988750},
- {7431449, -10802200},
- {6691309, -11542300},
- {5877850, -12201100},
- {5000000, -12771100},
- {4067369, -13246399},
- {3090169, -13621500},
- {2079119, -13892399},
- {1045279, -14056099},
- {0, -14110899},
- {-1045279, -14056099},
- {-2079119, -13892399},
- {-3090169, -13621500},
- {-4067369, -13246399},
- {-5000000, -12771100},
- {-5877850, -12201100},
- {-6691309, -11542300},
- {-7431449, -10802200},
- {-8090169, -9988750},
- {-8660249, -9110899},
- {-9135450, -8178270},
- {-9510560, -7201069},
- {-9781479, -6190020},
- {-9945219, -5156179},
- {-10000000, -4110899},
- {-9945219, -3065619},
- },
- {
- {-9945219, -3065619},
- {-9781479, -2031780},
- {-9510560, -1020730},
- {-9135450, -43529},
- {-2099999, 14110899},
- {2099999, 14110899},
- {9135450, -43529},
- {9510560, -1020730},
- {9781479, -2031780},
- {9945219, -3065619},
- {10000000, -4110899},
- {9945219, -5156179},
- {9781479, -6190020},
- {9510560, -7201069},
- {9135450, -8178270},
- {8660249, -9110899},
- {8090169, -9988750},
- {7431449, -10802200},
- {6691309, -11542300},
- {5877850, -12201100},
- {5000000, -12771100},
- {4067369, -13246399},
- {3090169, -13621500},
- {2079119, -13892399},
- {1045279, -14056099},
- {0, -14110899},
- {-1045279, -14056099},
- {-2079119, -13892399},
- {-3090169, -13621500},
- {-4067369, -13246399},
- {-5000000, -12771100},
- {-5877850, -12201100},
- {-6691309, -11542300},
- {-7431449, -10802200},
- {-8090169, -9988750},
- {-8660249, -9110899},
- {-9135450, -8178270},
- {-9510560, -7201069},
- {-9781479, -6190020},
- {-9945219, -5156179},
- {-10000000, -4110899},
- {-9945219, -3065619},
- },
- {
- {-9945219, -3065619},
- {-9781479, -2031780},
- {-9510560, -1020730},
- {-9135450, -43529},
- {-2099999, 14110899},
- {2099999, 14110899},
- {9135450, -43529},
- {9510560, -1020730},
- {9781479, -2031780},
- {9945219, -3065619},
- {10000000, -4110899},
- {9945219, -5156179},
- {9781479, -6190020},
- {9510560, -7201069},
- {9135450, -8178270},
- {8660249, -9110899},
- {8090169, -9988750},
- {7431449, -10802200},
- {6691309, -11542300},
- {5877850, -12201100},
- {5000000, -12771100},
- {4067369, -13246399},
- {3090169, -13621500},
- {2079119, -13892399},
- {1045279, -14056099},
- {0, -14110899},
- {-1045279, -14056099},
- {-2079119, -13892399},
- {-3090169, -13621500},
- {-4067369, -13246399},
- {-5000000, -12771100},
- {-5877850, -12201100},
- {-6691309, -11542300},
- {-7431449, -10802200},
- {-8090169, -9988750},
- {-8660249, -9110899},
- {-9135450, -8178270},
- {-9510560, -7201069},
- {-9781479, -6190020},
- {-9945219, -5156179},
- {-10000000, -4110899},
- {-9945219, -3065619},
- },
- {
- {-9945219, -3065619},
- {-9781479, -2031780},
- {-9510560, -1020730},
- {-9135450, -43529},
- {-2099999, 14110899},
- {2099999, 14110899},
- {9135450, -43529},
- {9510560, -1020730},
- {9781479, -2031780},
- {9945219, -3065619},
- {10000000, -4110899},
- {9945219, -5156179},
- {9781479, -6190020},
- {9510560, -7201069},
- {9135450, -8178270},
- {8660249, -9110899},
- {8090169, -9988750},
- {7431449, -10802200},
- {6691309, -11542300},
- {5877850, -12201100},
- {5000000, -12771100},
- {4067369, -13246399},
- {3090169, -13621500},
- {2079119, -13892399},
- {1045279, -14056099},
- {0, -14110899},
- {-1045279, -14056099},
- {-2079119, -13892399},
- {-3090169, -13621500},
- {-4067369, -13246399},
- {-5000000, -12771100},
- {-5877850, -12201100},
- {-6691309, -11542300},
- {-7431449, -10802200},
- {-8090169, -9988750},
- {-8660249, -9110899},
- {-9135450, -8178270},
- {-9510560, -7201069},
- {-9781479, -6190020},
- {-9945219, -5156179},
- {-10000000, -4110899},
- {-9945219, -3065619},
- },
- {
- {-9945219, -3065619},
- {-9781479, -2031780},
- {-9510560, -1020730},
- {-9135450, -43529},
- {-2099999, 14110899},
- {2099999, 14110899},
- {9135450, -43529},
- {9510560, -1020730},
- {9781479, -2031780},
- {9945219, -3065619},
- {10000000, -4110899},
- {9945219, -5156179},
- {9781479, -6190020},
- {9510560, -7201069},
- {9135450, -8178270},
- {8660249, -9110899},
- {8090169, -9988750},
- {7431449, -10802200},
- {6691309, -11542300},
- {5877850, -12201100},
- {5000000, -12771100},
- {4067369, -13246399},
- {3090169, -13621500},
- {2079119, -13892399},
- {1045279, -14056099},
- {0, -14110899},
- {-1045279, -14056099},
- {-2079119, -13892399},
- {-3090169, -13621500},
- {-4067369, -13246399},
- {-5000000, -12771100},
- {-5877850, -12201100},
- {-6691309, -11542300},
- {-7431449, -10802200},
- {-8090169, -9988750},
- {-8660249, -9110899},
- {-9135450, -8178270},
- {-9510560, -7201069},
- {-9781479, -6190020},
- {-9945219, -5156179},
- {-10000000, -4110899},
- {-9945219, -3065619},
- },
- {
- {-18000000, -1000000},
- {-15000000, 22000000},
- {-11000000, 26000000},
- {11000000, 26000000},
- {15000000, 22000000},
- {18000000, -1000000},
- {18000000, -26000000},
- {-18000000, -26000000},
- {-18000000, -1000000},
- },
- };
+ std::vector<Item> rects(202, {
+ {-9945219, -3065619},
+ {-9781479, -2031780},
+ {-9510560, -1020730},
+ {-9135450, -43529},
+ {-2099999, 14110899},
+ {2099999, 14110899},
+ {9135450, -43529},
+ {9510560, -1020730},
+ {9781479, -2031780},
+ {9945219, -3065619},
+ {10000000, -4110899},
+ {9945219, -5156179},
+ {9781479, -6190019},
+ {9510560, -7201069},
+ {9135450, -8178270},
+ {8660249, -9110899},
+ {8090169, -9988750},
+ {7431449, -10802209},
+ {6691309, -11542349},
+ {5877850, -12201069},
+ {5000000, -12771149},
+ {4067369, -13246350},
+ {3090169, -13621459},
+ {2079119, -13892379},
+ {1045279, -14056119},
+ {0, -14110899},
+ {-1045279, -14056119},
+ {-2079119, -13892379},
+ {-3090169, -13621459},
+ {-4067369, -13246350},
+ {-5000000, -12771149},
+ {-5877850, -12201069},
+ {-6691309, -11542349},
+ {-7431449, -10802209},
+ {-8090169, -9988750},
+ {-8660249, -9110899},
+ {-9135450, -8178270},
+ {-9510560, -7201069},
+ {-9781479, -6190019},
+ {-9945219, -5156179},
+ {-10000000, -4110899},
+ {-9945219, -3065619},
+ });
- std::vector<Item> proba = {
- {
- Rectangle(100, 2)
- },
- {
- Rectangle(100, 2)
- },
- {
- Rectangle(100, 2)
- },
- {
- Rectangle(10, 10)
- },
- };
-
- proba[0].rotate(Pi/3);
- proba[1].rotate(Pi-Pi/3);
-
-// std::vector<Item> input(25, Rectangle(70*SCALE, 10*SCALE));
std::vector<Item> input;
input.insert(input.end(), prusaParts().begin(), prusaParts().end());
// input.insert(input.end(), prusaExParts().begin(), prusaExParts().end());
// input.insert(input.end(), stegoParts().begin(), stegoParts().end());
// input.insert(input.end(), rects.begin(), rects.end());
-// input.insert(input.end(), proba.begin(), proba.end());
-// input.insert(input.end(), crasher.begin(), crasher.end());
Box bin(250*SCALE, 210*SCALE);
// PolygonImpl bin = {
@@ -560,10 +121,12 @@ void arrangeRectangles() {
// {}
// };
- auto min_obj_distance = static_cast<Coord>(0*SCALE);
+// Circle bin({0, 0}, 125*SCALE);
- using Placer = strategies::_NofitPolyPlacer<PolygonImpl, Box>;
- using Packer = Arranger<Placer, FirstFitSelection>;
+ auto min_obj_distance = static_cast<Coord>(6*SCALE);
+
+ using Placer = placers::_NofitPolyPlacer<PolygonImpl, decltype(bin)>;
+ using Packer = Nester<Placer, FirstFitSelection>;
Packer arrange(bin, min_obj_distance);
@@ -571,121 +134,23 @@ void arrangeRectangles() {
pconf.alignment = Placer::Config::Alignment::CENTER;
pconf.starting_point = Placer::Config::Alignment::CENTER;
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
- pconf.accuracy = 0.5f;
-
-// auto bincenter = ShapeLike::boundingBox(bin).center();
-// pconf.object_function = [&bin, bincenter](
-// Placer::Pile pile, const Item& item,
-// double /*area*/, double norm, double penality) {
-
-// using pl = PointLike;
-
-// static const double BIG_ITEM_TRESHOLD = 0.2;
-// static const double GRAVITY_RATIO = 0.5;
-// static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
-
-// // We will treat big items (compared to the print bed) differently
-// NfpPlacer::Pile bigs;
-// bigs.reserve(pile.size());
-// for(auto& p : pile) {
-// auto pbb = ShapeLike::boundingBox(p);
-// auto na = std::sqrt(pbb.width()*pbb.height())/norm;
-// if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
-// }
-
-// // Candidate item bounding box
-// auto ibb = item.boundingBox();
-
-// // Calculate the full bounding box of the pile with the candidate item
-// pile.emplace_back(item.transformedShape());
-// auto fullbb = ShapeLike::boundingBox(pile);
-// pile.pop_back();
-
-// // The bounding box of the big items (they will accumulate in the center
-// // of the pile
-// auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
-
-// // The size indicator of the candidate item. This is not the area,
-// // but almost...
-// auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
-
-// // Will hold the resulting score
-// double score = 0;
-
-// if(itemnormarea > BIG_ITEM_TRESHOLD) {
-// // This branch is for the bigger items..
-// // Here we will use the closest point of the item bounding box to
-// // the already arranged pile. So not the bb center nor the a choosen
-// // corner but whichever is the closest to the center. This will
-// // prevent unwanted strange arrangements.
-
-// auto minc = ibb.minCorner(); // bottom left corner
-// auto maxc = ibb.maxCorner(); // top right corner
-
-// // top left and bottom right corners
-// auto top_left = PointImpl{getX(minc), getY(maxc)};
-// auto bottom_right = PointImpl{getX(maxc), getY(minc)};
-
-// auto cc = fullbb.center(); // The gravity center
-
-// // Now the distnce of the gravity center will be calculated to the
-// // five anchor points and the smallest will be chosen.
-// std::array<double, 5> dists;
-// dists[0] = pl::distance(minc, cc);
-// dists[1] = pl::distance(maxc, cc);
-// dists[2] = pl::distance(ibb.center(), cc);
-// dists[3] = pl::distance(top_left, cc);
-// dists[4] = pl::distance(bottom_right, cc);
-
-// auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
-
-// // Density is the pack density: how big is the arranged pile
-// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
-
-// // The score is a weighted sum of the distance from pile center
-// // and the pile size
-// score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
-
-// } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
-// // If there are no big items, only small, we should consider the
-// // density here as well to not get silly results
-// auto bindist = pl::distance(ibb.center(), bincenter) / norm;
-// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
-// score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
-// } else {
-// // Here there are the small items that should be placed around the
-// // already processed bigger items.
-// // No need to play around with the anchor points, the center will be
-// // just fine for small items
-// score = pl::distance(ibb.center(), bigbb.center()) / norm;
-// }
-
-// // If it does not fit into the print bed we will beat it
-// // with a large penality. If we would not do this, there would be only
-// // one big pile that doesn't care whether it fits onto the print bed.
-// if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
-
-// return score;
-// };
+ pconf.accuracy = 0.65f;
+ pconf.parallel = true;
Packer::SelectionConfig sconf;
// sconf.allow_parallel = false;
// sconf.force_parallel = false;
// sconf.try_triplets = true;
// sconf.try_reverse_order = true;
-// sconf.waste_increment = 0.005;
+// sconf.waste_increment = 0.01;
arrange.configure(pconf, sconf);
arrange.progressIndicator([&](unsigned r){
-// svg::SVGWriter::Config conf;
-// conf.mm_in_coord_units = SCALE;
-// svg::SVGWriter svgw(conf);
-// svgw.setSize(bin);
-// svgw.writePackGroup(arrange.lastResult());
-// svgw.save("debout");
std::cout << "Remaining items: " << r << std::endl;
- })/*.useMinimumBoundigBoxRotation()*/;
+ });
+
+// findMinimumBoundingBoxRotations(input.begin(), input.end());
Benchmark bench;
@@ -693,7 +158,7 @@ void arrangeRectangles() {
Packer::ResultType result;
try {
- result = arrange.arrange(input.begin(), input.end());
+ result = arrange.execute(input.begin(), input.end());
} catch(GeometryException& ge) {
std::cerr << "Geometry error: " << ge.what() << std::endl;
return ;
@@ -707,7 +172,7 @@ void arrangeRectangles() {
std::vector<double> eff;
eff.reserve(result.size());
- auto bin_area = ShapeLike::area<PolygonImpl>(bin);
+ auto bin_area = sl::area(bin);
for(auto& r : result) {
double a = 0;
std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); });
@@ -728,10 +193,10 @@ void arrangeRectangles() {
for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); }
std::cout << ") Total: " << total << std::endl;
- for(auto& it : input) {
- auto ret = ShapeLike::isValid(it.transformedShape());
- std::cout << ret.second << std::endl;
- }
+// for(auto& it : input) {
+// auto ret = sl::isValid(it.transformedShape());
+// std::cout << ret.second << std::endl;
+// }
if(total != input.size()) std::cout << "ERROR " << "could not pack "
<< input.size() - total << " elements!"
@@ -744,13 +209,10 @@ void arrangeRectangles() {
SVGWriter svgw(conf);
svgw.setSize(Box(250*SCALE, 210*SCALE));
svgw.writePackGroup(result);
-// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);});
svgw.save("out");
}
int main(void /*int argc, char **argv*/) {
arrangeRectangles();
-// findDegenerateCase();
-
return EXIT_SUCCESS;
}
diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h
index c9e21ecfb..bfd88f4f5 100644
--- a/xs/src/libnest2d/libnest2d.h
+++ b/xs/src/libnest2d/libnest2d.h
@@ -22,6 +22,7 @@ using Point = PointImpl;
using Coord = TCoord<PointImpl>;
using Box = _Box<PointImpl>;
using Segment = _Segment<PointImpl>;
+using Circle = _Circle<PointImpl>;
using Item = _Item<PolygonImpl>;
using Rectangle = _Rectangle<PolygonImpl>;
@@ -29,15 +30,12 @@ using Rectangle = _Rectangle<PolygonImpl>;
using PackGroup = _PackGroup<PolygonImpl>;
using IndexedPackGroup = _IndexedPackGroup<PolygonImpl>;
-using FillerSelection = strategies::_FillerSelection<PolygonImpl>;
-using FirstFitSelection = strategies::_FirstFitSelection<PolygonImpl>;
-using DJDHeuristic = strategies::_DJDHeuristic<PolygonImpl>;
+using FillerSelection = selections::_FillerSelection<PolygonImpl>;
+using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>;
+using DJDHeuristic = selections::_DJDHeuristic<PolygonImpl>;
-using NfpPlacer = strategies::_NofitPolyPlacer<PolygonImpl>;
-using BottomLeftPlacer = strategies::_BottomLeftPlacer<PolygonImpl>;
-
-//template<NfpLevel lvl = NfpLevel::BOTH_CONCAVE_WITH_HOLES>
-//using NofitPolyPlacer = strategies::_NofitPolyPlacer<PolygonImpl, lvl>;
+using NfpPlacer = placers::_NofitPolyPlacer<PolygonImpl>;
+using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
}
diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp
index 67e19fcbd..bb0403b06 100644
--- a/xs/src/libnest2d/libnest2d/boost_alg.hpp
+++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp
@@ -36,7 +36,7 @@ using libnest2d::setX;
using libnest2d::setY;
using Box = libnest2d::_Box<PointImpl>;
using Segment = libnest2d::_Segment<PointImpl>;
-using Shapes = libnest2d::Nfp::Shapes<PolygonImpl>;
+using Shapes = libnest2d::nfp::Shapes<PolygonImpl>;
}
@@ -241,11 +241,11 @@ template<> struct tag<bp2d::PolygonImpl> {
template<> struct exterior_ring<bp2d::PolygonImpl> {
static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) {
- return libnest2d::ShapeLike::getContour(p);
+ return libnest2d::shapelike::getContour(p);
}
static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) {
- return libnest2d::ShapeLike::getContour(p);
+ return libnest2d::shapelike::getContour(p);
}
};
@@ -271,13 +271,13 @@ struct interior_rings<bp2d::PolygonImpl> {
static inline libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
bp2d::PolygonImpl& p)
{
- return libnest2d::ShapeLike::holes(p);
+ return libnest2d::shapelike::holes(p);
}
static inline const libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
bp2d::PolygonImpl const& p)
{
- return libnest2d::ShapeLike::holes(p);
+ return libnest2d::shapelike::holes(p);
}
};
@@ -311,83 +311,77 @@ struct range_value<bp2d::Shapes> {
namespace libnest2d { // Now the algorithms that boost can provide...
+namespace pointlike {
template<>
-inline double PointLike::distance(const PointImpl& p1,
- const PointImpl& p2 )
+inline double distance(const PointImpl& p1, const PointImpl& p2 )
{
return boost::geometry::distance(p1, p2);
}
template<>
-inline double PointLike::distance(const PointImpl& p,
- const bp2d::Segment& seg )
+inline double distance(const PointImpl& p, const bp2d::Segment& seg )
{
return boost::geometry::distance(p, seg);
}
+}
+namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
-inline bool ShapeLike::intersects(const PathImpl& sh1,
- const PathImpl& sh2)
+inline bool intersects(const PathImpl& sh1, const PathImpl& sh2)
{
return boost::geometry::intersects(sh1, sh2);
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
-inline bool ShapeLike::intersects(const PolygonImpl& sh1,
- const PolygonImpl& sh2)
+inline bool intersects(const PolygonImpl& sh1, const PolygonImpl& sh2)
{
return boost::geometry::intersects(sh1, sh2);
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
-inline bool ShapeLike::intersects(const bp2d::Segment& s1,
- const bp2d::Segment& s2)
+inline bool intersects(const bp2d::Segment& s1, const bp2d::Segment& s2)
{
return boost::geometry::intersects(s1, s2);
}
#ifndef DISABLE_BOOST_AREA
template<>
-inline double ShapeLike::area(const PolygonImpl& shape)
+inline double area(const PolygonImpl& shape, const PolygonTag&)
{
return boost::geometry::area(shape);
}
#endif
template<>
-inline bool ShapeLike::isInside<PolygonImpl>(const PointImpl& point,
- const PolygonImpl& shape)
+inline bool isInside(const PointImpl& point, const PolygonImpl& shape)
{
return boost::geometry::within(point, shape);
}
template<>
-inline bool ShapeLike::isInside(const PolygonImpl& sh1,
- const PolygonImpl& sh2)
+inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2)
{
return boost::geometry::within(sh1, sh2);
}
template<>
-inline bool ShapeLike::touches( const PolygonImpl& sh1,
- const PolygonImpl& sh2)
+inline bool touches(const PolygonImpl& sh1, const PolygonImpl& sh2)
{
return boost::geometry::touches(sh1, sh2);
}
template<>
-inline bool ShapeLike::touches( const PointImpl& point,
- const PolygonImpl& shape)
+inline bool touches( const PointImpl& point, const PolygonImpl& shape)
{
return boost::geometry::touches(point, shape);
}
#ifndef DISABLE_BOOST_BOUNDING_BOX
template<>
-inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh)
+inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(sh, b);
@@ -395,7 +389,8 @@ inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh)
}
template<>
-inline bp2d::Box ShapeLike::boundingBox<PolygonImpl>(const bp2d::Shapes& shapes)
+inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes,
+ const MultiPolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(shapes, b);
@@ -405,7 +400,7 @@ inline bp2d::Box ShapeLike::boundingBox<PolygonImpl>(const bp2d::Shapes& shapes)
#ifndef DISABLE_BOOST_CONVEX_HULL
template<>
-inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh)
+inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&)
{
PolygonImpl ret;
boost::geometry::convex_hull(sh, ret);
@@ -413,7 +408,8 @@ inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh)
}
template<>
-inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes)
+inline PolygonImpl convexHull(const TMultiShape<PolygonImpl>& shapes,
+ const MultiPolygonTag&)
{
PolygonImpl ret;
boost::geometry::convex_hull(shapes, ret);
@@ -421,56 +417,17 @@ inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes)
}
#endif
-#ifndef DISABLE_BOOST_ROTATE
-template<>
-inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
-{
- namespace trans = boost::geometry::strategy::transform;
-
- PolygonImpl cpy = sh;
- trans::rotate_transformer<boost::geometry::radian, Radians, 2, 2>
- rotate(rads);
-
- boost::geometry::transform(cpy, sh, rotate);
-}
-#endif
-
-#ifndef DISABLE_BOOST_TRANSLATE
-template<>
-inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
-{
- namespace trans = boost::geometry::strategy::transform;
-
- PolygonImpl cpy = sh;
- trans::translate_transformer<bp2d::Coord, 2, 2> translate(
- bp2d::getX(offs), bp2d::getY(offs));
-
- boost::geometry::transform(cpy, sh, translate);
-}
-#endif
-
#ifndef DISABLE_BOOST_OFFSET
template<>
-inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance)
+inline void offset(PolygonImpl& sh, bp2d::Coord distance)
{
PolygonImpl cpy = sh;
boost::geometry::buffer(cpy, sh, distance);
}
#endif
-#ifndef DISABLE_BOOST_NFP_MERGE
-template<>
-inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes,
- const PolygonImpl& sh)
-{
- bp2d::Shapes retv;
- boost::geometry::union_(shapes, sh, retv);
- return retv;
-}
-#endif
-
#ifndef DISABLE_BOOST_SERIALIZE
-template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
+template<> inline std::string serialize<libnest2d::Formats::SVG>(
const PolygonImpl& sh, double scale)
{
std::stringstream ss;
@@ -482,14 +439,14 @@ template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
Polygonf::ring_type ring;
Polygonf::inner_container_type holes;
- ring.reserve(ShapeLike::contourVertexCount(sh));
+ ring.reserve(shapelike::contourVertexCount(sh));
- for(auto it = ShapeLike::cbegin(sh); it != ShapeLike::cend(sh); it++) {
+ for(auto it = shapelike::cbegin(sh); it != shapelike::cend(sh); it++) {
auto& v = *it;
ring.emplace_back(getX(v)*scale, getY(v)*scale);
};
- auto H = ShapeLike::holes(sh);
+ auto H = shapelike::holes(sh);
for(PathImpl& h : H ) {
Polygonf::ring_type hf;
for(auto it = h.begin(); it != h.end(); it++) {
@@ -512,21 +469,47 @@ template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
#ifndef DISABLE_BOOST_UNSERIALIZE
template<>
-inline void ShapeLike::unserialize<libnest2d::Formats::SVG>(
+inline void unserialize<libnest2d::Formats::SVG>(
PolygonImpl& sh,
const std::string& str)
{
}
#endif
-template<> inline std::pair<bool, std::string>
-ShapeLike::isValid(const PolygonImpl& sh)
+template<> inline std::pair<bool, std::string> isValid(const PolygonImpl& sh)
{
std::string message;
bool ret = boost::geometry::is_valid(sh, message);
return {ret, message};
}
+}
+
+namespace nfp {
+
+#ifndef DISABLE_BOOST_NFP_MERGE
+
+// Warning: I could not get boost union_ to work. Geometries will overlap.
+template<>
+inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes,
+ const PolygonImpl& sh)
+{
+ bp2d::Shapes retv;
+ boost::geometry::union_(shapes, sh, retv);
+ return retv;
+}
+
+template<>
+inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes)
+{
+ bp2d::Shapes retv;
+ boost::geometry::union_(shapes, shapes.back(), retv);
+ return retv;
+}
+#endif
+
+}
+
}
diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp
index 15ceb1576..745fd2108 100644
--- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp
+++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp
@@ -99,49 +99,54 @@ template<> struct PointType<PolygonImpl> {
using Type = PointImpl;
};
-// Type of vertex iterator used by Clipper
-template<> struct VertexIteratorType<PolygonImpl> {
- using Type = ClipperLib::Path::iterator;
-};
-
-// Type of vertex iterator used by Clipper
-template<> struct VertexConstIteratorType<PolygonImpl> {
- using Type = ClipperLib::Path::const_iterator;
+template<> struct PointType<PointImpl> {
+ using Type = PointImpl;
};
template<> struct CountourType<PolygonImpl> {
using Type = PathImpl;
};
-// Tell binpack2d how to extract the X coord from a ClipperPoint object
-template<> inline TCoord<PointImpl> PointLike::x(const PointImpl& p)
+template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
+
+template<> struct ShapeTag<TMultiShape<PolygonImpl>> {
+ using Type = MultiPolygonTag;
+};
+
+template<> struct PointType<TMultiShape<PolygonImpl>> {
+ using Type = PointImpl;
+};
+
+template<> struct HolesContainer<PolygonImpl> {
+ using Type = ClipperLib::Paths;
+};
+
+namespace pointlike {
+
+// Tell libnest2d how to extract the X coord from a ClipperPoint object
+template<> inline TCoord<PointImpl> x(const PointImpl& p)
{
return p.X;
}
-// Tell binpack2d how to extract the Y coord from a ClipperPoint object
-template<> inline TCoord<PointImpl> PointLike::y(const PointImpl& p)
+// Tell libnest2d how to extract the Y coord from a ClipperPoint object
+template<> inline TCoord<PointImpl> y(const PointImpl& p)
{
return p.Y;
}
-// Tell binpack2d how to extract the X coord from a ClipperPoint object
-template<> inline TCoord<PointImpl>& PointLike::x(PointImpl& p)
+// Tell libnest2d how to extract the X coord from a ClipperPoint object
+template<> inline TCoord<PointImpl>& x(PointImpl& p)
{
return p.X;
}
-// Tell binpack2d how to extract the Y coord from a ClipperPoint object
-template<>
-inline TCoord<PointImpl>& PointLike::y(PointImpl& p)
+// Tell libnest2d how to extract the Y coord from a ClipperPoint object
+template<> inline TCoord<PointImpl>& y(PointImpl& p)
{
return p.Y;
}
-template<>
-inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity)
-{
- return sh.Contour.reserve(vertex_capacity);
}
#define DISABLE_BOOST_AREA
@@ -175,16 +180,24 @@ inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
return ClipperLib::Area(sh.Contour) + a;
}
+
}
-// Tell binpack2d how to make string out of a ClipperPolygon object
-template<>
-inline double ShapeLike::area(const PolygonImpl& sh) {
+namespace shapelike {
+
+template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity)
+{
+ return sh.Contour.reserve(vertex_capacity);
+}
+
+// Tell libnest2d how to make string out of a ClipperPolygon object
+template<> inline double area(const PolygonImpl& sh, const PolygonTag&)
+{
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
}
-template<>
-inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
+template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
+{
#define DISABLE_BOOST_OFFSET
using ClipperLib::ClipperOffset;
@@ -234,7 +247,8 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
}
// Tell libnest2d how to make string out of a ClipperPolygon object
-template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) {
+template<> inline std::string toString(const PolygonImpl& sh)
+{
std::stringstream ss;
ss << "Contour {\n";
@@ -257,37 +271,8 @@ template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) {
}
template<>
-inline TVertexIterator<PolygonImpl> ShapeLike::begin(PolygonImpl& sh)
-{
- return sh.Contour.begin();
-}
-
-template<>
-inline TVertexIterator<PolygonImpl> ShapeLike::end(PolygonImpl& sh)
-{
- return sh.Contour.end();
-}
-
-template<>
-inline TVertexConstIterator<PolygonImpl> ShapeLike::cbegin(
- const PolygonImpl& sh)
-{
- return sh.Contour.cbegin();
-}
-
-template<>
-inline TVertexConstIterator<PolygonImpl> ShapeLike::cend(
- const PolygonImpl& sh)
+inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
{
- return sh.Contour.cend();
-}
-
-template<> struct HolesContainer<PolygonImpl> {
- using Type = ClipperLib::Paths;
-};
-
-template<> inline PolygonImpl ShapeLike::create(const PathImpl& path,
- const HoleStore& holes) {
PolygonImpl p;
p.Contour = path;
@@ -308,8 +293,7 @@ template<> inline PolygonImpl ShapeLike::create(const PathImpl& path,
return p;
}
-template<> inline PolygonImpl ShapeLike::create( PathImpl&& path,
- HoleStore&& holes) {
+template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
PolygonImpl p;
p.Contour.swap(path);
@@ -331,49 +315,49 @@ template<> inline PolygonImpl ShapeLike::create( PathImpl&& path,
return p;
}
-template<> inline const THolesContainer<PolygonImpl>&
-ShapeLike::holes(const PolygonImpl& sh)
+template<>
+inline const THolesContainer<PolygonImpl>& holes(const PolygonImpl& sh)
{
return sh.Holes;
}
-template<> inline THolesContainer<PolygonImpl>&
-ShapeLike::holes(PolygonImpl& sh)
+template<> inline THolesContainer<PolygonImpl>& holes(PolygonImpl& sh)
{
return sh.Holes;
}
-template<> inline TContour<PolygonImpl>&
-ShapeLike::getHole(PolygonImpl& sh, unsigned long idx)
+template<>
+inline TContour<PolygonImpl>& getHole(PolygonImpl& sh, unsigned long idx)
{
return sh.Holes[idx];
}
-template<> inline const TContour<PolygonImpl>&
-ShapeLike::getHole(const PolygonImpl& sh, unsigned long idx)
+template<>
+inline const TContour<PolygonImpl>& getHole(const PolygonImpl& sh,
+ unsigned long idx)
{
return sh.Holes[idx];
}
-template<> inline size_t ShapeLike::holeCount(const PolygonImpl& sh)
+template<> inline size_t holeCount(const PolygonImpl& sh)
{
return sh.Holes.size();
}
-template<> inline PathImpl& ShapeLike::getContour(PolygonImpl& sh)
+template<> inline PathImpl& getContour(PolygonImpl& sh)
{
return sh.Contour;
}
template<>
-inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh)
+inline const PathImpl& getContour(const PolygonImpl& sh)
{
return sh.Contour;
}
#define DISABLE_BOOST_TRANSLATE
template<>
-inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
+inline void translate(PolygonImpl& sh, const PointImpl& offs)
{
for(auto& p : sh.Contour) { p += offs; }
for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; }
@@ -381,7 +365,7 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
#define DISABLE_BOOST_ROTATE
template<>
-inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
+inline void rotate(PolygonImpl& sh, const Radians& rads)
{
using Coord = TCoord<PointImpl>;
@@ -402,9 +386,11 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
}
}
+} // namespace shapelike
+
#define DISABLE_BOOST_NFP_MERGE
-inline Nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
- Nfp::Shapes<PolygonImpl> retv;
+inline std::vector<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
+ shapelike::Shapes<PolygonImpl> retv;
ClipperLib::PolyTree result;
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
@@ -438,8 +424,10 @@ inline Nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
return retv;
}
-template<> inline Nfp::Shapes<PolygonImpl>
-Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes)
+namespace nfp {
+
+template<> inline std::vector<PolygonImpl>
+merge(const std::vector<PolygonImpl>& shapes)
{
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
@@ -461,6 +449,8 @@ Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes)
}
+}
+
//#define DISABLE_BOOST_SERIALIZE
//#define DISABLE_BOOST_UNSERIALIZE
diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp
index 058c47cd4..d16257731 100644
--- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp
+++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp
@@ -22,34 +22,12 @@ template<class GeomType>
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
/// Getting the type of point structure used by a shape.
-template<class Shape> struct PointType { /*using Type = void;*/ };
+template<class Sh> struct PointType { using Type = typename Sh::PointType; };
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
template<class Shape>
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
-/// Getting the VertexIterator type of a shape class.
-template<class Shape> struct VertexIteratorType { /*using Type = void;*/ };
-
-/// Getting the const vertex iterator for a shape class.
-template<class Shape> struct VertexConstIteratorType {/* using Type = void;*/ };
-
-/**
- * TVertexIterator<Shape> as shorthand for
- * `typename VertexIteratorType<Shape>::Type`
- */
-template<class Shape>
-using TVertexIterator =
-typename VertexIteratorType<remove_cvref_t<Shape>>::Type;
-
-/**
- * \brief TVertexConstIterator<Shape> as shorthand for
- * `typename VertexConstIteratorType<Shape>::Type`
- */
-template<class ShapeClass>
-using TVertexConstIterator =
-typename VertexConstIteratorType<remove_cvref_t<ShapeClass>>::Type;
-
/**
* \brief A point pair base class for other point pairs (segment, box, ...).
* \tparam RawPoint The actual point type to use.
@@ -60,6 +38,17 @@ struct PointPair {
RawPoint p2;
};
+struct PolygonTag {};
+struct MultiPolygonTag {};
+struct BoxTag {};
+struct CircleTag {};
+
+template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
+template<class S> using Tag = typename ShapeTag<S>::Type;
+
+template<class S> struct MultiShape { using Type = std::vector<S>; };
+template<class S> using TMultiShape = typename MultiShape<S>::Type;
+
/**
* \brief An abstraction of a box;
*/
@@ -69,6 +58,9 @@ class _Box: PointPair<RawPoint> {
using PointPair<RawPoint>::p2;
public:
+ using Tag = BoxTag;
+ using PointType = RawPoint;
+
inline _Box() = default;
inline _Box(const RawPoint& p, const RawPoint& pp):
PointPair<RawPoint>({p, pp}) {}
@@ -98,6 +90,9 @@ class _Circle {
double radius_ = 0;
public:
+ using Tag = CircleTag;
+ using PointType = RawPoint;
+
_Circle() = default;
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
@@ -109,7 +104,7 @@ public:
inline void radius(double r) { radius_ = r; }
inline double area() const BP2D_NOEXCEPT {
- return 2.0*Pi*radius_;
+ return 2.0*Pi*radius_*radius_;
}
};
@@ -123,6 +118,8 @@ class _Segment: PointPair<RawPoint> {
mutable Radians angletox_ = std::nan("");
public:
+ using PointType = RawPoint;
+
inline _Segment() = default;
inline _Segment(const RawPoint& p, const RawPoint& pp):
@@ -156,36 +153,36 @@ public:
inline double length();
};
-// This struct serves as a namespace. The only difference is that is can be
+// This struct serves almost as a namespace. The only difference is that is can
// used in friend declarations.
-struct PointLike {
+namespace pointlike {
template<class RawPoint>
- static TCoord<RawPoint> x(const RawPoint& p)
+ inline TCoord<RawPoint> x(const RawPoint& p)
{
return p.x();
}
template<class RawPoint>
- static TCoord<RawPoint> y(const RawPoint& p)
+ inline TCoord<RawPoint> y(const RawPoint& p)
{
return p.y();
}
template<class RawPoint>
- static TCoord<RawPoint>& x(RawPoint& p)
+ inline TCoord<RawPoint>& x(RawPoint& p)
{
return p.x();
}
template<class RawPoint>
- static TCoord<RawPoint>& y(RawPoint& p)
+ inline TCoord<RawPoint>& y(RawPoint& p)
{
return p.y();
}
template<class RawPoint>
- static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
+ inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
{
static_assert(always_false<RawPoint>::value,
"PointLike::distance(point, point) unimplemented!");
@@ -193,7 +190,7 @@ struct PointLike {
}
template<class RawPoint>
- static double distance(const RawPoint& /*p1*/,
+ inline double distance(const RawPoint& /*p1*/,
const _Segment<RawPoint>& /*s*/)
{
static_assert(always_false<RawPoint>::value,
@@ -202,13 +199,13 @@ struct PointLike {
}
template<class RawPoint>
- static std::pair<TCoord<RawPoint>, bool> horizontalDistance(
+ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
const RawPoint& p, const _Segment<RawPoint>& s)
{
using Unit = TCoord<RawPoint>;
- auto x = PointLike::x(p), y = PointLike::y(p);
- auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first());
- auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second());
+ auto x = pointlike::x(p), y = pointlike::y(p);
+ auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
+ auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
TCoord<RawPoint> ret;
@@ -228,13 +225,13 @@ struct PointLike {
}
template<class RawPoint>
- static std::pair<TCoord<RawPoint>, bool> verticalDistance(
+ inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
const RawPoint& p, const _Segment<RawPoint>& s)
{
using Unit = TCoord<RawPoint>;
- auto x = PointLike::x(p), y = PointLike::y(p);
- auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first());
- auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second());
+ auto x = pointlike::x(p), y = pointlike::y(p);
+ auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
+ auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
TCoord<RawPoint> ret;
@@ -252,36 +249,36 @@ struct PointLike {
return {ret, true};
}
-};
+}
template<class RawPoint>
TCoord<RawPoint> _Box<RawPoint>::width() const BP2D_NOEXCEPT
{
- return PointLike::x(maxCorner()) - PointLike::x(minCorner());
+ return pointlike::x(maxCorner()) - pointlike::x(minCorner());
}
template<class RawPoint>
TCoord<RawPoint> _Box<RawPoint>::height() const BP2D_NOEXCEPT
{
- return PointLike::y(maxCorner()) - PointLike::y(minCorner());
+ return pointlike::y(maxCorner()) - pointlike::y(minCorner());
}
template<class RawPoint>
-TCoord<RawPoint> getX(const RawPoint& p) { return PointLike::x<RawPoint>(p); }
+TCoord<RawPoint> getX(const RawPoint& p) { return pointlike::x<RawPoint>(p); }
template<class RawPoint>
-TCoord<RawPoint> getY(const RawPoint& p) { return PointLike::y<RawPoint>(p); }
+TCoord<RawPoint> getY(const RawPoint& p) { return pointlike::y<RawPoint>(p); }
template<class RawPoint>
void setX(RawPoint& p, const TCoord<RawPoint>& val)
{
- PointLike::x<RawPoint>(p) = val;
+ pointlike::x<RawPoint>(p) = val;
}
template<class RawPoint>
void setY(RawPoint& p, const TCoord<RawPoint>& val)
{
- PointLike::y<RawPoint>(p) = val;
+ pointlike::y<RawPoint>(p) = val;
}
template<class RawPoint>
@@ -303,7 +300,7 @@ inline Radians _Segment<RawPoint>::angleToXaxis() const
template<class RawPoint>
inline double _Segment<RawPoint>::length()
{
- return PointLike::distance(first(), second());
+ return pointlike::distance(first(), second());
}
template<class RawPoint>
@@ -313,9 +310,9 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
using Coord = TCoord<RawPoint>;
- RawPoint ret = {
- static_cast<Coord>( std::round((getX(minc) + getX(maxc))/2.0) ),
- static_cast<Coord>( std::round((getY(minc) + getY(maxc))/2.0) )
+ RawPoint ret = { // No rounding here, we dont know if these are int coords
+ static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ),
+ static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 )
};
return ret;
@@ -356,124 +353,125 @@ enum class Formats {
// This struct serves as a namespace. The only difference is that it can be
// used in friend declarations and can be aliased at class scope.
-struct ShapeLike {
+namespace shapelike {
template<class RawShape>
- using Shapes = std::vector<RawShape>;
+ using Shapes = TMultiShape<RawShape>;
template<class RawShape>
- static RawShape create(const TContour<RawShape>& contour,
+ inline RawShape create(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes)
{
return RawShape(contour, holes);
}
template<class RawShape>
- static RawShape create(TContour<RawShape>&& contour,
+ inline RawShape create(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes)
{
return RawShape(contour, holes);
}
template<class RawShape>
- static RawShape create(const TContour<RawShape>& contour)
+ inline RawShape create(const TContour<RawShape>& contour)
{
return create<RawShape>(contour, {});
}
template<class RawShape>
- static RawShape create(TContour<RawShape>&& contour)
+ inline RawShape create(TContour<RawShape>&& contour)
{
return create<RawShape>(contour, {});
}
template<class RawShape>
- static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
+ inline THolesContainer<RawShape>& holes(RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
- static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
+ inline const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
- static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
+ inline TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
- static const TContour<RawShape>& getHole(const RawShape& sh,
+ inline const TContour<RawShape>& getHole(const RawShape& sh,
unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
- static size_t holeCount(const RawShape& sh)
+ inline size_t holeCount(const RawShape& sh)
{
return holes(sh).size();
}
template<class RawShape>
- static TContour<RawShape>& getContour(RawShape& sh)
+ inline TContour<RawShape>& getContour(RawShape& sh)
{
return sh;
}
template<class RawShape>
- static const TContour<RawShape>& getContour(const RawShape& sh)
+ inline const TContour<RawShape>& getContour(const RawShape& sh)
{
return sh;
}
// Optional, does nothing by default
template<class RawShape>
- static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
+ inline void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
template<class RawShape, class...Args>
- static void addVertex(RawShape& sh, Args...args)
+ inline void addVertex(RawShape& sh, Args...args)
{
return getContour(sh).emplace_back(std::forward<Args>(args)...);
}
template<class RawShape>
- static TVertexIterator<RawShape> begin(RawShape& sh)
+ inline typename TContour<RawShape>::iterator begin(RawShape& sh)
{
- return sh.begin();
+ return getContour(sh).begin();
}
template<class RawShape>
- static TVertexIterator<RawShape> end(RawShape& sh)
+ inline typename TContour<RawShape>::iterator end(RawShape& sh)
{
- return sh.end();
+ return getContour(sh).end();
}
template<class RawShape>
- static TVertexConstIterator<RawShape> cbegin(const RawShape& sh)
+ inline typename TContour<RawShape>::const_iterator
+ cbegin(const RawShape& sh)
{
- return sh.cbegin();
+ return getContour(sh).cbegin();
}
template<class RawShape>
- static TVertexConstIterator<RawShape> cend(const RawShape& sh)
+ inline typename TContour<RawShape>::const_iterator cend(const RawShape& sh)
{
- return sh.cend();
+ return getContour(sh).cend();
}
template<class RawShape>
- static std::string toString(const RawShape& /*sh*/)
+ inline std::string toString(const RawShape& /*sh*/)
{
return "";
}
template<Formats, class RawShape>
- static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1)
+ inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::serialize() unimplemented!");
@@ -481,14 +479,14 @@ struct ShapeLike {
}
template<Formats, class RawShape>
- static void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
+ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::unserialize() unimplemented!");
}
template<class RawShape>
- static double area(const RawShape& /*sh*/)
+ inline double area(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::area() unimplemented!");
@@ -496,7 +494,7 @@ struct ShapeLike {
}
template<class RawShape>
- static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
+ inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::intersects() unimplemented!");
@@ -504,7 +502,7 @@ struct ShapeLike {
}
template<class RawShape>
- static bool isInside(const TPoint<RawShape>& /*point*/,
+ inline bool isInside(const TPoint<RawShape>& /*point*/,
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
@@ -513,7 +511,7 @@ struct ShapeLike {
}
template<class RawShape>
- static bool isInside(const RawShape& /*shape*/,
+ inline bool isInside(const RawShape& /*shape*/,
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
@@ -522,7 +520,7 @@ struct ShapeLike {
}
template<class RawShape>
- static bool touches( const RawShape& /*shape*/,
+ inline bool touches( const RawShape& /*shape*/,
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
@@ -531,7 +529,7 @@ struct ShapeLike {
}
template<class RawShape>
- static bool touches( const TPoint<RawShape>& /*point*/,
+ inline bool touches( const TPoint<RawShape>& /*point*/,
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
@@ -540,64 +538,66 @@ struct ShapeLike {
}
template<class RawShape>
- static _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/)
+ inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/,
+ const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::boundingBox(shape) unimplemented!");
}
- template<class RawShape>
- static _Box<TPoint<RawShape>> boundingBox(const Shapes<RawShape>& /*sh*/)
+ template<class RawShapes>
+ inline _Box<TPoint<typename RawShapes::value_type>>
+ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
- static_assert(always_false<RawShape>::value,
+ static_assert(always_false<RawShapes>::value,
"ShapeLike::boundingBox(shapes) unimplemented!");
}
template<class RawShape>
- static RawShape convexHull(const RawShape& /*sh*/)
+ inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::convexHull(shape) unimplemented!");
return RawShape();
}
- template<class RawShape>
- static RawShape convexHull(const Shapes<RawShape>& /*sh*/)
+ template<class RawShapes>
+ inline typename RawShapes::value_type
+ convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
- static_assert(always_false<RawShape>::value,
+ static_assert(always_false<RawShapes>::value,
"ShapeLike::convexHull(shapes) unimplemented!");
- return RawShape();
+ return typename RawShapes::value_type();
}
template<class RawShape>
- static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
+ inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::rotate() unimplemented!");
}
template<class RawShape, class RawPoint>
- static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
+ inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::translate() unimplemented!");
}
template<class RawShape>
- static void offset(RawShape& /*sh*/, TCoord<TPoint<RawShape>> /*distance*/)
+ inline void offset(RawShape& /*sh*/, TCoord<TPoint<RawShape>> /*distance*/)
{
- static_assert(always_false<RawShape>::value,
- "ShapeLike::offset() unimplemented!");
+ dout() << "The current geometry backend does not support offsetting!\n";
}
template<class RawShape>
- static std::pair<bool, std::string> isValid(const RawShape& /*sh*/)
+ inline std::pair<bool, std::string> isValid(const RawShape& /*sh*/)
{
return {false, "ShapeLike::isValid() unimplemented!"};
}
template<class RawShape>
- static inline bool isConvex(const TContour<RawShape>& sh)
+ inline bool isConvex(const TContour<RawShape>& sh)
{
using Vertex = TPoint<RawShape>;
auto first = sh.begin();
@@ -633,43 +633,55 @@ struct ShapeLike {
// No need to implement these
// *************************************************************************
- template<class RawShape>
- static inline _Box<TPoint<RawShape>> boundingBox(
- const _Box<TPoint<RawShape>>& box)
+ template<class Box>
+ inline Box boundingBox(const Box& box, const BoxTag& )
{
return box;
}
- template<class RawShape>
- static inline _Box<TPoint<RawShape>> boundingBox(
- const _Circle<TPoint<RawShape>>& circ)
+ template<class Circle>
+ inline _Box<typename Circle::PointType> boundingBox(
+ const Circle& circ, const CircleTag&)
{
- using Coord = TCoord<TPoint<RawShape>>;
- TPoint<RawShape> pmin = {
+ using Point = typename Circle::PointType;
+ using Coord = TCoord<Point>;
+ Point pmin = {
static_cast<Coord>(getX(circ.center()) - circ.radius()),
static_cast<Coord>(getY(circ.center()) - circ.radius()) };
- TPoint<RawShape> pmax = {
+ Point pmax = {
static_cast<Coord>(getX(circ.center()) + circ.radius()),
static_cast<Coord>(getY(circ.center()) + circ.radius()) };
return {pmin, pmax};
}
- template<class RawShape>
- static inline double area(const _Box<TPoint<RawShape>>& box)
+ template<class S> // Dispatch function
+ inline _Box<TPoint<S>> boundingBox(const S& sh)
{
- return static_cast<double>(box.width() * box.height());
+ return boundingBox(sh, Tag<S>() );
}
- template<class RawShape>
- static inline double area(const _Circle<TPoint<RawShape>>& circ)
+ template<class Box>
+ inline double area(const Box& box, const BoxTag& )
+ {
+ return box.area();
+ }
+
+ template<class Circle>
+ inline double area(const Circle& circ, const CircleTag& )
{
return circ.area();
}
+ template<class RawShape> // Dispatching function
+ inline double area(const RawShape& sh)
+ {
+ return area(sh, Tag<RawShape>());
+ }
+
template<class RawShape>
- static inline double area(const Shapes<RawShape>& shapes)
+ inline double area(const Shapes<RawShape>& shapes)
{
return std::accumulate(shapes.begin(), shapes.end(), 0.0,
[](double a, const RawShape& b) {
@@ -678,14 +690,21 @@ struct ShapeLike {
}
template<class RawShape>
- static bool isInside(const TPoint<RawShape>& point,
+ inline auto convexHull(const RawShape& sh)
+ -> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
+ {
+ return convexHull(sh, Tag<RawShape>());
+ }
+
+ template<class RawShape>
+ inline bool isInside(const TPoint<RawShape>& point,
const _Circle<TPoint<RawShape>>& circ)
{
- return PointLike::distance(point, circ.center()) < circ.radius();
+ return pointlike::distance(point, circ.center()) < circ.radius();
}
template<class RawShape>
- static bool isInside(const TPoint<RawShape>& point,
+ inline bool isInside(const TPoint<RawShape>& point,
const _Box<TPoint<RawShape>>& box)
{
auto px = getX(point);
@@ -699,7 +718,7 @@ struct ShapeLike {
}
template<class RawShape>
- static bool isInside(const RawShape& sh,
+ inline bool isInside(const RawShape& sh,
const _Circle<TPoint<RawShape>>& circ)
{
return std::all_of(cbegin(sh), cend(sh),
@@ -709,7 +728,7 @@ struct ShapeLike {
}
template<class RawShape>
- static bool isInside(const _Box<TPoint<RawShape>>& box,
+ inline bool isInside(const _Box<TPoint<RawShape>>& box,
const _Circle<TPoint<RawShape>>& circ)
{
return isInside<RawShape>(box.minCorner(), circ) &&
@@ -717,7 +736,7 @@ struct ShapeLike {
}
template<class RawShape>
- static bool isInside(const _Box<TPoint<RawShape>>& ibb,
+ inline bool isInside(const _Box<TPoint<RawShape>>& ibb,
const _Box<TPoint<RawShape>>& box)
{
auto iminX = getX(ibb.minCorner());
@@ -734,31 +753,31 @@ struct ShapeLike {
}
template<class RawShape> // Potential O(1) implementation may exist
- static inline TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx)
+ inline TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx)
{
return *(begin(sh) + idx);
}
template<class RawShape> // Potential O(1) implementation may exist
- static inline const TPoint<RawShape>& vertex(const RawShape& sh,
+ inline const TPoint<RawShape>& vertex(const RawShape& sh,
unsigned long idx)
{
return *(cbegin(sh) + idx);
}
template<class RawShape>
- static inline size_t contourVertexCount(const RawShape& sh)
+ inline size_t contourVertexCount(const RawShape& sh)
{
return cend(sh) - cbegin(sh);
}
template<class RawShape, class Fn>
- static inline void foreachContourVertex(RawShape& sh, Fn fn) {
+ inline void foreachContourVertex(RawShape& sh, Fn fn) {
for(auto it = begin(sh); it != end(sh); ++it) fn(*it);
}
template<class RawShape, class Fn>
- static inline void foreachHoleVertex(RawShape& sh, Fn fn) {
+ inline void foreachHoleVertex(RawShape& sh, Fn fn) {
for(int i = 0; i < holeCount(sh); ++i) {
auto& h = getHole(sh, i);
for(auto it = begin(h); it != end(h); ++it) fn(*it);
@@ -766,12 +785,12 @@ struct ShapeLike {
}
template<class RawShape, class Fn>
- static inline void foreachContourVertex(const RawShape& sh, Fn fn) {
+ inline void foreachContourVertex(const RawShape& sh, Fn fn) {
for(auto it = cbegin(sh); it != cend(sh); ++it) fn(*it);
}
template<class RawShape, class Fn>
- static inline void foreachHoleVertex(const RawShape& sh, Fn fn) {
+ inline void foreachHoleVertex(const RawShape& sh, Fn fn) {
for(int i = 0; i < holeCount(sh); ++i) {
auto& h = getHole(sh, i);
for(auto it = cbegin(h); it != cend(h); ++it) fn(*it);
@@ -779,18 +798,27 @@ struct ShapeLike {
}
template<class RawShape, class Fn>
- static inline void foreachVertex(RawShape& sh, Fn fn) {
+ inline void foreachVertex(RawShape& sh, Fn fn) {
foreachContourVertex(sh, fn);
foreachHoleVertex(sh, fn);
}
template<class RawShape, class Fn>
- static inline void foreachVertex(const RawShape& sh, Fn fn) {
+ inline void foreachVertex(const RawShape& sh, Fn fn) {
foreachContourVertex(sh, fn);
foreachHoleVertex(sh, fn);
}
+}
-};
+#define DECLARE_MAIN_TYPES(T) \
+ using Polygon = T; \
+ using Point = TPoint<T>; \
+ using Coord = TCoord<Point>; \
+ using Contour = TContour<T>; \
+ using Box = _Box<Point>; \
+ using Circle = _Circle<Point>; \
+ using Segment = _Segment<Point>; \
+ using Polygons = TMultiShape<T>
}
diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp
index 90cf21be5..2982454cd 100644
--- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp
+++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp
@@ -9,6 +9,27 @@
namespace libnest2d {
+namespace __nfp {
+// Do not specialize this...
+template<class RawShape>
+inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
+{
+ using Coord = TCoord<TPoint<RawShape>>;
+ Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2);
+ auto diff = y1 - y2;
+ if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
+ return x1 < x2;
+
+ return diff < 0;
+}
+}
+
+/// A collection of static methods for handling the no fit polygon creation.
+namespace nfp {
+
+//namespace sl = shapelike;
+//namespace pl = pointlike;
+
/// The complexity level of a polygon that an NFP implementation can handle.
enum class NfpLevel: unsigned {
CONVEX_ONLY,
@@ -18,12 +39,17 @@ enum class NfpLevel: unsigned {
BOTH_CONCAVE_WITH_HOLES
};
-/// A collection of static methods for handling the no fit polygon creation.
-struct Nfp {
+template<class RawShape>
+using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
+
+template<class RawShape> struct MaxNfpLevel {
+ static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
+};
+
// Shorthand for a pile of polygons
template<class RawShape>
-using Shapes = typename ShapeLike::Shapes<RawShape>;
+using Shapes = TMultiShape<RawShape>;
/**
* Merge a bunch of polygons with the specified additional polygon.
@@ -36,10 +62,10 @@ using Shapes = typename ShapeLike::Shapes<RawShape>;
* mostly it will be a set containing only one big polygon but if the input
* polygons are disjuct than the resulting set will contain more polygons.
*/
-template<class RawShape>
-static Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
+template<class RawShapes>
+inline RawShapes merge(const RawShapes& /*shc*/)
{
- static_assert(always_false<RawShape>::value,
+ static_assert(always_false<RawShapes>::value,
"Nfp::merge(shapes, shape) unimplemented!");
}
@@ -55,25 +81,12 @@ static Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
* polygons are disjuct than the resulting set will contain more polygons.
*/
template<class RawShape>
-static Shapes<RawShape> merge(const Shapes<RawShape>& shc,
- const RawShape& sh)
+inline TMultiShape<RawShape> merge(const TMultiShape<RawShape>& shc,
+ const RawShape& sh)
{
- auto m = merge(shc);
+ auto m = nfp::merge(shc);
m.push_back(sh);
- return merge(m);
-}
-
-/**
- * A method to get a vertex from a polygon that always maintains a relative
- * position to the coordinate system: It is always the rightmost top vertex.
- *
- * This way it does not matter in what order the vertices are stored, the
- * reference will be always the same for the same polygon.
- */
-template<class RawShape>
-inline static TPoint<RawShape> referenceVertex(const RawShape& sh)
-{
- return rightmostUpVertex(sh);
+ return nfp::merge(m);
}
/**
@@ -82,14 +95,14 @@ inline static TPoint<RawShape> referenceVertex(const RawShape& sh)
* the result will be the leftmost (with the highest X coordinate).
*/
template<class RawShape>
-static TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
+inline TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
{
// find min x and min y vertex
- auto it = std::min_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh),
- _vsort<RawShape>);
+ auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh),
+ __nfp::_vsort<RawShape>);
- return *it;
+ return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;;
}
/**
@@ -98,26 +111,27 @@ static TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
* the result will be the rightmost (with the lowest X coordinate).
*/
template<class RawShape>
-static TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
+TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
{
// find max x and max y vertex
- auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh),
- _vsort<RawShape>);
+ auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh),
+ __nfp::_vsort<RawShape>);
- return *it;
+ return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;
}
+/**
+ * A method to get a vertex from a polygon that always maintains a relative
+ * position to the coordinate system: It is always the rightmost top vertex.
+ *
+ * This way it does not matter in what order the vertices are stored, the
+ * reference will be always the same for the same polygon.
+ */
template<class RawShape>
-using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
-
-/// Helper function to get the NFP
-template<NfpLevel nfptype, class RawShape>
-static NfpResult<RawShape> noFitPolygon(const RawShape& sh,
- const RawShape& other)
+inline TPoint<RawShape> referenceVertex(const RawShape& sh)
{
- NfpImpl<RawShape, nfptype> nfp;
- return nfp(sh, other);
+ return rightmostUpVertex(sh);
}
/**
@@ -139,11 +153,11 @@ static NfpResult<RawShape> noFitPolygon(const RawShape& sh,
*
*/
template<class RawShape>
-static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
+inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
const RawShape& other)
{
using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>;
- using sl = ShapeLike;
+ namespace sl = shapelike;
RawShape rsh; // Final nfp placeholder
Vertex top_nfp;
@@ -187,7 +201,7 @@ static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
sl::addVertex(rsh, edgelist.front().second());
// Sorting function for the nfp reference vertex search
- auto& cmp = _vsort<RawShape>;
+ auto& cmp = __nfp::_vsort<RawShape>;
// the reference (rightmost top) vertex so far
top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp );
@@ -214,7 +228,7 @@ static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
}
template<class RawShape>
-static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
+NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother)
{
@@ -233,7 +247,7 @@ static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
- using sl = ShapeLike;
+ namespace sl = shapelike;
using std::signbit;
using std::sort;
using std::vector;
@@ -528,27 +542,16 @@ struct NfpImpl {
}
};
-template<class RawShape> struct MaxNfpLevel {
- static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
-};
-
-private:
-
-// Do not specialize this...
-template<class RawShape>
-static inline bool _vsort(const TPoint<RawShape>& v1,
- const TPoint<RawShape>& v2)
+/// Helper function to get the NFP
+template<NfpLevel nfptype, class RawShape>
+inline NfpResult<RawShape> noFitPolygon(const RawShape& sh,
+ const RawShape& other)
{
- using Coord = TCoord<TPoint<RawShape>>;
- Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2);
- auto diff = y1 - y2;
- if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
- return x1 < x2;
-
- return diff < 0;
+ NfpImpl<RawShape, nfptype> nfps;
+ return nfps(sh, other);
}
-};
+}
}
diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp
index 7f23de358..4d1e62f99 100644
--- a/xs/src/libnest2d/libnest2d/libnest2d.hpp
+++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp
@@ -9,10 +9,12 @@
#include <functional>
#include "geometry_traits.hpp"
-#include "optimizer.hpp"
namespace libnest2d {
+namespace sl = shapelike;
+namespace pl = pointlike;
+
/**
* \brief An item to be placed on a bin.
*
@@ -28,7 +30,8 @@ class _Item {
using Coord = TCoord<TPoint<RawShape>>;
using Vertex = TPoint<RawShape>;
using Box = _Box<Vertex>;
- using sl = ShapeLike;
+
+ using VertexConstIterator = typename TContour<RawShape>::const_iterator;
// The original shape that gets encapsulated.
RawShape sh_;
@@ -38,7 +41,7 @@ class _Item {
Radians rotation_;
Coord offset_distance_;
- // Info about whether the tranformations will have to take place
+ // Info about whether the transformations will have to take place
// This is needed because if floating point is used, it is hard to say
// that a zero angle is not a rotation because of testing for equality.
bool has_rotation_ = false, has_translation_ = false, has_offset_ = false;
@@ -58,12 +61,12 @@ class _Item {
};
mutable Convexity convexity_ = Convexity::UNCHECKED;
- mutable TVertexConstIterator<RawShape> rmt_; // rightmost top vertex
- mutable TVertexConstIterator<RawShape> lmb_; // leftmost bottom vertex
+ mutable VertexConstIterator rmt_; // rightmost top vertex
+ mutable VertexConstIterator lmb_; // leftmost bottom vertex
mutable bool rmt_valid_ = false, lmb_valid_ = false;
mutable struct BBCache {
- Box bb; bool valid; Vertex tr;
- BBCache(): valid(false), tr(0, 0) {}
+ Box bb; bool valid;
+ BBCache(): valid(false) {}
} bb_cache_;
public:
@@ -80,7 +83,7 @@ public:
* supports. Giving out a non const iterator would make it impossible to
* perform correct cache invalidation.
*/
- using Iterator = TVertexConstIterator<RawShape>;
+ using Iterator = VertexConstIterator;
/**
* @brief Get the orientation of the polygon.
@@ -109,7 +112,7 @@ public:
explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {}
/**
- * @brief Create an item from an initilizer list.
+ * @brief Create an item from an initializer list.
* @param il The initializer list of vertices.
*/
inline _Item(const std::initializer_list< Vertex >& il):
@@ -159,7 +162,7 @@ public:
}
/**
- * @brief Get a copy of an outer vertex whithin the carried shape.
+ * @brief Get a copy of an outer vertex within the carried shape.
*
* Note that the vertex considered here is taken from the original shape
* that this item is constructed from. This means that no transformation is
@@ -244,7 +247,7 @@ public:
* @param p
* @return
*/
- inline bool isPointInside(const Vertex& p) const
+ inline bool isInside(const Vertex& p) const
{
return sl::isInside(p, transformedShape());
}
@@ -307,7 +310,7 @@ public:
{
if(translation_ != tr) {
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
- bb_cache_.valid = false;
+ //bb_cache_.valid = false;
}
}
@@ -342,13 +345,19 @@ public:
inline Box boundingBox() const {
if(!bb_cache_.valid) {
- bb_cache_.bb = sl::boundingBox(transformedShape());
- bb_cache_.tr = {0, 0};
+ if(!has_rotation_)
+ bb_cache_.bb = sl::boundingBox(offsettedShape());
+ else {
+ // TODO make sure this works
+ auto rotsh = offsettedShape();
+ sl::rotate(rotsh, rotation_);
+ bb_cache_.bb = sl::boundingBox(rotsh);
+ }
bb_cache_.valid = true;
}
- auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr;
- return {bb.minCorner() + tr, bb.maxCorner() + tr};
+ auto &bb = bb_cache_.bb; auto &tr = translation_;
+ return {bb.minCorner() + tr, bb.maxCorner() + tr };
}
inline Vertex referenceVertex() const {
@@ -438,7 +447,7 @@ public:
inline _Rectangle(Unit width, Unit height,
// disable this ctor if o != CLOCKWISE
enable_if_t< o == TO::CLOCKWISE, int> = 0 ):
- _Item<RawShape>( ShapeLike::create<RawShape>( {
+ _Item<RawShape>( sl::create<RawShape>( {
{0, 0},
{0, height},
{width, height},
@@ -452,7 +461,7 @@ public:
inline _Rectangle(Unit width, Unit height,
// disable this ctor if o != COUNTER_CLOCKWISE
enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ):
- _Item<RawShape>( ShapeLike::create<RawShape>( {
+ _Item<RawShape>( sl::create<RawShape>( {
{0, 0},
{width, 0},
{width, height},
@@ -473,18 +482,38 @@ public:
template<class RawShape>
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
- return ShapeLike::isInside<RawShape>(boundingBox(), box);
+ return sl::isInside<RawShape>(boundingBox(), box);
}
template<class RawShape> inline bool
_Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
- return ShapeLike::isInside<RawShape>(transformedShape(), circ);
+ return sl::isInside<RawShape>(transformedShape(), circ);
+}
+
+
+template<class I> using _ItemRef = std::reference_wrapper<I>;
+template<class I> using _ItemGroup = std::vector<_ItemRef<I>>;
+
+template<class Iterator>
+struct ConstItemRange {
+ Iterator from;
+ Iterator to;
+ bool valid = false;
+
+ ConstItemRange() = default;
+ ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {}
+};
+
+template<class Container>
+inline ConstItemRange<typename Container::const_iterator>
+rem(typename Container::const_iterator it, const Container& cont) {
+ return {std::next(it), cont.end()};
}
/**
* \brief A wrapper interface (trait) class for any placement strategy provider.
*
- * If a client want's to use its own placement algorithm, all it has to do is to
+ * If a client wants to use its own placement algorithm, all it has to do is to
* specialize this class template and define all the ten methods it has. It can
* use the strategies::PlacerBoilerplace class for creating a new placement
* strategy where only the constructor and the trypack method has to be provided
@@ -515,8 +544,9 @@ public:
*/
using PackResult = typename PlacementStrategy::PackResult;
- using ItemRef = std::reference_wrapper<Item>;
- using ItemGroup = std::vector<ItemRef>;
+ using ItemRef = _ItemRef<Item>;
+ using ItemGroup = _ItemGroup<Item>;
+ using DefaultIterator = typename ItemGroup::const_iterator;
/**
* @brief Constructor taking the bin and an optional configuration.
@@ -536,29 +566,32 @@ public:
* Note that it depends on the particular placer implementation how it
* reacts to config changes in the middle of a calculation.
*
- * @param config The configuration object defined by the placement startegy.
+ * @param config The configuration object defined by the placement strategy.
*/
inline void configure(const Config& config) { impl_.configure(config); }
/**
- * @brief A method that tries to pack an item and returns an object
- * describing the pack result.
- *
- * The result can be casted to bool and used as an argument to the accept
- * method to accept a succesfully packed item. This way the next packing
- * will consider the accepted item as well. The PackResult should carry the
- * transformation info so that if the tried item is later modified or tried
- * multiple times, the result object should set it to the originally
- * determied position. An implementation can be found in the
- * strategies::PlacerBoilerplate::PackResult class.
+ * Try to pack an item with a result object that contains the packing
+ * information for later accepting it.
*
- * @param item Ithe item to be packed.
- * @return The PackResult object that can be implicitly casted to bool.
+ * \param item_store A container of items that are intended to be packed
+ * later. Can be used by the placer to switch tactics. When it's knows that
+ * many items will come a greedy strategy may not be the best.
+ * \param from The iterator to the item from which the packing should start,
+ * including the pointed item
+ * \param count How many items should be packed. If the value is 1, than
+ * just the item pointed to by "from" argument should be packed.
*/
- inline PackResult trypack(Item& item) { return impl_.trypack(item); }
+ template<class Iter = DefaultIterator>
+ inline PackResult trypack(
+ Item& item,
+ const ConstItemRange<Iter>& remaining = ConstItemRange<Iter>())
+ {
+ return impl_.trypack(item, remaining);
+ }
/**
- * @brief A method to accept a previously tried item.
+ * @brief A method to accept a previously tried item (or items).
*
* If the pack result is a failure the method should ignore it.
* @param r The result of a previous trypack call.
@@ -566,19 +599,25 @@ public:
inline void accept(PackResult& r) { impl_.accept(r); }
/**
- * @brief pack Try to pack an item and immediately accept it on success.
+ * @brief pack Try to pack and immediately accept it on success.
*
* A default implementation would be to call
- * { auto&& r = trypack(item); accept(r); return r; } but we should let the
+ * { auto&& r = trypack(...); accept(r); return r; } but we should let the
* implementor of the placement strategy to harvest any optimizations from
- * the absence of an intermadiate step. The above version can still be used
+ * the absence of an intermediate step. The above version can still be used
* in the implementation.
*
* @param item The item to pack.
* @return Returns true if the item was packed or false if it could not be
* packed.
*/
- inline bool pack(Item& item) { return impl_.pack(item); }
+ template<class Range = ConstItemRange<DefaultIterator>>
+ inline bool pack(
+ Item& item,
+ const Range& remaining = Range())
+ {
+ return impl_.pack(item, remaining);
+ }
/// Unpack the last element (remove it from the list of packed items).
inline void unpackLast() { impl_.unpackLast(); }
@@ -597,13 +636,6 @@ public:
inline double filledArea() const { return impl_.filledArea(); }
-#ifndef NDEBUG
- inline auto getDebugItems() -> decltype(impl_.debug_items_)&
- {
- return impl_.debug_items_;
- }
-#endif
-
};
// The progress function will be called with the number of placed items
@@ -628,15 +660,15 @@ public:
* Note that it depends on the particular placer implementation how it
* reacts to config changes in the middle of a calculation.
*
- * @param config The configuration object defined by the selection startegy.
+ * @param config The configuration object defined by the selection strategy.
*/
inline void configure(const Config& config) {
impl_.configure(config);
}
/**
- * @brief A function callback which should be called whenewer an item or
- * a group of items where succesfully packed.
+ * @brief A function callback which should be called whenever an item or
+ * a group of items where successfully packed.
* @param fn A function callback object taking one unsigned integer as the
* number of the remaining items to pack.
*/
@@ -649,7 +681,7 @@ public:
* placer compatible with the PlacementStrategyLike interface.
*
* \param first, last The first and last iterator if the input sequence. It
- * can be only an iterator of a type converitible to Item.
+ * can be only an iterator of a type convertible to Item.
* \param bin. The shape of the bin. It has to be supported by the placement
* strategy.
* \param An optional config object for the placer.
@@ -681,7 +713,7 @@ public:
/**
* @brief Get the items for a particular bin.
* @param binIndex The index of the requested bin.
- * @return Returns a list of allitems packed into the requested bin.
+ * @return Returns a list of all items packed into the requested bin.
*/
inline ItemGroup itemsForBin(size_t binIndex) {
return impl_.itemsForBin(binIndex);
@@ -723,15 +755,14 @@ using _IndexedPackGroup = std::vector<
>;
/**
- * The Arranger is the frontend class for the binpack2d library. It takes the
+ * The Arranger is the front-end class for the libnest2d library. It takes the
* input items and outputs the items with the proper transformations to be
* inside the provided bin.
*/
template<class PlacementStrategy, class SelectionStrategy >
-class Arranger {
+class Nester {
using TSel = SelectionStrategyLike<SelectionStrategy>;
TSel selector_;
- bool use_min_bb_rotation_ = false;
public:
using Item = typename PlacementStrategy::Item;
using ItemRef = std::reference_wrapper<Item>;
@@ -769,7 +800,7 @@ public:
template<class TBinType = BinType,
class PConf = PlacementConfig,
class SConf = SelectionConfig>
- Arranger( TBinType&& bin,
+ Nester( TBinType&& bin,
Unit min_obj_distance = 0,
PConf&& pconfig = PConf(),
SConf&& sconfig = SConf()):
@@ -802,9 +833,9 @@ public:
* the selection algorithm.
*/
template<class TIterator>
- inline PackGroup arrange(TIterator from, TIterator to)
+ inline PackGroup execute(TIterator from, TIterator to)
{
- return _arrange(from, to);
+ return _execute(from, to);
}
/**
@@ -815,20 +846,20 @@ public:
* input sequence size.
*/
template<class TIterator>
- inline IndexedPackGroup arrangeIndexed(TIterator from, TIterator to)
+ inline IndexedPackGroup executeIndexed(TIterator from, TIterator to)
{
- return _arrangeIndexed(from, to);
+ return _executeIndexed(from, to);
}
/// Shorthand to normal arrange method.
template<class TIterator>
inline PackGroup operator() (TIterator from, TIterator to)
{
- return _arrange(from, to);
+ return _execute(from, to);
}
- /// Set a progress indicatior function object for the selector.
- inline Arranger& progressIndicator(ProgressFunction func)
+ /// Set a progress indicator function object for the selector.
+ inline Nester& progressIndicator(ProgressFunction func)
{
selector_.progressIndicator(func); return *this;
}
@@ -842,24 +873,20 @@ public:
return ret;
}
- inline Arranger& useMinimumBoundigBoxRotation(bool s = true) {
- use_min_bb_rotation_ = s; return *this;
- }
-
private:
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
- // This funtion will be used only if the iterators are pointing to
- // a type compatible with the binpack2d::_Item template.
+ // This function will be used only if the iterators are pointing to
+ // a type compatible with the libnets2d::_Item template.
// This way we can use references to input elements as they will
// have to exist for the lifetime of this call.
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
>
- inline PackGroup _arrange(TIterator from, TIterator to, bool = false)
+ inline PackGroup _execute(TIterator from, TIterator to, bool = false)
{
- __arrange(from, to);
+ __execute(from, to);
return lastResult();
}
@@ -867,28 +894,28 @@ private:
class IT = remove_cvref_t<typename TIterator::value_type>,
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
>
- inline PackGroup _arrange(TIterator from, TIterator to, int = false)
+ inline PackGroup _execute(TIterator from, TIterator to, int = false)
{
item_cache_ = {from, to};
- __arrange(item_cache_.begin(), item_cache_.end());
+ __execute(item_cache_.begin(), item_cache_.end());
return lastResult();
}
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
- // This funtion will be used only if the iterators are pointing to
- // a type compatible with the binpack2d::_Item template.
+ // This function will be used only if the iterators are pointing to
+ // a type compatible with the libnest2d::_Item template.
// This way we can use references to input elements as they will
// have to exist for the lifetime of this call.
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
>
- inline IndexedPackGroup _arrangeIndexed(TIterator from,
+ inline IndexedPackGroup _executeIndexed(TIterator from,
TIterator to,
bool = false)
{
- __arrange(from, to);
+ __execute(from, to);
return createIndexedPackGroup(from, to, selector_);
}
@@ -896,12 +923,12 @@ private:
class IT = remove_cvref_t<typename TIterator::value_type>,
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
>
- inline IndexedPackGroup _arrangeIndexed(TIterator from,
+ inline IndexedPackGroup _executeIndexed(TIterator from,
TIterator to,
int = false)
{
item_cache_ = {from, to};
- __arrange(item_cache_.begin(), item_cache_.end());
+ __execute(item_cache_.begin(), item_cache_.end());
return createIndexedPackGroup(from, to, selector_);
}
@@ -933,37 +960,12 @@ private:
return pg;
}
- Radians findBestRotation(Item& item) {
- opt::StopCriteria stopcr;
- stopcr.absolute_score_difference = 0.01;
- stopcr.max_iterations = 10000;
- opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr);
-
- auto orig_rot = item.rotation();
-
- auto result = solver.optimize_min([&item, &orig_rot](Radians rot){
- item.rotation(orig_rot + rot);
- auto bb = item.boundingBox();
- return std::sqrt(bb.height()*bb.width());
- }, opt::initvals(Radians(0)), opt::bound<Radians>(-Pi/2, Pi/2));
-
- item.rotation(orig_rot);
-
- return std::get<0>(result.optimum);
- }
-
- template<class TIter> inline void __arrange(TIter from, TIter to)
+ template<class TIter> inline void __execute(TIter from, TIter to)
{
if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) {
item.addOffset(static_cast<Unit>(std::ceil(min_obj_distance_/2.0)));
});
- if(use_min_bb_rotation_)
- std::for_each(from, to, [this](Item& item){
- Radians rot = findBestRotation(item);
- item.rotate(rot);
- });
-
selector_.template packItems<PlacementStrategy>(
from, to, bin_, pconfig_);
diff --git a/xs/src/libnest2d/libnest2d/metaloop.hpp b/xs/src/libnest2d/libnest2d/metaloop.hpp
index 18755525c..d88988ba1 100644
--- a/xs/src/libnest2d/libnest2d/metaloop.hpp
+++ b/xs/src/libnest2d/libnest2d/metaloop.hpp
@@ -67,11 +67,11 @@ class metaloop {
// need to wrap that in a type (see metaloop::Int).
/*
- * A helper alias to create integer values wrapped as a type. It is nessecary
+ * A helper alias to create integer values wrapped as a type. It is necessary
* because a non type template parameter (such as int) would be prohibited in
* a partial specialization. Also for the same reason we have to use a class
* _Metaloop instead of a simple function as a functor. A function cannot be
- * partially specialized in a way that is neccesary for this trick.
+ * partially specialized in a way that is necessary for this trick.
*/
template<int N> using Int = std::integral_constant<int, N>;
@@ -88,7 +88,7 @@ public:
// It takes the real functor that can be specified in-place but only
// with C++14 because the second parameter's type will depend on the
// type of the parameter pack element that is processed. In C++14 we can
- // specify this second parameter type as auto in the lamda parameter list.
+ // specify this second parameter type as auto in the lambda parameter list.
inline MapFn(Fn&& fn): fn_(forward<Fn>(fn)) {}
template<class T> void operator ()(T&& pack_element) {
@@ -146,7 +146,7 @@ public:
* version of run is called which does not call itself anymore.
*
* If you are utterly annoyed, at least you have learned a super crazy
- * functional metaprogramming pattern.
+ * functional meta-programming pattern.
*/
template<class...Args>
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>;
diff --git a/xs/src/libnest2d/libnest2d/optimizer.hpp b/xs/src/libnest2d/libnest2d/optimizer.hpp
index c8ed2e378..90d2f2ff9 100644
--- a/xs/src/libnest2d/libnest2d/optimizer.hpp
+++ b/xs/src/libnest2d/libnest2d/optimizer.hpp
@@ -102,6 +102,9 @@ struct StopCriteria {
/// If the relative value difference between two scores.
double relative_score_difference = std::nan("");
+ /// Stop if this value or better is found.
+ double stop_score = std::nan("");
+
unsigned max_iterations = 0;
};
diff --git a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp
index 737ca6e3c..1a0f06e02 100644
--- a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp
+++ b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp
@@ -142,10 +142,12 @@ protected:
default: ;
}
- auto abs_diff = stopcr_.absolute_score_difference;
- auto rel_diff = stopcr_.relative_score_difference;
+ double abs_diff = stopcr_.absolute_score_difference;
+ double rel_diff = stopcr_.relative_score_difference;
+ double stopval = stopcr_.stop_score;
if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff);
if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff);
+ if(!std::isnan(stopval)) opt_.set_stopval(stopval);
if(this->stopcr_.max_iterations > 0)
opt_.set_maxeval(this->stopcr_.max_iterations );
diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp
index 775e44e09..18c27c40c 100644
--- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp
+++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp
@@ -5,11 +5,26 @@
#include "placer_boilerplate.hpp"
-namespace libnest2d { namespace strategies {
+namespace libnest2d { namespace placers {
+
+template<class T, class = T> struct Epsilon {};
+
+template<class T>
+struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
+ static const T Value = 1;
+};
+
+template<class T>
+struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
+ static const T Value = 1e-3;
+};
template<class RawShape>
struct BLConfig {
- TCoord<TPoint<RawShape>> min_obj_distance = 0;
+ DECLARE_MAIN_TYPES(RawShape);
+
+ Coord min_obj_distance = 0;
+ Coord epsilon = Epsilon<Coord>::Value;
bool allow_rotations = false;
};
@@ -27,9 +42,13 @@ public:
explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {}
- PackResult trypack(Item& item) {
+ template<class Range = ConstItemRange<typename Base::DefaultIter>>
+ PackResult trypack(Item& item,
+ const Range& = Range())
+ {
auto r = _trypack(item);
if(!r && Base::config_.allow_rotations) {
+
item.rotate(Degrees(90));
r =_trypack(item);
}
@@ -65,20 +84,21 @@ protected:
setInitialPosition(item);
Unit d = availableSpaceDown(item);
- bool can_move = d > 1 /*std::numeric_limits<Unit>::epsilon()*/;
+ auto eps = config_.epsilon;
+ bool can_move = d > eps;
bool can_be_packed = can_move;
bool left = true;
while(can_move) {
if(left) { // write previous down move and go down
- item.translate({0, -d+1});
+ item.translate({0, -d+eps});
d = availableSpaceLeft(item);
- can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
+ can_move = d > eps;
left = false;
} else { // write previous left move and go down
- item.translate({-d+1, 0});
+ item.translate({-d+eps, 0});
d = availableSpaceDown(item);
- can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
+ can_move = d > eps;
left = true;
}
}
@@ -112,10 +132,10 @@ protected:
const RawShape& scanpoly)
{
auto tsh = other.transformedShape();
- return ( ShapeLike::intersects(tsh, scanpoly) ||
- ShapeLike::isInside(tsh, scanpoly) ) &&
- ( !ShapeLike::intersects(tsh, item.rawShape()) &&
- !ShapeLike::isInside(tsh, item.rawShape()) );
+ return ( sl::intersects(tsh, scanpoly) ||
+ sl::isInside(tsh, scanpoly) ) &&
+ ( !sl::intersects(tsh, item.rawShape()) &&
+ !sl::isInside(tsh, item.rawShape()) );
}
template<class C = Coord>
@@ -126,25 +146,25 @@ protected:
{
auto tsh = other.transformedShape();
- bool inters_scanpoly = ShapeLike::intersects(tsh, scanpoly) &&
- !ShapeLike::touches(tsh, scanpoly);
- bool inters_item = ShapeLike::intersects(tsh, item.rawShape()) &&
- !ShapeLike::touches(tsh, item.rawShape());
+ bool inters_scanpoly = sl::intersects(tsh, scanpoly) &&
+ !sl::touches(tsh, scanpoly);
+ bool inters_item = sl::intersects(tsh, item.rawShape()) &&
+ !sl::touches(tsh, item.rawShape());
return ( inters_scanpoly ||
- ShapeLike::isInside(tsh, scanpoly)) &&
+ sl::isInside(tsh, scanpoly)) &&
( !inters_item &&
- !ShapeLike::isInside(tsh, item.rawShape())
+ !sl::isInside(tsh, item.rawShape())
);
}
- Container itemsInTheWayOf(const Item& item, const Dir dir) {
+ ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) {
// Get the left or down polygon, that has the same area as the shadow
// of input item reflected to the left or downwards
auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) :
downPoly(item);
- Container ret; // packed items 'in the way' of item
+ ItemGroup ret; // packed items 'in the way' of item
ret.reserve(items_.size());
// Predicate to find items that are 'in the way' for left (down) move
@@ -173,18 +193,18 @@ protected:
if(dir == Dir::LEFT) {
getCoord = [](const Vertex& v) { return getX(v); };
- availableDistance = PointLike::horizontalDistance<Vertex>;
+ availableDistance = pointlike::horizontalDistance<Vertex>;
availableDistanceSV = [](const Segment& s, const Vertex& v) {
- auto ret = PointLike::horizontalDistance<Vertex>(v, s);
+ auto ret = pointlike::horizontalDistance<Vertex>(v, s);
if(ret.second) ret.first = -ret.first;
return ret;
};
}
else {
getCoord = [](const Vertex& v) { return getY(v); };
- availableDistance = PointLike::verticalDistance<Vertex>;
+ availableDistance = pointlike::verticalDistance<Vertex>;
availableDistanceSV = [](const Segment& s, const Vertex& v) {
- auto ret = PointLike::verticalDistance<Vertex>(v, s);
+ auto ret = pointlike::verticalDistance<Vertex>(v, s);
if(ret.second) ret.first = -ret.first;
return ret;
};
@@ -214,9 +234,9 @@ protected:
assert(pleft.vertexCount() > 0);
auto trpleft = pleft.transformedShape();
- auto first = ShapeLike::begin(trpleft);
+ auto first = sl::begin(trpleft);
auto next = first + 1;
- auto endit = ShapeLike::end(trpleft);
+ auto endit = sl::end(trpleft);
while(next != endit) {
Segment seg(*(first++), *(next++));
@@ -340,16 +360,16 @@ protected:
// reserve for all vertices plus 2 for the left horizontal wall, 2 for
// the additional vertices for maintaning min object distance
- ShapeLike::reserve(rsh, finish-start+4);
+ sl::reserve(rsh, finish-start+4);
/*auto addOthers = [&rsh, finish, start, &item](){
for(size_t i = start+1; i < finish; i++)
- ShapeLike::addVertex(rsh, item.vertex(i));
+ sl::addVertex(rsh, item.vertex(i));
};*/
auto reverseAddOthers = [&rsh, finish, start, &item](){
for(auto i = finish-1; i > start; i--)
- ShapeLike::addVertex(rsh, item.vertex(
+ sl::addVertex(rsh, item.vertex(
static_cast<unsigned long>(i)));
};
@@ -361,25 +381,25 @@ protected:
// Clockwise polygon construction
- ShapeLike::addVertex(rsh, topleft_vertex);
+ sl::addVertex(rsh, topleft_vertex);
if(dir == Dir::LEFT) reverseAddOthers();
else {
- ShapeLike::addVertex(rsh, getX(topleft_vertex), 0);
- ShapeLike::addVertex(rsh, getX(bottomleft_vertex), 0);
+ sl::addVertex(rsh, getX(topleft_vertex), 0);
+ sl::addVertex(rsh, getX(bottomleft_vertex), 0);
}
- ShapeLike::addVertex(rsh, bottomleft_vertex);
+ sl::addVertex(rsh, bottomleft_vertex);
if(dir == Dir::LEFT) {
- ShapeLike::addVertex(rsh, 0, getY(bottomleft_vertex));
- ShapeLike::addVertex(rsh, 0, getY(topleft_vertex));
+ sl::addVertex(rsh, 0, getY(bottomleft_vertex));
+ sl::addVertex(rsh, 0, getY(topleft_vertex));
}
else reverseAddOthers();
// Close the polygon
- ShapeLike::addVertex(rsh, topleft_vertex);
+ sl::addVertex(rsh, topleft_vertex);
return rsh;
}
diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp
index 5d09a61fc..c86fb507e 100644
--- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp
+++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp
@@ -1,21 +1,148 @@
#ifndef NOFITPOLY_HPP
#define NOFITPOLY_HPP
+#include <cassert>
+
+// For caching nfps
+#include <unordered_map>
+
+// For parallel for
+#include <functional>
+#include <iterator>
+#include <future>
+#include <atomic>
+
#ifndef NDEBUG
#include <iostream>
#endif
#include "placer_boilerplate.hpp"
#include "../geometry_traits_nfp.hpp"
#include "libnest2d/optimizer.hpp"
-#include <cassert>
#include "tools/svgtools.hpp"
-namespace libnest2d { namespace strategies {
+#ifdef USE_TBB
+#include <tbb/parallel_for.h>
+#elif defined(_OPENMP)
+#include <omp.h>
+#endif
+
+namespace libnest2d {
+
+namespace __parallel {
+
+using std::function;
+using std::iterator_traits;
+template<class It>
+using TIteratorValue = typename iterator_traits<It>::value_type;
+
+template<class Iterator>
+inline void enumerate(
+ Iterator from, Iterator to,
+ function<void(TIteratorValue<Iterator>, size_t)> fn,
+ std::launch policy = std::launch::deferred | std::launch::async)
+{
+ using TN = size_t;
+ auto iN = to-from;
+ TN N = iN < 0? 0 : TN(iN);
+
+#ifdef USE_TBB
+ if((policy & std::launch::async) == std::launch::async) {
+ tbb::parallel_for<TN>(0, N, [from, fn] (TN n) { fn(*(from + n), n); } );
+ } else {
+ for(TN n = 0; n < N; n++) fn(*(from + n), n);
+ }
+#elif defined(_OPENMP)
+ if((policy & std::launch::async) == std::launch::async) {
+ #pragma omp parallel for
+ for(TN n = 0; n < N; n++) fn(*(from + n), n);
+ }
+ else {
+ for(TN n = 0; n < N; n++) fn(*(from + n), n);
+ }
+#else
+ std::vector<std::future<void>> rets(N);
+
+ auto it = from;
+ for(TN b = 0; b < N; b++) {
+ rets[b] = std::async(policy, fn, *it++, unsigned(b));
+ }
+
+ for(TN fi = 0; fi < N; ++fi) rets[fi].wait();
+#endif
+}
+
+class SpinLock {
+ static std::atomic_flag locked;
+public:
+ void lock() {
+ while (locked.test_and_set(std::memory_order_acquire)) { ; }
+ }
+ void unlock() {
+ locked.clear(std::memory_order_release);
+ }
+};
+
+std::atomic_flag SpinLock::locked = ATOMIC_FLAG_INIT ;
+
+}
+
+namespace __itemhash {
+
+using Key = size_t;
+
+template<class S>
+Key hash(const _Item<S>& item) {
+ using Point = TPoint<S>;
+ using Segment = _Segment<Point>;
+
+ static const int N = 26;
+ static const int M = N*N - 1;
+
+ std::string ret;
+ auto& rhs = item.rawShape();
+ auto& ctr = sl::getContour(rhs);
+ auto it = ctr.begin();
+ auto nx = std::next(it);
+
+ double circ = 0;
+ while(nx != ctr.end()) {
+ Segment seg(*it++, *nx++);
+ Radians a = seg.angleToXaxis();
+ double deg = Degrees(a);
+ int ms = 'A', ls = 'A';
+ while(deg > N) { ms++; deg -= N; }
+ ls += int(deg);
+ ret.push_back(char(ms)); ret.push_back(char(ls));
+ circ += seg.length();
+ }
+
+ it = ctr.begin(); nx = std::next(it);
+
+ while(nx != ctr.end()) {
+ Segment seg(*it++, *nx++);
+ auto l = int(M * seg.length() / circ);
+ int ms = 'A', ls = 'A';
+ while(l > N) { ms++; l -= N; }
+ ls += l;
+ ret.push_back(char(ms)); ret.push_back(char(ls));
+ }
+
+ return std::hash<std::string>()(ret);
+}
+
+template<class S>
+using Hash = std::unordered_map<Key, nfp::NfpResult<S>>;
+
+}
+
+namespace placers {
template<class RawShape>
struct NfpPConfig {
+ using ItemGroup = _ItemGroup<_Item<RawShape>>;
+
enum class Alignment {
CENTER,
BOTTOM_LEFT,
@@ -45,47 +172,19 @@ struct NfpPConfig {
* that will optimize for the best pack efficiency. With a custom fitting
* function you can e.g. influence the shape of the arranged pile.
*
- * \param shapes The first parameter is a container with all the placed
- * polygons excluding the current candidate. You can calculate a bounding
- * box or convex hull on this pile of polygons without the candidate item
- * or push back the candidate item into the container and then calculate
- * some features.
- *
- * \param item The second parameter is the candidate item.
- *
- * \param occupied_area The third parameter is the sum of areas of the
- * items in the first parameter so you don't have to iterate through them
- * if you only need their area.
- *
- * \param norm A norming factor for physical dimensions. E.g. if your score
- * is the distance between the item and the bin center, you should divide
- * that distance with the norming factor. If the score is an area than
- * divide it with the square of the norming factor. Imagine it as a unit of
- * distance.
- *
- * \param penality The fifth parameter is the amount of minimum penality if
- * the arranged pile would't fit into the bin. You can use the wouldFit()
- * function to check this. Note that the pile can be outside the bin's
- * boundaries while the placement algorithm is running. Your job is only to
- * check if the pile could be translated into a position in the bin where
- * all the items would be inside. For a box shaped bin you can use the
- * pile's bounding box to check whether it's width and height is small
- * enough. If the pile would not fit, you have to make sure that the
- * resulting score will be higher then the penality value. A good solution
- * would be to set score = 2*penality-score in case the pile wouldn't fit
- * into the bin.
+ * \param item The only parameter is the candidate item which has info
+ * about its current position. Your job is to rate this position compared to
+ * the already packed items.
*
*/
- std::function<double(Nfp::Shapes<RawShape>&, const _Item<RawShape>&,
- double, double, double)>
- object_function;
+ std::function<double(const _Item<RawShape>&)> object_function;
/**
* @brief The quality of search for an optimal placement.
* This is a compromise slider between quality and speed. Zero is the
* fast and poor solution while 1.0 is the slowest but most accurate.
*/
- float accuracy = 1.0;
+ float accuracy = 0.65f;
/**
* @brief If you want to see items inside other item's holes, you have to
@@ -96,6 +195,39 @@ struct NfpPConfig {
*/
bool explore_holes = false;
+ /**
+ * @brief If true, use all CPUs available. Run on a single core otherwise.
+ */
+ bool parallel = true;
+
+ /**
+ * @brief before_packing Callback that is called just before a search for
+ * a new item's position is started. You can use this to create various
+ * cache structures and update them between subsequent packings.
+ *
+ * \param merged pile A polygon that is the union of all items in the bin.
+ *
+ * \param pile The items parameter is a container with all the placed
+ * polygons excluding the current candidate. You can for instance check the
+ * alignment with the candidate item or do anything else.
+ *
+ * \param remaining A container with the remaining items waiting to be
+ * placed. You can use some features about the remaining items to alter to
+ * score of the current placement. If you know that you have to leave place
+ * for other items as well, that might influence your decision about where
+ * the current candidate should be placed. E.g. imagine three big circles
+ * which you want to place into a box: you might place them in a triangle
+ * shape which has the maximum pack density. But if there is a 4th big
+ * circle than you won't be able to pack it. If you knew apriori that
+ * there four circles are to be placed, you would have placed the first 3
+ * into an L shape. This parameter can be used to make these kind of
+ * decisions (for you or a more intelligent AI).
+ */
+ std::function<void(const nfp::Shapes<RawShape>&, // merged pile
+ const ItemGroup&, // packed items
+ const ItemGroup& // remaining items
+ )> before_packing;
+
NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}),
alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {}
};
@@ -129,11 +261,11 @@ template<class RawShape> class EdgeCache {
void createCache(const RawShape& sh) {
{ // For the contour
- auto first = ShapeLike::cbegin(sh);
+ auto first = shapelike::cbegin(sh);
auto next = std::next(first);
- auto endit = ShapeLike::cend(sh);
+ auto endit = shapelike::cend(sh);
- contour_.distances.reserve(ShapeLike::contourVertexCount(sh));
+ contour_.distances.reserve(shapelike::contourVertexCount(sh));
while(next != endit) {
contour_.emap.emplace_back(*(first++), *(next++));
@@ -142,7 +274,7 @@ template<class RawShape> class EdgeCache {
}
}
- for(auto& h : ShapeLike::holes(sh)) { // For the holes
+ for(auto& h : shapelike::holes(sh)) { // For the holes
auto first = h.begin();
auto next = std::next(first);
auto endit = h.end();
@@ -161,12 +293,11 @@ template<class RawShape> class EdgeCache {
}
size_t stride(const size_t N) const {
- using std::ceil;
using std::round;
using std::pow;
return static_cast<Coord>(
- std::round(N/std::pow(N, std::pow(accuracy_, 1.0/3.0)))
+ round(N/pow(N, pow(accuracy_, 1.0/3.0)))
);
}
@@ -177,6 +308,7 @@ template<class RawShape> class EdgeCache {
const auto S = stride(N);
contour_.corners.reserve(N / S + 1);
+ contour_.corners.emplace_back(0.0);
auto N_1 = N-1;
contour_.corners.emplace_back(0.0);
for(size_t i = 0; i < N_1; i += S) {
@@ -190,8 +322,8 @@ template<class RawShape> class EdgeCache {
if(!hc.corners.empty()) return;
const auto N = hc.distances.size();
- const auto S = stride(N);
auto N_1 = N-1;
+ const auto S = stride(N);
hc.corners.reserve(N / S + 1);
hc.corners.emplace_back(0.0);
for(size_t i = 0; i < N_1; i += S) {
@@ -290,11 +422,11 @@ public:
};
-template<NfpLevel lvl>
-struct Lvl { static const NfpLevel value = lvl; };
+template<nfp::NfpLevel lvl>
+struct Lvl { static const nfp::NfpLevel value = lvl; };
template<class RawShape>
-inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
+inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
const _Item<RawShape>& stationary,
const _Item<RawShape>& orbiter)
{
@@ -314,131 +446,78 @@ inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
auto dtouch = touch_sh - touch_other;
auto top_other = orbiter.rightmostTopVertex() + dtouch;
auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point
- ShapeLike::translate(nfp.first, dnfp);
+ shapelike::translate(nfp.first, dnfp);
}
template<class RawShape>
-inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
+inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
const RawShape& stationary,
const _Item<RawShape>& orbiter)
{
- auto touch_sh = Nfp::rightmostUpVertex(stationary);
+ auto touch_sh = nfp::rightmostUpVertex(stationary);
auto touch_other = orbiter.leftmostBottomVertex();
auto dtouch = touch_sh - touch_other;
auto top_other = orbiter.rightmostTopVertex() + dtouch;
auto dnfp = top_other - nfp.second;
- ShapeLike::translate(nfp.first, dnfp);
-}
-
-template<class RawShape, class Container>
-Nfp::Shapes<RawShape> nfp( const Container& polygons,
- const _Item<RawShape>& trsh,
- Lvl<NfpLevel::CONVEX_ONLY>)
-{
- using Item = _Item<RawShape>;
-
- Nfp::Shapes<RawShape> nfps;
-
- //int pi = 0;
- for(Item& sh : polygons) {
- auto subnfp_r = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
- sh.transformedShape(), trsh.transformedShape());
- #ifndef NDEBUG
- auto vv = ShapeLike::isValid(sh.transformedShape());
- assert(vv.first);
-
- auto vnfp = ShapeLike::isValid(subnfp_r.first);
- assert(vnfp.first);
- #endif
-
- correctNfpPosition(subnfp_r, sh, trsh);
-
- nfps = Nfp::merge(nfps, subnfp_r.first);
-
-// double SCALE = 1000000;
-// using SVGWriter = svg::SVGWriter<RawShape>;
-// SVGWriter::Config conf;
-// conf.mm_in_coord_units = SCALE;
-// SVGWriter svgw(conf);
-// Box bin(250*SCALE, 210*SCALE);
-// svgw.setSize(bin);
-// for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]);
-// svgw.writeItem(trsh);
-//// svgw.writeItem(Item(subnfp_r.first));
-// for(auto& n : nfps) svgw.writeItem(Item(n));
-// svgw.save("nfpout");
-// pi++;
- }
-
- return nfps;
+ shapelike::translate(nfp.first, dnfp);
}
-template<class RawShape, class Container, class Level>
-Nfp::Shapes<RawShape> nfp( const Container& polygons,
- const _Item<RawShape>& trsh,
- Level)
-{
- using Item = _Item<RawShape>;
-
- Nfp::Shapes<RawShape> nfps;
-
- auto& orb = trsh.transformedShape();
- bool orbconvex = trsh.isContourConvex();
+template<class RawShape, class Circle = _Circle<TPoint<RawShape>> >
+Circle minimizeCircle(const RawShape& sh) {
+ using Point = TPoint<RawShape>;
+ using Coord = TCoord<Point>;
- for(Item& sh : polygons) {
- Nfp::NfpResult<RawShape> subnfp;
- auto& stat = sh.transformedShape();
+ auto& ctr = sl::getContour(sh);
+ if(ctr.empty()) return {{0, 0}, 0};
- if(sh.isContourConvex() && orbconvex)
- subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb);
- else if(orbconvex)
- subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb);
- else
- subnfp = Nfp::noFitPolygon<Level::value>(stat, orb);
+ auto bb = sl::boundingBox(sh);
+ auto capprx = bb.center();
+ auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner());
- correctNfpPosition(subnfp, sh, trsh);
- nfps = Nfp::merge(nfps, subnfp.first);
- }
+ opt::StopCriteria stopcr;
+ stopcr.max_iterations = 30;
+ stopcr.relative_score_difference = 1e-3;
+ opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
- return nfps;
+ std::vector<double> dists(ctr.size(), 0);
+ auto result = solver.optimize_min(
+ [capprx, rapprx, &ctr, &dists](double xf, double yf) {
+ auto xt = Coord( std::round(getX(capprx) + rapprx*xf) );
+ auto yt = Coord( std::round(getY(capprx) + rapprx*yf) );
-// using Item = _Item<RawShape>;
-// using sl = ShapeLike;
+ Point centr(xt, yt);
-// Nfp::Shapes<RawShape> nfps, stationary;
+ unsigned i = 0;
+ for(auto v : ctr) {
+ dists[i++] = pl::distance(v, centr);
+ }
-// for(Item& sh : polygons) {
-// stationary = Nfp::merge(stationary, sh.transformedShape());
-// }
+ auto mit = std::max_element(dists.begin(), dists.end());
-// for(RawShape& sh : stationary) {
+ assert(mit != dists.end());
-//// auto vv = sl::isValid(sh);
-//// std::cout << vv.second << std::endl;
+ return *mit;
+ },
+ opt::initvals(0.0, 0.0),
+ opt::bound(-1.0, 1.0), opt::bound(-1.0, 1.0)
+ );
+ double oxf = std::get<0>(result.optimum);
+ double oyf = std::get<1>(result.optimum);
+ auto xt = Coord( std::round(getX(capprx) + rapprx*oxf) );
+ auto yt = Coord( std::round(getY(capprx) + rapprx*oyf) );
-// Nfp::NfpResult<RawShape> subnfp;
-// bool shconvex = sl::isConvex<RawShape>(sl::getContour(sh));
-// if(shconvex && trsh.isContourConvex()) {
-// subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
-// sh, trsh.transformedShape());
-// } else if(trsh.isContourConvex()) {
-// subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(
-// sh, trsh.transformedShape());
-// }
-// else {
-// subnfp = Nfp::noFitPolygon<Level::value>( sh,
-// trsh.transformedShape());
-// }
+ Point cc(xt, yt);
+ auto r = result.score;
-// correctNfpPosition(subnfp, sh, trsh);
-
-// nfps = Nfp::merge(nfps, subnfp.first);
-// }
+ return {cc, r};
+}
-// return nfps;
+template<class RawShape>
+_Circle<TPoint<RawShape>> boundingCircle(const RawShape& sh) {
+ return minimizeCircle(sh);
}
template<class RawShape, class TBin = _Box<TPoint<RawShape>>>
@@ -452,20 +531,26 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin
using Box = _Box<TPoint<RawShape>>;
+ using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
+
+ using ItemKeys = std::vector<__itemhash::Key>;
+
+ // Norming factor for the optimization function
const double norm_;
- const double penality_;
- using MaxNfpLevel = Nfp::MaxNfpLevel<RawShape>;
- using sl = ShapeLike;
+ // Caching calculated nfps
+ __itemhash::Hash<RawShape> nfpcache_;
+
+ // Storing item hash keys
+ ItemKeys item_keys_;
public:
- using Pile = Nfp::Shapes<RawShape>;
+ using Pile = nfp::Shapes<RawShape>;
inline explicit _NofitPolyPlacer(const BinType& bin):
Base(bin),
- norm_(std::sqrt(sl::area<RawShape>(bin))),
- penality_(1e6*norm_) {}
+ norm_(std::sqrt(sl::area(bin))) {}
_NofitPolyPlacer(const _NofitPolyPlacer&) = default;
_NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
@@ -475,81 +560,322 @@ public:
_NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default;
#endif
- bool static inline wouldFit(const Box& bb, const RawShape& bin) {
- auto bbin = sl::boundingBox<RawShape>(bin);
+ static inline double overfit(const Box& bb, const RawShape& bin) {
+ auto bbin = sl::boundingBox(bin);
auto d = bbin.center() - bb.center();
_Rectangle<RawShape> rect(bb.width(), bb.height());
rect.translate(bb.minCorner() + d);
- return sl::isInside<RawShape>(rect.transformedShape(), bin);
+ return sl::isInside(rect.transformedShape(), bin) ? -1.0 : 1;
}
- bool static inline wouldFit(const RawShape& chull, const RawShape& bin) {
- auto bbch = sl::boundingBox<RawShape>(chull);
- auto bbin = sl::boundingBox<RawShape>(bin);
+ static inline double overfit(const RawShape& chull, const RawShape& bin) {
+ auto bbch = sl::boundingBox(chull);
+ auto bbin = sl::boundingBox(bin);
auto d = bbch.center() - bbin.center();
auto chullcpy = chull;
sl::translate(chullcpy, d);
- return sl::isInside<RawShape>(chullcpy, bin);
+ return sl::isInside(chullcpy, bin) ? -1.0 : 1.0;
}
- bool static inline wouldFit(const RawShape& chull, const Box& bin)
+ static inline double overfit(const RawShape& chull, const Box& bin)
{
- auto bbch = sl::boundingBox<RawShape>(chull);
- return wouldFit(bbch, bin);
+ auto bbch = sl::boundingBox(chull);
+ return overfit(bbch, bin);
}
- bool static inline wouldFit(const Box& bb, const Box& bin)
+ static inline double overfit(const Box& bb, const Box& bin)
{
- return bb.width() <= bin.width() && bb.height() <= bin.height();
+ auto wdiff = double(bb.width() - bin.width());
+ auto hdiff = double(bb.height() - bin.height());
+ double diff = 0;
+ if(wdiff > 0) diff += wdiff;
+ if(hdiff > 0) diff += hdiff;
+ return diff;
}
- bool static inline wouldFit(const Box& bb, const _Circle<Vertex>& bin)
+ static inline double overfit(const Box& bb, const _Circle<Vertex>& bin)
{
- return sl::isInside<RawShape>(bb, bin);
+ double boxr = 0.5*pl::distance(bb.minCorner(), bb.maxCorner());
+ double diff = boxr - bin.radius();
+ return diff;
}
- bool static inline wouldFit(const RawShape& chull,
+ static inline double overfit(const RawShape& chull,
const _Circle<Vertex>& bin)
{
- return sl::isInside<RawShape>(chull, bin);
+ double r = boundingCircle(chull).radius();
+ double diff = r - bin.radius();
+ return diff;
+ }
+
+ template<class Range = ConstItemRange<typename Base::DefaultIter>>
+ PackResult trypack(Item& item,
+ const Range& remaining = Range()) {
+ auto result = _trypack(item, remaining);
+
+ // Experimental
+ // if(!result) repack(item, result);
+
+ return result;
+ }
+
+ ~_NofitPolyPlacer() {
+ clearItems();
+ }
+
+ inline void clearItems() {
+ finalAlign(bin_);
+ Base::clearItems();
+ }
+
+private:
+
+ using Shapes = TMultiShape<RawShape>;
+ using ItemRef = std::reference_wrapper<Item>;
+ using ItemWithHash = const std::pair<ItemRef, __itemhash::Key>;
+
+ Shapes calcnfp(const ItemWithHash itsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
+ {
+ using namespace nfp;
+
+ Shapes nfps(items_.size());
+ const Item& trsh = itsh.first;
+
+ __parallel::enumerate(items_.begin(), items_.end(),
+ [&nfps, &trsh](const Item& sh, size_t n)
+ {
+ auto& fixedp = sh.transformedShape();
+ auto& orbp = trsh.transformedShape();
+ auto subnfp_r = noFitPolygon<NfpLevel::CONVEX_ONLY>(fixedp, orbp);
+ correctNfpPosition(subnfp_r, sh, trsh);
+ nfps[n] = subnfp_r.first;
+ });
+
+// for(auto& n : nfps) {
+// auto valid = sl::isValid(n);
+// if(!valid.first) std::cout << "Warning: " << valid.second << std::endl;
+// }
+
+ return nfp::merge(nfps);
}
- PackResult trypack(Item& item) {
+ template<class Level>
+ Shapes calcnfp( const ItemWithHash itsh, Level)
+ { // Function for arbitrary level of nfp implementation
+ using namespace nfp;
+
+ Shapes nfps;
+ const Item& trsh = itsh.first;
+
+ auto& orb = trsh.transformedShape();
+ bool orbconvex = trsh.isContourConvex();
+
+ for(Item& sh : items_) {
+ nfp::NfpResult<RawShape> subnfp;
+ auto& stat = sh.transformedShape();
+
+ if(sh.isContourConvex() && orbconvex)
+ subnfp = nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb);
+ else if(orbconvex)
+ subnfp = nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb);
+ else
+ subnfp = nfp::noFitPolygon<Level::value>(stat, orb);
+
+ correctNfpPosition(subnfp, sh, trsh);
+
+ nfps = nfp::merge(nfps, subnfp.first);
+ }
+
+ return nfps;
+ }
+
+ // Very much experimental
+ void repack(Item& item, PackResult& result) {
+
+ if((sl::area(bin_) - this->filledArea()) >= item.area()) {
+ auto prev_func = config_.object_function;
+
+ unsigned iter = 0;
+ ItemGroup backup_rf = items_;
+ std::vector<Item> backup_cpy;
+ for(Item& itm : items_) backup_cpy.emplace_back(itm);
+
+ auto ofn = [this, &item, &result, &iter, &backup_cpy, &backup_rf]
+ (double ratio)
+ {
+ auto& bin = bin_;
+ iter++;
+ config_.object_function = [bin, ratio](
+ nfp::Shapes<RawShape>& pile,
+ const Item& item,
+ const ItemGroup& /*remaining*/)
+ {
+ pile.emplace_back(item.transformedShape());
+ auto ch = sl::convexHull(pile);
+ auto pbb = sl::boundingBox(pile);
+ pile.pop_back();
+
+ double parea = 0.5*(sl::area(ch) + sl::area(pbb));
+
+ double pile_area = std::accumulate(
+ pile.begin(), pile.end(), item.area(),
+ [](double sum, const RawShape& sh){
+ return sum + sl::area(sh);
+ });
+
+ // The pack ratio -- how much is the convex hull occupied
+ double pack_rate = (pile_area)/parea;
+
+ // ratio of waste
+ double waste = 1.0 - pack_rate;
+
+ // Score is the square root of waste. This will extend the
+ // range of good (lower) values and shrink the range of bad
+ // (larger) values.
+ auto wscore = std::sqrt(waste);
+
+
+ auto ibb = item.boundingBox();
+ auto bbb = sl::boundingBox(bin);
+ auto c = ibb.center();
+ double norm = 0.5*pl::distance(bbb.minCorner(),
+ bbb.maxCorner());
+
+ double dscore = pl::distance(c, pbb.center()) / norm;
+
+ return ratio*wscore + (1.0 - ratio) * dscore;
+ };
+
+ auto bb = sl::boundingBox(bin);
+ double norm = bb.width() + bb.height();
+
+ auto items = items_;
+ clearItems();
+ auto it = items.begin();
+ while(auto pr = _trypack(*it++)) {
+ this->accept(pr); if(it == items.end()) break;
+ }
+
+ auto count_diff = items.size() - items_.size();
+ double score = count_diff;
+
+ if(count_diff == 0) {
+ result = _trypack(item);
+
+ if(result) {
+ std::cout << "Success" << std::endl;
+ score = 0.0;
+ } else {
+ score += result.overfit() / norm;
+ }
+ } else {
+ result = PackResult();
+ items_ = backup_rf;
+ for(unsigned i = 0; i < items_.size(); i++) {
+ items_[i].get() = backup_cpy[i];
+ }
+ }
+
+ std::cout << iter << " repack result: " << score << " "
+ << ratio << " " << count_diff << std::endl;
+
+ return score;
+ };
+
+ opt::StopCriteria stopcr;
+ stopcr.max_iterations = 30;
+ stopcr.stop_score = 1e-20;
+ opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
+ solver.optimize_min(ofn, opt::initvals(0.5),
+ opt::bound(0.0, 1.0));
+
+ // optimize
+ config_.object_function = prev_func;
+ }
+
+ }
+
+ struct Optimum {
+ double relpos;
+ unsigned nfpidx;
+ int hidx;
+ Optimum(double pos, unsigned nidx):
+ relpos(pos), nfpidx(nidx), hidx(-1) {}
+ Optimum(double pos, unsigned nidx, int holeidx):
+ relpos(pos), nfpidx(nidx), hidx(holeidx) {}
+ };
+
+ class Optimizer: public opt::TOptimizer<opt::Method::L_SUBPLEX> {
+ public:
+ Optimizer() {
+ opt::StopCriteria stopcr;
+ stopcr.max_iterations = 200;
+ stopcr.relative_score_difference = 1e-20;
+ this->stopcr_ = stopcr;
+ }
+ };
+
+ static Box boundingBox(const Box& pilebb, const Box& ibb ) {
+ auto& pminc = pilebb.minCorner();
+ auto& pmaxc = pilebb.maxCorner();
+ auto& iminc = ibb.minCorner();
+ auto& imaxc = ibb.maxCorner();
+ Vertex minc, maxc;
+
+ setX(minc, std::min(getX(pminc), getX(iminc)));
+ setY(minc, std::min(getY(pminc), getY(iminc)));
+
+ setX(maxc, std::max(getX(pmaxc), getX(imaxc)));
+ setY(maxc, std::max(getY(pmaxc), getY(imaxc)));
+ return Box(minc, maxc);
+ }
+
+ using Edges = EdgeCache<RawShape>;
+
+ template<class Range = ConstItemRange<typename Base::DefaultIter>>
+ PackResult _trypack(
+ Item& item,
+ const Range& remaining = Range()) {
PackResult ret;
bool can_pack = false;
+ double best_overfit = std::numeric_limits<double>::max();
+
+ auto remlist = ItemGroup(remaining.from, remaining.to);
+ size_t itemhash = __itemhash::hash(item);
if(items_.empty()) {
setInitialPosition(item);
- can_pack = item.isInside(bin_);
+ best_overfit = overfit(item.transformedShape(), bin_);
+ can_pack = best_overfit <= 0;
} else {
- double global_score = penality_;
+ double global_score = std::numeric_limits<double>::max();
auto initial_tr = item.translation();
auto initial_rot = item.rotation();
Vertex final_tr = {0, 0};
Radians final_rot = initial_rot;
- Nfp::Shapes<RawShape> nfps;
+ Shapes nfps;
for(auto rot : config_.rotations) {
item.translation(initial_tr);
item.rotation(initial_rot + rot);
+ item.boundingBox(); // fill the bb cache
// place the new item outside of the print bed to make sure
- // it is disjuct from the current merged pile
+ // it is disjunct from the current merged pile
placeOutsideOfBin(item);
- auto trsh = item.transformedShape();
+ nfps = calcnfp({item, itemhash}, Lvl<MaxNfpLevel::value>());
- nfps = nfp(items_, item, Lvl<MaxNfpLevel::value>());
- auto iv = Nfp::referenceVertex(trsh);
+ auto iv = item.referenceVertex();
auto startpos = item.translation();
- std::vector<EdgeCache<RawShape>> ecache;
+ std::vector<Edges> ecache;
ecache.reserve(nfps.size());
for(auto& nfp : nfps ) {
@@ -557,79 +883,59 @@ public:
ecache.back().accuracy(config_.accuracy);
}
- struct Optimum {
- double relpos;
- unsigned nfpidx;
- int hidx;
- Optimum(double pos, unsigned nidx):
- relpos(pos), nfpidx(nidx), hidx(-1) {}
- Optimum(double pos, unsigned nidx, int holeidx):
- relpos(pos), nfpidx(nidx), hidx(holeidx) {}
- };
-
- auto getNfpPoint = [&ecache](const Optimum& opt)
- {
- return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) :
- ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
- };
-
- Nfp::Shapes<RawShape> pile;
+ Shapes pile;
pile.reserve(items_.size()+1);
- double pile_area = 0;
+ // double pile_area = 0;
for(Item& mitem : items_) {
pile.emplace_back(mitem.transformedShape());
- pile_area += mitem.area();
+ // pile_area += mitem.area();
}
- auto merged_pile = Nfp::merge(pile);
+ auto merged_pile = nfp::merge(pile);
+ auto& bin = bin_;
+ double norm = norm_;
+ auto pbb = sl::boundingBox(merged_pile);
+ auto binbb = sl::boundingBox(bin);
// This is the kernel part of the object function that is
// customizable by the library client
auto _objfunc = config_.object_function?
config_.object_function :
- [this, &merged_pile](
- Nfp::Shapes<RawShape>& /*pile*/,
- const Item& item,
- double occupied_area,
- double norm,
- double /*penality*/)
+ [norm, bin, binbb, pbb](const Item& item)
{
- merged_pile.emplace_back(item.transformedShape());
- auto ch = sl::convexHull(merged_pile);
- merged_pile.pop_back();
+ auto ibb = item.boundingBox();
+ auto fullbb = boundingBox(pbb, ibb);
- // The pack ratio -- how much is the convex hull occupied
- double pack_rate = occupied_area/sl::area(ch);
+ double score = pl::distance(ibb.center(), binbb.center());
+ score /= norm;
- // ratio of waste
- double waste = 1.0 - pack_rate;
-
- // Score is the square root of waste. This will extend the
- // range of good (lower) values and shring the range of bad
- // (larger) values.
- auto score = std::sqrt(waste);
-
- if(!wouldFit(ch, bin_)) score += norm;
+ double miss = overfit(fullbb, bin);
+ miss = miss > 0? miss : 0;
+ score += std::pow(miss, 2);
return score;
};
// Our object function for placement
- auto rawobjfunc = [&] (Vertex v)
+ auto rawobjfunc =
+ [_objfunc, iv, startpos] (Vertex v, Item& itm)
{
auto d = v - iv;
d += startpos;
- item.translation(d);
-
- double occupied_area = pile_area + item.area();
-
- double score = _objfunc(pile, item, occupied_area,
- norm_, penality_);
+ itm.translation(d);
+ return _objfunc(itm);
+ };
- return score;
+ auto getNfpPoint = [&ecache](const Optimum& opt)
+ {
+ return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) :
+ ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
};
- auto boundaryCheck = [&](const Optimum& o) {
+ auto boundaryCheck =
+ [&merged_pile, &getNfpPoint, &item, &bin, &iv, &startpos]
+ (const Optimum& o)
+ {
auto v = getNfpPoint(o);
auto d = v - iv;
d += startpos;
@@ -639,84 +945,123 @@ public:
auto chull = sl::convexHull(merged_pile);
merged_pile.pop_back();
- return wouldFit(chull, bin_);
+ return overfit(chull, bin);
};
- opt::StopCriteria stopcr;
- stopcr.max_iterations = 100;
- stopcr.relative_score_difference = 1e-6;
- opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
-
Optimum optimum(0, 0);
- double best_score = penality_;
+ double best_score = std::numeric_limits<double>::max();
+ std::launch policy = std::launch::deferred;
+ if(config_.parallel) policy |= std::launch::async;
+
+ if(config_.before_packing)
+ config_.before_packing(merged_pile, items_, remlist);
+
+ using OptResult = opt::Result<double>;
+ using OptResults = std::vector<OptResult>;
// Local optimization with the four polygon corners as
// starting points
for(unsigned ch = 0; ch < ecache.size(); ch++) {
auto& cache = ecache[ch];
- auto contour_ofn = [&rawobjfunc, &getNfpPoint, ch]
- (double relpos)
- {
- return rawobjfunc(getNfpPoint(Optimum(relpos, ch)));
- };
+ OptResults results(cache.corners().size());
+
+ auto& rofn = rawobjfunc;
+ auto& nfpoint = getNfpPoint;
- std::for_each(cache.corners().begin(),
- cache.corners().end(),
- [ch, &contour_ofn, &solver, &best_score,
- &optimum, &boundaryCheck] (double pos)
+ __parallel::enumerate(
+ cache.corners().begin(),
+ cache.corners().end(),
+ [&results, &item, &rofn, &nfpoint, ch]
+ (double pos, size_t n)
{
+ Optimizer solver;
+
+ Item itemcpy = item;
+ auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy]
+ (double relpos)
+ {
+ Optimum op(relpos, ch);
+ return rofn(nfpoint(op), itemcpy);
+ };
+
try {
- auto result = solver.optimize_min(contour_ofn,
+ results[n] = solver.optimize_min(contour_ofn,
opt::initvals<double>(pos),
opt::bound<double>(0, 1.0)
);
-
- if(result.score < best_score) {
- Optimum o(std::get<0>(result.optimum), ch, -1);
- if(boundaryCheck(o)) {
- best_score = result.score;
- optimum = o;
- }
- }
} catch(std::exception& e) {
derr() << "ERROR: " << e.what() << "\n";
}
- });
+ }, policy);
+
+ auto resultcomp =
+ []( const OptResult& r1, const OptResult& r2 ) {
+ return r1.score < r2.score;
+ };
+
+ auto mr = *std::min_element(results.begin(), results.end(),
+ resultcomp);
+
+ if(mr.score < best_score) {
+ Optimum o(std::get<0>(mr.optimum), ch, -1);
+ double miss = boundaryCheck(o);
+ if(miss <= 0) {
+ best_score = mr.score;
+ optimum = o;
+ } else {
+ best_overfit = std::min(miss, best_overfit);
+ }
+ }
for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) {
- auto hole_ofn =
- [&rawobjfunc, &getNfpPoint, ch, hidx]
- (double pos)
- {
- Optimum opt(pos, ch, hidx);
- return rawobjfunc(getNfpPoint(opt));
- };
+ results.clear();
+ results.resize(cache.corners(hidx).size());
- std::for_each(cache.corners(hidx).begin(),
+ // TODO : use parallel for
+ __parallel::enumerate(cache.corners(hidx).begin(),
cache.corners(hidx).end(),
- [&hole_ofn, &solver, &best_score,
- &optimum, ch, hidx, &boundaryCheck]
- (double pos)
+ [&results, &item, &nfpoint,
+ &rofn, ch, hidx]
+ (double pos, size_t n)
{
+ Optimizer solver;
+
+ Item itmcpy = item;
+ auto hole_ofn =
+ [&rofn, &nfpoint, ch, hidx, &itmcpy]
+ (double pos)
+ {
+ Optimum opt(pos, ch, hidx);
+ return rofn(nfpoint(opt), itmcpy);
+ };
+
try {
- auto result = solver.optimize_min(hole_ofn,
+ results[n] = solver.optimize_min(hole_ofn,
opt::initvals<double>(pos),
opt::bound<double>(0, 1.0)
);
- if(result.score < best_score) {
- Optimum o(std::get<0>(result.optimum),
- ch, hidx);
- if(boundaryCheck(o)) {
- best_score = result.score;
- optimum = o;
- }
- }
} catch(std::exception& e) {
derr() << "ERROR: " << e.what() << "\n";
}
- });
+ }, policy);
+
+ auto hmr = *std::min_element(results.begin(),
+ results.end(),
+ resultcomp);
+
+ if(hmr.score < best_score) {
+ Optimum o(std::get<0>(hmr.optimum),
+ ch, hidx);
+ double miss = boundaryCheck(o);
+ if(miss <= 0.0) {
+ best_score = hmr.score;
+ optimum = o;
+ } else {
+ best_overfit = std::min(miss, best_overfit);
+ }
+ }
}
}
@@ -736,24 +1081,39 @@ public:
if(can_pack) {
ret = PackResult(item);
+ item_keys_.emplace_back(itemhash);
+ } else {
+ ret = PackResult(best_overfit);
}
return ret;
}
- ~_NofitPolyPlacer() {
- clearItems();
+ inline void finalAlign(const RawShape& pbin) {
+ auto bbin = sl::boundingBox(pbin);
+ finalAlign(bbin);
}
- inline void clearItems() {
- Nfp::Shapes<RawShape> m;
+ inline void finalAlign(_Circle<TPoint<RawShape>> cbin) {
+ if(items_.empty()) return;
+ nfp::Shapes<RawShape> m;
m.reserve(items_.size());
+ for(Item& item : items_) m.emplace_back(item.transformedShape());
+ auto c = boundingCircle(sl::convexHull(m));
+
+ auto d = cbin.center() - c.center();
+ for(Item& item : items_) item.translate(d);
+ }
+
+ inline void finalAlign(Box bbin) {
+ if(items_.empty()) return;
+ nfp::Shapes<RawShape> m;
+ m.reserve(items_.size());
for(Item& item : items_) m.emplace_back(item.transformedShape());
- auto&& bb = sl::boundingBox<RawShape>(m);
+ auto&& bb = sl::boundingBox(m);
Vertex ci, cb;
- auto bbin = sl::boundingBox<RawShape>(bin_);
switch(config_.alignment) {
case Config::Alignment::CENTER: {
@@ -785,16 +1145,12 @@ public:
auto d = cb - ci;
for(Item& item : items_) item.translate(d);
-
- Base::clearItems();
}
-private:
-
void setInitialPosition(Item& item) {
Box&& bb = item.boundingBox();
Vertex ci, cb;
- auto bbin = sl::boundingBox<RawShape>(bin_);
+ auto bbin = sl::boundingBox(bin_);
switch(config_.starting_point) {
case Config::Alignment::CENTER: {
@@ -830,7 +1186,7 @@ private:
void placeOutsideOfBin(Item& item) {
auto&& bb = item.boundingBox();
- Box binbb = sl::boundingBox<RawShape>(bin_);
+ Box binbb = sl::boundingBox(bin_);
Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp
index 9d2cb626b..0df1b8c91 100644
--- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp
+++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp
@@ -3,14 +3,11 @@
#include "../libnest2d.hpp"
-namespace libnest2d { namespace strategies {
+namespace libnest2d { namespace placers {
struct EmptyConfig {};
-template<class Subclass, class RawShape, class TBin,
- class Cfg = EmptyConfig,
- class Store = std::vector<std::reference_wrapper<_Item<RawShape>>>
- >
+template<class Subclass, class RawShape, class TBin, class Cfg = EmptyConfig>
class PlacerBoilerplate {
mutable bool farea_valid_ = false;
mutable double farea_ = 0.0;
@@ -22,25 +19,30 @@ public:
using Coord = TCoord<Vertex>;
using Unit = Coord;
using Config = Cfg;
- using Container = Store;
+ using ItemGroup = _ItemGroup<Item>;
+ using DefaultIter = typename ItemGroup::const_iterator;
class PackResult {
Item *item_ptr_;
Vertex move_;
Radians rot_;
+ double overfit_;
friend class PlacerBoilerplate;
friend Subclass;
+
PackResult(Item& item):
item_ptr_(&item),
move_(item.translation()),
rot_(item.rotation()) {}
- PackResult(): item_ptr_(nullptr) {}
+
+ PackResult(double overfit = 1.0):
+ item_ptr_(nullptr), overfit_(overfit) {}
+
public:
operator bool() { return item_ptr_ != nullptr; }
+ double overfit() const { return overfit_; }
};
- using ItemGroup = const Container&;
-
inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin)
{
items_.reserve(cap);
@@ -56,8 +58,10 @@ public:
config_ = config;
}
- bool pack(Item& item) {
- auto&& r = static_cast<Subclass*>(this)->trypack(item);
+ template<class Range = ConstItemRange<DefaultIter>>
+ bool pack(Item& item,
+ const Range& rem = Range()) {
+ auto&& r = static_cast<Subclass*>(this)->trypack(item, rem);
if(r) {
items_.push_back(*(r.item_ptr_));
farea_valid_ = false;
@@ -79,14 +83,11 @@ public:
farea_valid_ = false;
}
- inline ItemGroup getItems() const { return items_; }
+ inline const ItemGroup& getItems() const { return items_; }
inline void clearItems() {
items_.clear();
farea_valid_ = false;
-#ifndef NDEBUG
- debug_items_.clear();
-#endif
}
inline double filledArea() const {
@@ -103,14 +104,10 @@ public:
return farea_;
}
-#ifndef NDEBUG
- std::vector<Item> debug_items_;
-#endif
-
protected:
BinType bin_;
- Container items_;
+ ItemGroup items_;
Cfg config_;
};
@@ -121,6 +118,7 @@ using Base::items_; \
using Base::config_; \
public: \
using typename Base::Item; \
+using typename Base::ItemGroup; \
using typename Base::BinType; \
using typename Base::Config; \
using typename Base::Vertex; \
@@ -128,7 +126,6 @@ using typename Base::Segment; \
using typename Base::PackResult; \
using typename Base::Coord; \
using typename Base::Unit; \
-using typename Base::Container; \
private:
}
diff --git a/xs/src/libnest2d/libnest2d/rotfinder.hpp b/xs/src/libnest2d/libnest2d/rotfinder.hpp
new file mode 100644
index 000000000..525fd8759
--- /dev/null
+++ b/xs/src/libnest2d/libnest2d/rotfinder.hpp
@@ -0,0 +1,41 @@
+#ifndef ROTFINDER_HPP
+#define ROTFINDER_HPP
+
+#include <libnest2d/libnest2d.hpp>
+#include <libnest2d/optimizer.hpp>
+#include <iterator>
+
+namespace libnest2d {
+
+template<class RawShape>
+Radians findBestRotation(_Item<RawShape>& item) {
+ opt::StopCriteria stopcr;
+ stopcr.absolute_score_difference = 0.01;
+ stopcr.max_iterations = 10000;
+ opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr);
+
+ auto orig_rot = item.rotation();
+
+ auto result = solver.optimize_min([&item, &orig_rot](Radians rot){
+ item.rotation(orig_rot + rot);
+ auto bb = item.boundingBox();
+ return std::sqrt(bb.height()*bb.width());
+ }, opt::initvals(Radians(0)), opt::bound<Radians>(-Pi/2, Pi/2));
+
+ item.rotation(orig_rot);
+
+ return std::get<0>(result.optimum);
+}
+
+template<class Iterator>
+void findMinimumBoundingBoxRotations(Iterator from, Iterator to) {
+ using V = typename std::iterator_traits<Iterator>::value_type;
+ std::for_each(from, to, [](V& item){
+ Radians rot = findBestRotation(item);
+ item.rotate(rot);
+ });
+}
+
+}
+
+#endif // ROTFINDER_HPP
diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp
index e3ad97c10..ee93d0592 100644
--- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp
+++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp
@@ -8,7 +8,7 @@
#include "selection_boilerplate.hpp"
-namespace libnest2d { namespace strategies {
+namespace libnest2d { namespace selections {
/**
* Selection heuristic based on [López-Camacho]\
@@ -118,7 +118,7 @@ public:
using Placer = PlacementStrategyLike<TPlacer>;
using ItemList = std::list<ItemRef>;
- const double bin_area = ShapeLike::area<RawShape>(bin);
+ const double bin_area = sl::area(bin);
const double w = bin_area * config_.waste_increment;
const double INITIAL_FILL_PROPORTION = config_.initial_fill_proportion;
@@ -227,10 +227,14 @@ public:
bool ret = false;
auto it = not_packed.begin();
+ auto pack = [&placer, &not_packed](ItemListIt it) {
+ return placer.pack(*it, rem(it, not_packed));
+ };
+
while(it != not_packed.end() && !ret &&
free_area - (item_area = it->get().area()) <= waste)
{
- if(item_area <= free_area && placer.pack(*it) ) {
+ if(item_area <= free_area && pack(it) ) {
free_area -= item_area;
filled_area = bin_area - free_area;
ret = true;
@@ -270,6 +274,11 @@ public:
auto it2 = it;
std::vector<TPair> wrong_pairs;
+ using std::placeholders::_1;
+
+ auto trypack = [&placer, &not_packed](ItemListIt it) {
+ return placer.trypack(*it, rem(it, not_packed));
+ };
while(it != endit && !ret &&
free_area - (item_area = it->get().area()) -
@@ -278,7 +287,7 @@ public:
if(item_area + smallestPiece(it, not_packed)->get().area() >
free_area ) { it++; continue; }
- auto pr = placer.trypack(*it);
+ auto pr = trypack(it);
// First would fit
it2 = not_packed.begin();
@@ -294,14 +303,14 @@ public:
}
placer.accept(pr);
- auto pr2 = placer.trypack(*it2);
+ auto pr2 = trypack(it2);
if(!pr2) {
placer.unpackLast(); // remove first
if(try_reverse) {
- pr2 = placer.trypack(*it2);
+ pr2 = trypack(it2);
if(pr2) {
placer.accept(pr2);
- auto pr12 = placer.trypack(*it);
+ auto pr12 = trypack(it);
if(pr12) {
placer.accept(pr12);
ret = true;
@@ -365,6 +374,14 @@ public:
return it->get().area();
};
+ auto trypack = [&placer, &not_packed](ItemListIt it) {
+ return placer.trypack(*it, rem(it, not_packed));
+ };
+
+ auto pack = [&placer, &not_packed](ItemListIt it) {
+ return placer.pack(*it, rem(it, not_packed));
+ };
+
while (it != endit && !ret) { // drill down 1st level
// We need to determine in each iteration the largest, second
@@ -394,7 +411,7 @@ public:
it++; continue;
}
- auto pr = placer.trypack(*it);
+ auto pr = trypack(it);
// Check for free area and try to pack the 1st item...
if(!pr) { it++; continue; }
@@ -420,15 +437,15 @@ public:
bool can_pack2 = false;
placer.accept(pr);
- auto pr2 = placer.trypack(*it2);
+ auto pr2 = trypack(it2);
auto pr12 = pr;
if(!pr2) {
placer.unpackLast(); // remove first
if(try_reverse) {
- pr2 = placer.trypack(*it2);
+ pr2 = trypack(it2);
if(pr2) {
placer.accept(pr2);
- pr12 = placer.trypack(*it);
+ pr12 = trypack(it);
if(pr12) can_pack2 = true;
placer.unpackLast();
}
@@ -463,7 +480,7 @@ public:
if(a3_sum > free_area) { it3++; continue; }
placer.accept(pr12); placer.accept(pr2);
- bool can_pack3 = placer.pack(*it3);
+ bool can_pack3 = pack(it3);
if(!can_pack3) {
placer.unpackLast();
@@ -473,16 +490,16 @@ public:
if(!can_pack3 && try_reverse) {
std::array<size_t, 3> indices = {0, 1, 2};
- std::array<ItemRef, 3>
- candidates = {*it, *it2, *it3};
+ std::array<typename ItemList::iterator, 3>
+ candidates = {it, it2, it3};
- auto tryPack = [&placer, &candidates](
+ auto tryPack = [&placer, &candidates, &pack](
const decltype(indices)& idx)
{
std::array<bool, 3> packed = {false};
for(auto id : idx) packed.at(id) =
- placer.pack(candidates[id]);
+ pack(candidates[id]);
bool check =
std::all_of(packed.begin(),
@@ -536,7 +553,7 @@ public:
{ auto it = store_.begin();
while (it != store_.end()) {
Placer p(bin); p.configure(pconfig);
- if(!p.pack(*it)) {
+ if(!p.pack(*it, rem(it, store_))) {
it = store_.erase(it);
} else it++;
}
@@ -551,11 +568,7 @@ public:
{
packed_bins_[idx] = placer.getItems();
-#ifndef NDEBUG
- packed_bins_[idx].insert(packed_bins_[idx].end(),
- placer.getDebugItems().begin(),
- placer.getDebugItems().end());
-#endif
+
// TODO here should be a spinlock
slock.lock();
acounter -= packednum;
@@ -601,7 +614,7 @@ public:
while(it != not_packed.end() &&
filled_area < INITIAL_FILL_AREA)
{
- if(placer.pack(*it)) {
+ if(placer.pack(*it, rem(it, not_packed))) {
filled_area += it->get().area();
free_area = bin_area - filled_area;
it = not_packed.erase(it);
diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp
index d0018dc73..0da7220a1 100644
--- a/xs/src/libnest2d/libnest2d/selections/filler.hpp
+++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp
@@ -3,7 +3,7 @@
#include "selection_boilerplate.hpp"
-namespace libnest2d { namespace strategies {
+namespace libnest2d { namespace selections {
template<class RawShape>
class _FillerSelection: public SelectionBoilerplate<RawShape> {
@@ -56,18 +56,13 @@ public:
std::sort(store_.begin(), store_.end(), sortfunc);
-// Container a = {store_[0], store_[1], store_[4], store_[5] };
-//// a.insert(a.end(), store_.end()-10, store_.end());
-// store_ = a;
-
PlacementStrategyLike<TPlacer> placer(bin);
placer.configure(pconfig);
auto it = store_.begin();
while(it != store_.end()) {
- if(!placer.pack(*it)) {
+ if(!placer.pack(*it, {std::next(it), store_.end()})) {
if(packed_bins_.back().empty()) ++it;
-// makeProgress(placer);
placer.clearItems();
packed_bins_.emplace_back();
} else {
@@ -76,9 +71,6 @@ public:
}
}
-// if(was_packed) {
-// packed_bins_.push_back(placer.getItems());
-// }
}
};
diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp
index 665b9da9f..bca7497db 100644
--- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp
+++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp
@@ -4,7 +4,7 @@
#include "../libnest2d.hpp"
#include "selection_boilerplate.hpp"
-namespace libnest2d { namespace strategies {
+namespace libnest2d { namespace selections {
template<class RawShape>
class _FirstFitSelection: public SelectionBoilerplate<RawShape> {
@@ -40,6 +40,7 @@ public:
packed_bins_.clear();
std::vector<Placer> placers;
+ placers.reserve(last-first);
std::copy(first, last, std::back_inserter(store_));
@@ -66,21 +67,25 @@ public:
}
}
- for(auto& item : store_ ) {
+ auto it = store_.begin();
+
+ while(it != store_.end()) {
bool was_packed = false;
+ size_t j = 0;
while(!was_packed) {
-
- for(size_t j = 0; j < placers.size() && !was_packed; j++) {
- if((was_packed = placers[j].pack(item)))
- makeProgress(placers[j], j);
+ for(; j < placers.size() && !was_packed; j++) {
+ if((was_packed = placers[j].pack(*it, rem(it, store_) )))
+ makeProgress(placers[j], j);
}
if(!was_packed) {
placers.emplace_back(bin);
placers.back().configure(pconfig);
packed_bins_.emplace_back();
+ j = placers.size() - 1;
}
}
+ ++it;
}
}
diff --git a/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp b/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp
index 59ef5cb23..05bbae658 100644
--- a/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp
+++ b/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp
@@ -3,8 +3,7 @@
#include "../libnest2d.hpp"
-namespace libnest2d {
-namespace strategies {
+namespace libnest2d { namespace selections {
template<class RawShape>
class SelectionBoilerplate {
diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp
index 39315ff1a..323fb8d31 100644
--- a/xs/src/libnest2d/tests/test.cpp
+++ b/xs/src/libnest2d/tests/test.cpp
@@ -5,6 +5,7 @@
#include "printer_parts.h"
#include <libnest2d/geometry_traits_nfp.hpp>
//#include "../tools/libnfpglue.hpp"
+//#include "../tools/nfp_svgnest_glue.hpp"
std::vector<libnest2d::Item>& prusaParts() {
static std::vector<libnest2d::Item> ret;
@@ -99,6 +100,43 @@ TEST(BasicFunctionality, creationAndDestruction)
}
+TEST(GeometryAlgorithms, boundingCircle) {
+ using namespace libnest2d;
+ using placers::boundingCircle;
+
+ PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}};
+ Circle c = boundingCircle(p);
+
+ ASSERT_EQ(c.center().X, 0);
+ ASSERT_EQ(c.center().Y, 0);
+ ASSERT_DOUBLE_EQ(c.radius(), 10);
+
+ shapelike::translate(p, PointImpl{10, 10});
+ c = boundingCircle(p);
+
+ ASSERT_EQ(c.center().X, 10);
+ ASSERT_EQ(c.center().Y, 10);
+ ASSERT_DOUBLE_EQ(c.radius(), 10);
+
+ auto parts = prusaParts();
+
+ int i = 0;
+ for(auto& part : parts) {
+ c = boundingCircle(part.transformedShape());
+ if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl;
+
+ else for(auto v : shapelike::getContour(part.transformedShape()) ) {
+ auto d = pointlike::distance(v, c.center());
+ if(d > c.radius() ) {
+ auto e = std::abs( 1.0 - d/c.radius());
+ ASSERT_LE(e, 1e-3);
+ }
+ }
+ i++;
+ }
+
+}
+
TEST(GeometryAlgorithms, Distance) {
using namespace libnest2d;
@@ -107,14 +145,14 @@ TEST(GeometryAlgorithms, Distance) {
Point p2 = {10, 0};
Point p3 = {10, 10};
- ASSERT_DOUBLE_EQ(PointLike::distance(p1, p2), 10);
- ASSERT_DOUBLE_EQ(PointLike::distance(p1, p3), sqrt(200));
+ ASSERT_DOUBLE_EQ(pointlike::distance(p1, p2), 10);
+ ASSERT_DOUBLE_EQ(pointlike::distance(p1, p3), sqrt(200));
Segment seg(p1, p3);
- ASSERT_DOUBLE_EQ(PointLike::distance(p2, seg), 7.0710678118654755);
+ ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
- auto result = PointLike::horizontalDistance(p2, seg);
+ auto result = pointlike::horizontalDistance(p2, seg);
auto check = [](Coord val, Coord expected) {
if(std::is_floating_point<Coord>::value)
@@ -127,11 +165,11 @@ TEST(GeometryAlgorithms, Distance) {
ASSERT_TRUE(result.second);
check(result.first, 10);
- result = PointLike::verticalDistance(p2, seg);
+ result = pointlike::verticalDistance(p2, seg);
ASSERT_TRUE(result.second);
check(result.first, -10);
- result = PointLike::verticalDistance(Point{10, 20}, seg);
+ result = pointlike::verticalDistance(Point{10, 20}, seg);
ASSERT_TRUE(result.second);
check(result.first, 10);
@@ -139,12 +177,12 @@ TEST(GeometryAlgorithms, Distance) {
Point p4 = {80, 0};
Segment seg2 = { {0, 0}, {0, 40} };
- result = PointLike::horizontalDistance(p4, seg2);
+ result = pointlike::horizontalDistance(p4, seg2);
ASSERT_TRUE(result.second);
check(result.first, 80);
- result = PointLike::verticalDistance(p4, seg2);
+ result = pointlike::verticalDistance(p4, seg2);
// Point should not be related to the segment
ASSERT_FALSE(result.second);
@@ -172,7 +210,7 @@ TEST(GeometryAlgorithms, Area) {
{61, 97}
};
- ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 );
+ ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 );
}
TEST(GeometryAlgorithms, IsPointInsidePolygon) {
@@ -182,21 +220,21 @@ TEST(GeometryAlgorithms, IsPointInsidePolygon) {
Point p = {1, 1};
- ASSERT_TRUE(rect.isPointInside(p));
+ ASSERT_TRUE(rect.isInside(p));
p = {11, 11};
- ASSERT_FALSE(rect.isPointInside(p));
+ ASSERT_FALSE(rect.isInside(p));
p = {11, 12};
- ASSERT_FALSE(rect.isPointInside(p));
+ ASSERT_FALSE(rect.isInside(p));
p = {3, 3};
- ASSERT_TRUE(rect.isPointInside(p));
+ ASSERT_TRUE(rect.isInside(p));
}
@@ -250,7 +288,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon)
Item leftp(placer.leftPoly(item));
- ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first);
+ ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first);
ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount());
for(unsigned long i = 0; i < leftControl.vertexCount(); i++) {
@@ -260,7 +298,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon)
Item downp(placer.downPoly(item));
- ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first);
+ ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first);
ASSERT_EQ(downp.vertexCount(), downControl.vertexCount());
for(unsigned long i = 0; i < downControl.vertexCount(); i++) {
@@ -297,7 +335,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight)
{20, 20} };
- Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
+ Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
auto groups = arrange(rects.begin(), rects.end());
@@ -350,7 +388,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
Coord min_obj_distance = 5;
- Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
+ Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
min_obj_distance);
auto groups = arrange(rects.begin(), rects.end());
@@ -401,7 +439,7 @@ R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
setX(v, getX(v)/SCALE);
rbin.setVertex(i, v);
}
- out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
+ out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
for(Item& sh : r) {
Item tsh(sh.transformedShape());
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
@@ -410,7 +448,7 @@ R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
setX(v, getX(v)/SCALE);
tsh.setVertex(i, v);
}
- out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
+ out << shapelike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
}
out << "\n</svg>" << std::endl;
}
@@ -664,7 +702,7 @@ std::vector<ItemPair> nfp_concave_testdata = {
}
};
-template<NfpLevel lvl, Coord SCALE>
+template<nfp::NfpLevel lvl, Coord SCALE>
void testNfp(const std::vector<ItemPair>& testdata) {
using namespace libnest2d;
@@ -674,29 +712,33 @@ void testNfp(const std::vector<ItemPair>& testdata) {
auto& exportfun = exportSVG<SCALE, Box>;
- auto onetest = [&](Item& orbiter, Item& stationary){
+ auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){
testcase++;
orbiter.translate({210*SCALE, 0});
- auto&& nfp = Nfp::noFitPolygon<lvl>(stationary.rawShape(),
+ auto&& nfp = nfp::noFitPolygon<lvl>(stationary.rawShape(),
orbiter.transformedShape());
- strategies::correctNfpPosition(nfp, stationary, orbiter);
+ placers::correctNfpPosition(nfp, stationary, orbiter);
- auto v = ShapeLike::isValid(nfp.first);
+ auto valid = shapelike::isValid(nfp.first);
- if(!v.first) {
- std::cout << v.second << std::endl;
- }
+ /*Item infp(nfp.first);
+ if(!valid.first) {
+ std::cout << "test instance: " << testidx << " "
+ << valid.second << std::endl;
+ std::vector<std::reference_wrapper<Item>> inp = {std::ref(infp)};
+ exportfun(inp, bin, testidx);
+ }*/
- ASSERT_TRUE(v.first);
+ ASSERT_TRUE(valid.first);
Item infp(nfp.first);
int i = 0;
auto rorbiter = orbiter.transformedShape();
- auto vo = Nfp::referenceVertex(rorbiter);
+ auto vo = nfp::referenceVertex(rorbiter);
ASSERT_TRUE(stationary.isInside(infp));
@@ -710,7 +752,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
bool touching = Item::touches(tmp, stationary);
- if(!touching) {
+ if(!touching || !valid.first) {
std::vector<std::reference_wrapper<Item>> inp = {
std::ref(stationary), std::ref(tmp), std::ref(infp)
};
@@ -722,22 +764,24 @@ void testNfp(const std::vector<ItemPair>& testdata) {
}
};
+ unsigned tidx = 0;
for(auto& td : testdata) {
auto orbiter = td.orbiter;
auto stationary = td.stationary;
- onetest(orbiter, stationary);
+ onetest(orbiter, stationary, tidx++);
}
+ tidx = 0;
for(auto& td : testdata) {
auto orbiter = td.stationary;
auto stationary = td.orbiter;
- onetest(orbiter, stationary);
+ onetest(orbiter, stationary, tidx++);
}
}
}
TEST(GeometryAlgorithms, nfpConvexConvex) {
- testNfp<NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
+ testNfp<nfp::NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
}
//TEST(GeometryAlgorithms, nfpConcaveConcave) {
@@ -758,7 +802,7 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) {
Rectangle input(10, 10);
- strategies::EdgeCache<PolygonImpl> ecache(input);
+ placers::EdgeCache<PolygonImpl> ecache(input);
auto first = *input.begin();
ASSERT_TRUE(getX(first) == getX(ecache.coords(0)));
@@ -770,7 +814,7 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) {
for(int i = 0; i <= 100; i++) {
auto v = ecache.coords(i*(0.01));
- ASSERT_TRUE(ShapeLike::touches(v, input.transformedShape()));
+ ASSERT_TRUE(shapelike::touches(v, input.transformedShape()));
}
}
@@ -784,17 +828,17 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
rect2.translate({10, 0});
rect3.translate({25, 0});
- ShapeLike::Shapes<PolygonImpl> pile;
+ shapelike::Shapes<PolygonImpl> pile;
pile.push_back(rect1.transformedShape());
pile.push_back(rect2.transformedShape());
- auto result = Nfp::merge(pile, rect3.transformedShape());
+ auto result = nfp::merge(pile, rect3.transformedShape());
ASSERT_EQ(result.size(), 1);
Rectangle ref(45, 15);
- ASSERT_EQ(ShapeLike::area(result.front()), ref.area());
+ ASSERT_EQ(shapelike::area(result.front()), ref.area());
}
int main(int argc, char **argv) {
diff --git a/xs/src/libnest2d/tools/libnfpglue.cpp b/xs/src/libnest2d/tools/libnfpglue.cpp
index 18656fd40..31733acf9 100644
--- a/xs/src/libnest2d/tools/libnfpglue.cpp
+++ b/xs/src/libnest2d/tools/libnfpglue.cpp
@@ -56,7 +56,7 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) {
NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
{
- using Vertex = PointImpl;
+ namespace sl = shapelike;
NfpR ret;
@@ -85,7 +85,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
// this can throw
auto nfp = libnfporb::generateNFP(pstat, porb, true);
- auto &ct = ShapeLike::getContour(ret.first);
+ auto &ct = sl::getContour(ret.first);
ct.reserve(nfp.front().size()+1);
for(auto v : nfp.front()) {
v = scale(v, refactor);
@@ -94,7 +94,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
ct.push_back(ct.front());
std::reverse(ct.begin(), ct.end());
- auto &rholes = ShapeLike::holes(ret.first);
+ auto &rholes = sl::holes(ret.first);
for(size_t hidx = 1; hidx < nfp.size(); ++hidx) {
if(nfp[hidx].size() >= 3) {
rholes.emplace_back();
@@ -110,31 +110,31 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
}
}
- ret.second = Nfp::referenceVertex(ret.first);
+ ret.second = nfp::referenceVertex(ret.first);
} catch(std::exception& e) {
std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
// auto ch_stat = ShapeLike::convexHull(sh);
// auto ch_orb = ShapeLike::convexHull(cother);
- ret = Nfp::nfpConvexOnly(sh, cother);
+ ret = nfp::nfpConvexOnly(sh, cother);
}
return ret;
}
-NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
+NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);//nfpConvexOnly(sh, cother);
}
-NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
+NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);
}
-NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
+NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);
diff --git a/xs/src/libnest2d/tools/libnfpglue.hpp b/xs/src/libnest2d/tools/libnfpglue.hpp
index 75f639445..1ff033cb9 100644
--- a/xs/src/libnest2d/tools/libnfpglue.hpp
+++ b/xs/src/libnest2d/tools/libnfpglue.hpp
@@ -5,22 +5,22 @@
namespace libnest2d {
-using NfpR = Nfp::NfpResult<PolygonImpl>;
+using NfpR = nfp::NfpResult<PolygonImpl>;
NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother);
template<>
-struct Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
+struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
template<>
-struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
+struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
template<>
-struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
+struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
@@ -34,7 +34,7 @@ struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
//};
-template<> struct Nfp::MaxNfpLevel<PolygonImpl> {
+template<> struct nfp::MaxNfpLevel<PolygonImpl> {
static const BP2D_CONSTEXPR NfpLevel value =
// NfpLevel::CONVEX_ONLY;
NfpLevel::BOTH_CONCAVE;
diff --git a/xs/src/libnest2d/tools/nfp_svgnest.hpp b/xs/src/libnest2d/tools/nfp_svgnest.hpp
new file mode 100644
index 000000000..ac5700c10
--- /dev/null
+++ b/xs/src/libnest2d/tools/nfp_svgnest.hpp
@@ -0,0 +1,1018 @@
+#ifndef NFP_SVGNEST_HPP
+#define NFP_SVGNEST_HPP
+
+#include <limits>
+#include <unordered_map>
+
+#include <libnest2d/geometry_traits_nfp.hpp>
+
+namespace libnest2d {
+
+namespace __svgnest {
+
+using std::sqrt;
+using std::min;
+using std::max;
+using std::abs;
+using std::isnan;
+
+//template<class Coord> struct _Scale {
+// static const BP2D_CONSTEXPR long long Value = 1000000;
+//};
+
+template<class S> struct _alg {
+ using Contour = TContour<S>;
+ using Point = TPoint<S>;
+ using iCoord = TCoord<Point>;
+ using Coord = double;
+ using Shapes = nfp::Shapes<S>;
+
+ static const Coord TOL;
+
+#define dNAN std::nan("")
+
+ struct Vector {
+ Coord x = 0.0, y = 0.0;
+ bool marked = false;
+ Vector() = default;
+ Vector(Coord X, Coord Y): x(X), y(Y) {}
+ Vector(const Point& p): x(Coord(getX(p))), y(Coord(getY(p))) {}
+ operator Point() const { return {iCoord(x), iCoord(y)}; }
+ Vector& operator=(const Point& p) {
+ x = getX(p), y = getY(p); return *this;
+ }
+ bool operator!=(const Vector& v) const {
+ return v.x != x || v.y != y;
+ }
+ Vector(std::initializer_list<Coord> il):
+ x(*il.begin()), y(*std::next(il.begin())) {}
+ };
+
+ static inline Coord x(const Point& p) { return Coord(getX(p)); }
+ static inline Coord y(const Point& p) { return Coord(getY(p)); }
+
+ static inline Coord x(const Vector& p) { return p.x; }
+ static inline Coord y(const Vector& p) { return p.y; }
+
+ class Cntr {
+ std::vector<Vector> v_;
+ public:
+ Cntr(const Contour& c) {
+ v_.reserve(c.size());
+ std::transform(c.begin(), c.end(), std::back_inserter(v_),
+ [](const Point& p) {
+ return Vector(double(x(p)) / 1e6, double(y(p)) / 1e6);
+ });
+ std::reverse(v_.begin(), v_.end());
+ v_.pop_back();
+ }
+ Cntr() = default;
+
+ Coord offsetx = 0;
+ Coord offsety = 0;
+ size_t size() const { return v_.size(); }
+ bool empty() const { return v_.empty(); }
+ typename std::vector<Vector>::const_iterator cbegin() const { return v_.cbegin(); }
+ typename std::vector<Vector>::const_iterator cend() const { return v_.cend(); }
+ typename std::vector<Vector>::iterator begin() { return v_.begin(); }
+ typename std::vector<Vector>::iterator end() { return v_.end(); }
+ Vector& operator[](size_t idx) { return v_[idx]; }
+ const Vector& operator[](size_t idx) const { return v_[idx]; }
+ template<class...Args>
+ void emplace_back(Args&&...args) {
+ v_.emplace_back(std::forward<Args>(args)...);
+ }
+ template<class...Args>
+ void push(Args&&...args) {
+ v_.emplace_back(std::forward<Args>(args)...);
+ }
+ void clear() { v_.clear(); }
+
+ operator Contour() const {
+ Contour cnt;
+ cnt.reserve(v_.size() + 1);
+ std::transform(v_.begin(), v_.end(), std::back_inserter(cnt),
+ [](const Vector& vertex) {
+ return Point(iCoord(vertex.x) * 1000000, iCoord(vertex.y) * 1000000);
+ });
+ if(!cnt.empty()) cnt.emplace_back(cnt.front());
+ S sh = shapelike::create<S>(cnt);
+
+// std::reverse(cnt.begin(), cnt.end());
+ return shapelike::getContour(sh);
+ }
+ };
+
+ inline static bool _almostEqual(Coord a, Coord b,
+ Coord tolerance = TOL)
+ {
+ return std::abs(a - b) < tolerance;
+ }
+
+ // returns true if p lies on the line segment defined by AB,
+ // but not at any endpoints may need work!
+ static bool _onSegment(const Vector& A, const Vector& B, const Vector& p) {
+
+ // vertical line
+ if(_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x)) {
+ if(!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) &&
+ p.y < max(B.y, A.y) && p.y > min(B.y, A.y)){
+ return true;
+ }
+ else{
+ return false;
+ }
+ }
+
+ // horizontal line
+ if(_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y)){
+ if(!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) &&
+ p.x < max(B.x, A.x) && p.x > min(B.x, A.x)){
+ return true;
+ }
+ else{
+ return false;
+ }
+ }
+
+ //range check
+ if((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) ||
+ (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y))
+ return false;
+
+ // exclude end points
+ if((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) ||
+ (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y)))
+ return false;
+
+
+ double cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y);
+
+ if(abs(cross) > TOL) return false;
+
+ double dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y)*(B.y - A.y);
+
+ if(dot < 0 || _almostEqual(dot, 0)) return false;
+
+ double len2 = (B.x - A.x)*(B.x - A.x) + (B.y - A.y)*(B.y - A.y);
+
+ if(dot > len2 || _almostEqual(dot, len2)) return false;
+
+ return true;
+ }
+
+ // return true if point is in the polygon, false if outside, and null if exactly on a point or edge
+ static int pointInPolygon(const Vector& point, const Cntr& polygon) {
+ if(polygon.size() < 3){
+ return 0;
+ }
+
+ bool inside = false;
+ Coord offsetx = polygon.offsetx;
+ Coord offsety = polygon.offsety;
+
+ for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j=i++) {
+ auto xi = polygon[i].x + offsetx;
+ auto yi = polygon[i].y + offsety;
+ auto xj = polygon[j].x + offsetx;
+ auto yj = polygon[j].y + offsety;
+
+ if(_almostEqual(xi, point.x) && _almostEqual(yi, point.y)){
+ return 0; // no result
+ }
+
+ if(_onSegment({xi, yi}, {xj, yj}, point)){
+ return 0; // exactly on the segment
+ }
+
+ if(_almostEqual(xi, xj) && _almostEqual(yi, yj)){ // ignore very small lines
+ continue;
+ }
+
+ bool intersect = ((yi > point.y) != (yj > point.y)) &&
+ (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
+ if (intersect) inside = !inside;
+ }
+
+ return inside? 1 : -1;
+ }
+
+ static bool intersect(const Cntr& A, const Cntr& B){
+ Contour a = A, b = B;
+ return shapelike::intersects(shapelike::create<S>(a), shapelike::create<S>(b));
+ }
+
+ static Vector _normalizeVector(const Vector& v) {
+ if(_almostEqual(v.x*v.x + v.y*v.y, Coord(1))){
+ return Point(v); // given vector was already a unit vector
+ }
+ auto len = sqrt(v.x*v.x + v.y*v.y);
+ auto inverse = 1/len;
+
+ return { Coord(v.x*inverse), Coord(v.y*inverse) };
+ }
+
+ static double pointDistance( const Vector& p,
+ const Vector& s1,
+ const Vector& s2,
+ Vector normal,
+ bool infinite = false)
+ {
+ normal = _normalizeVector(normal);
+
+ Vector dir = {
+ normal.y,
+ -normal.x
+ };
+
+ auto pdot = p.x*dir.x + p.y*dir.y;
+ auto s1dot = s1.x*dir.x + s1.y*dir.y;
+ auto s2dot = s2.x*dir.x + s2.y*dir.y;
+
+ auto pdotnorm = p.x*normal.x + p.y*normal.y;
+ auto s1dotnorm = s1.x*normal.x + s1.y*normal.y;
+ auto s2dotnorm = s2.x*normal.x + s2.y*normal.y;
+
+ if(!infinite){
+ if (((pdot<s1dot || _almostEqual(pdot, s1dot)) &&
+ (pdot<s2dot || _almostEqual(pdot, s2dot))) ||
+ ((pdot>s1dot || _almostEqual(pdot, s1dot)) &&
+ (pdot>s2dot || _almostEqual(pdot, s2dot))))
+ {
+ // dot doesn't collide with segment,
+ // or lies directly on the vertex
+ return dNAN;
+ }
+ if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) &&
+ (pdotnorm>s1dotnorm && pdotnorm>s2dotnorm))
+ {
+ return min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm);
+ }
+ if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) &&
+ (pdotnorm<s1dotnorm && pdotnorm<s2dotnorm)){
+ return -min(s1dotnorm-pdotnorm, s2dotnorm-pdotnorm);
+ }
+ }
+
+ return -(pdotnorm - s1dotnorm + (s1dotnorm - s2dotnorm)*(s1dot - pdot)
+ / double(s1dot - s2dot));
+ }
+
+ static double segmentDistance( const Vector& A,
+ const Vector& B,
+ const Vector& E,
+ const Vector& F,
+ Vector direction)
+ {
+ Vector normal = {
+ direction.y,
+ -direction.x
+ };
+
+ Vector reverse = {
+ -direction.x,
+ -direction.y
+ };
+
+ auto dotA = A.x*normal.x + A.y*normal.y;
+ auto dotB = B.x*normal.x + B.y*normal.y;
+ auto dotE = E.x*normal.x + E.y*normal.y;
+ auto dotF = F.x*normal.x + F.y*normal.y;
+
+ auto crossA = A.x*direction.x + A.y*direction.y;
+ auto crossB = B.x*direction.x + B.y*direction.y;
+ auto crossE = E.x*direction.x + E.y*direction.y;
+ auto crossF = F.x*direction.x + F.y*direction.y;
+
+// auto crossABmin = min(crossA, crossB);
+// auto crossABmax = max(crossA, crossB);
+
+// auto crossEFmax = max(crossE, crossF);
+// auto crossEFmin = min(crossE, crossF);
+
+ auto ABmin = min(dotA, dotB);
+ auto ABmax = max(dotA, dotB);
+
+ auto EFmax = max(dotE, dotF);
+ auto EFmin = min(dotE, dotF);
+
+ // segments that will merely touch at one point
+ if(_almostEqual(ABmax, EFmin, TOL) || _almostEqual(ABmin, EFmax,TOL)) {
+ return dNAN;
+ }
+ // segments miss eachother completely
+ if(ABmax < EFmin || ABmin > EFmax){
+ return dNAN;
+ }
+
+ double overlap = 0;
+
+ if((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin))
+ {
+ overlap = 1;
+ }
+ else{
+ auto minMax = min(ABmax, EFmax);
+ auto maxMin = max(ABmin, EFmin);
+
+ auto maxMax = max(ABmax, EFmax);
+ auto minMin = min(ABmin, EFmin);
+
+ overlap = (minMax-maxMin)/(maxMax-minMin);
+ }
+
+ auto crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y);
+ auto crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y);
+
+ // lines are colinear
+ if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){
+
+ Vector ABnorm = {B.y-A.y, A.x-B.x};
+ Vector EFnorm = {F.y-E.y, E.x-F.x};
+
+ auto ABnormlength = sqrt(ABnorm.x*ABnorm.x + ABnorm.y*ABnorm.y);
+ ABnorm.x /= ABnormlength;
+ ABnorm.y /= ABnormlength;
+
+ auto EFnormlength = sqrt(EFnorm.x*EFnorm.x + EFnorm.y*EFnorm.y);
+ EFnorm.x /= EFnormlength;
+ EFnorm.y /= EFnormlength;
+
+ // segment normals must point in opposite directions
+ if(abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL &&
+ ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0){
+ // normal of AB segment must point in same direction as
+ // given direction vector
+ auto normdot = ABnorm.y * direction.y + ABnorm.x * direction.x;
+ // the segments merely slide along eachother
+ if(_almostEqual(normdot,0, TOL)){
+ return dNAN;
+ }
+ if(normdot < 0){
+ return 0.0;
+ }
+ }
+ return dNAN;
+ }
+
+ std::vector<double> distances; distances.reserve(10);
+
+ // coincident points
+ if(_almostEqual(dotA, dotE)){
+ distances.emplace_back(crossA-crossE);
+ }
+ else if(_almostEqual(dotA, dotF)){
+ distances.emplace_back(crossA-crossF);
+ }
+ else if(dotA > EFmin && dotA < EFmax){
+ auto d = pointDistance(A,E,F,reverse);
+ if(!isnan(d) && _almostEqual(d, 0))
+ { // A currently touches EF, but AB is moving away from EF
+ auto dB = pointDistance(B,E,F,reverse,true);
+ if(dB < 0 || _almostEqual(dB*overlap,0)){
+ d = dNAN;
+ }
+ }
+ if(!isnan(d)){
+ distances.emplace_back(d);
+ }
+ }
+
+ if(_almostEqual(dotB, dotE)){
+ distances.emplace_back(crossB-crossE);
+ }
+ else if(_almostEqual(dotB, dotF)){
+ distances.emplace_back(crossB-crossF);
+ }
+ else if(dotB > EFmin && dotB < EFmax){
+ auto d = pointDistance(B,E,F,reverse);
+
+ if(!isnan(d) && _almostEqual(d, 0))
+ { // crossA>crossB A currently touches EF, but AB is moving away from EF
+ double dA = pointDistance(A,E,F,reverse,true);
+ if(dA < 0 || _almostEqual(dA*overlap,0)){
+ d = dNAN;
+ }
+ }
+ if(!isnan(d)){
+ distances.emplace_back(d);
+ }
+ }
+
+ if(dotE > ABmin && dotE < ABmax){
+ auto d = pointDistance(E,A,B,direction);
+ if(!isnan(d) && _almostEqual(d, 0))
+ { // crossF<crossE A currently touches EF, but AB is moving away from EF
+ double dF = pointDistance(F,A,B,direction, true);
+ if(dF < 0 || _almostEqual(dF*overlap,0)){
+ d = dNAN;
+ }
+ }
+ if(!isnan(d)){
+ distances.emplace_back(d);
+ }
+ }
+
+ if(dotF > ABmin && dotF < ABmax){
+ auto d = pointDistance(F,A,B,direction);
+ if(!isnan(d) && _almostEqual(d, 0))
+ { // && crossE<crossF A currently touches EF,
+ // but AB is moving away from EF
+ double dE = pointDistance(E,A,B,direction, true);
+ if(dE < 0 || _almostEqual(dE*overlap,0)){
+ d = dNAN;
+ }
+ }
+ if(!isnan(d)){
+ distances.emplace_back(d);
+ }
+ }
+
+ if(distances.empty()){
+ return dNAN;
+ }
+
+ return *std::min_element(distances.begin(), distances.end());
+ }
+
+ static double polygonSlideDistance( const Cntr& AA,
+ const Cntr& BB,
+ Vector direction,
+ bool ignoreNegative)
+ {
+// Vector A1, A2, B1, B2;
+ Cntr A = AA;
+ Cntr B = BB;
+
+ Coord Aoffsetx = A.offsetx;
+ Coord Boffsetx = B.offsetx;
+ Coord Aoffsety = A.offsety;
+ Coord Boffsety = B.offsety;
+
+ // close the loop for polygons
+ if(A[0] != A[A.size()-1]){
+ A.emplace_back(AA[0]);
+ }
+
+ if(B[0] != B[B.size()-1]){
+ B.emplace_back(BB[0]);
+ }
+
+ auto& edgeA = A;
+ auto& edgeB = B;
+
+ double distance = dNAN, d = dNAN;
+
+ Vector dir = _normalizeVector(direction);
+
+// Vector normal = {
+// dir.y,
+// -dir.x
+// };
+
+// Vector reverse = {
+// -dir.x,
+// -dir.y,
+// };
+
+ for(size_t i = 0; i < edgeB.size() - 1; i++){
+ for(size_t j = 0; j < edgeA.size() - 1; j++){
+ Vector A1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety };
+ Vector A2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety};
+ Vector B1 = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety };
+ Vector B2 = {x(edgeB[i+1]) + Boffsetx, y(edgeB[i+1]) + Boffsety};
+
+ if((_almostEqual(A1.x, A2.x) && _almostEqual(A1.y, A2.y)) ||
+ (_almostEqual(B1.x, B2.x) && _almostEqual(B1.y, B2.y))){
+ continue; // ignore extremely small lines
+ }
+
+ d = segmentDistance(A1, A2, B1, B2, dir);
+
+ if(!isnan(d) && (isnan(distance) || d < distance)){
+ if(!ignoreNegative || d > 0 || _almostEqual(d, 0)){
+ distance = d;
+ }
+ }
+ }
+ }
+ return distance;
+ }
+
+ static double polygonProjectionDistance(const Cntr& AA,
+ const Cntr& BB,
+ Vector direction)
+ {
+ Cntr A = AA;
+ Cntr B = BB;
+
+ auto Boffsetx = B.offsetx;
+ auto Boffsety = B.offsety;
+ auto Aoffsetx = A.offsetx;
+ auto Aoffsety = A.offsety;
+
+ // close the loop for polygons
+ if(A[0] != A[A.size()-1]){
+ A.push(A[0]);
+ }
+
+ if(B[0] != B[B.size()-1]){
+ B.push(B[0]);
+ }
+
+ auto& edgeA = A;
+ auto& edgeB = B;
+
+ double distance = dNAN, d;
+// Vector p, s1, s2;
+
+ for(size_t i = 0; i < edgeB.size(); i++) {
+ // the shortest/most negative projection of B onto A
+ double minprojection = dNAN;
+ Vector minp;
+ for(size_t j = 0; j < edgeA.size() - 1; j++){
+ Vector p = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety };
+ Vector s1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety };
+ Vector s2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety };
+
+ if(abs((s2.y-s1.y) * direction.x -
+ (s2.x-s1.x) * direction.y) < TOL) continue;
+
+ // project point, ignore edge boundaries
+ d = pointDistance(p, s1, s2, direction);
+
+ if(!isnan(d) && (isnan(minprojection) || d < minprojection)) {
+ minprojection = d;
+ minp = p;
+ }
+ }
+
+ if(!isnan(minprojection) && (isnan(distance) ||
+ minprojection > distance)){
+ distance = minprojection;
+ }
+ }
+
+ return distance;
+ }
+
+ static std::pair<bool, Vector> searchStartPoint(
+ const Cntr& AA, const Cntr& BB, bool inside, const std::vector<Cntr>& NFP = {})
+ {
+ // clone arrays
+ auto A = AA;
+ auto B = BB;
+
+// // close the loop for polygons
+// if(A[0] != A[A.size()-1]){
+// A.push(A[0]);
+// }
+
+// if(B[0] != B[B.size()-1]){
+// B.push(B[0]);
+// }
+
+ // returns true if point already exists in the given nfp
+ auto inNfp = [](const Vector& p, const std::vector<Cntr>& nfp){
+ if(nfp.empty()){
+ return false;
+ }
+
+ for(size_t i=0; i < nfp.size(); i++){
+ for(size_t j = 0; j< nfp[i].size(); j++){
+ if(_almostEqual(p.x, nfp[i][j].x) &&
+ _almostEqual(p.y, nfp[i][j].y)){
+ return true;
+ }
+ }
+ }
+
+ return false;
+ };
+
+ for(size_t i = 0; i < A.size() - 1; i++){
+ if(!A[i].marked) {
+ A[i].marked = true;
+ for(size_t j = 0; j < B.size(); j++){
+ B.offsetx = A[i].x - B[j].x;
+ B.offsety = A[i].y - B[j].y;
+
+ int Binside = 0;
+ for(size_t k = 0; k < B.size(); k++){
+ int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A);
+ if(inpoly != 0){
+ Binside = inpoly;
+ break;
+ }
+ }
+
+ if(Binside == 0){ // A and B are the same
+ return {false, {}};
+ }
+
+ auto startPoint = std::make_pair(true, Vector(B.offsetx, B.offsety));
+ if(((Binside && inside) || (!Binside && !inside)) &&
+ !intersect(A,B) && !inNfp(startPoint.second, NFP)){
+ return startPoint;
+ }
+
+ // slide B along vector
+ auto vx = A[i+1].x - A[i].x;
+ auto vy = A[i+1].y - A[i].y;
+
+ double d1 = polygonProjectionDistance(A,B,{vx, vy});
+ double d2 = polygonProjectionDistance(B,A,{-vx, -vy});
+
+ double d = dNAN;
+
+ // todo: clean this up
+ if(isnan(d1) && isnan(d2)){
+ // nothin
+ }
+ else if(isnan(d1)){
+ d = d2;
+ }
+ else if(isnan(d2)){
+ d = d1;
+ }
+ else{
+ d = min(d1,d2);
+ }
+
+ // only slide until no longer negative
+ // todo: clean this up
+ if(!isnan(d) && !_almostEqual(d,0) && d > 0){
+
+ }
+ else{
+ continue;
+ }
+
+ auto vd2 = vx*vx + vy*vy;
+
+ if(d*d < vd2 && !_almostEqual(d*d, vd2)){
+ auto vd = sqrt(vx*vx + vy*vy);
+ vx *= d/vd;
+ vy *= d/vd;
+ }
+
+ B.offsetx += vx;
+ B.offsety += vy;
+
+ for(size_t k = 0; k < B.size(); k++){
+ int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A);
+ if(inpoly != 0){
+ Binside = inpoly;
+ break;
+ }
+ }
+ startPoint = std::make_pair(true, Vector{B.offsetx, B.offsety});
+ if(((Binside && inside) || (!Binside && !inside)) &&
+ !intersect(A,B) && !inNfp(startPoint.second, NFP)){
+ return startPoint;
+ }
+ }
+ }
+ }
+
+ return {false, Vector(0, 0)};
+ }
+
+ static std::vector<Cntr> noFitPolygon(Cntr A,
+ Cntr B,
+ bool inside,
+ bool searchEdges)
+ {
+ if(A.size() < 3 || B.size() < 3) {
+ throw GeometryException(GeomErr::NFP);
+ return {};
+ }
+
+ A.offsetx = 0;
+ A.offsety = 0;
+
+ long i = 0, j = 0;
+
+ auto minA = y(A[0]);
+ long minAindex = 0;
+
+ auto maxB = y(B[0]);
+ long maxBindex = 0;
+
+ for(i = 1; i < A.size(); i++){
+ A[i].marked = false;
+ if(y(A[i]) < minA){
+ minA = y(A[i]);
+ minAindex = i;
+ }
+ }
+
+ for(i = 1; i < B.size(); i++){
+ B[i].marked = false;
+ if(y(B[i]) > maxB){
+ maxB = y(B[i]);
+ maxBindex = i;
+ }
+ }
+
+ std::pair<bool, Vector> startpoint;
+
+ if(!inside){
+ // shift B such that the bottom-most point of B is at the top-most
+ // point of A. This guarantees an initial placement with no
+ // intersections
+ startpoint = { true,
+ { x(A[minAindex]) - x(B[maxBindex]),
+ y(A[minAindex]) - y(B[maxBindex]) }
+ };
+ }
+ else {
+ // no reliable heuristic for inside
+ startpoint = searchStartPoint(A, B, true);
+ }
+
+ std::vector<Cntr> NFPlist;
+
+ struct Touch {
+ int type;
+ long A;
+ long B;
+ Touch(int t, long a, long b): type(t), A(a), B(b) {}
+ };
+
+ while(startpoint.first) {
+
+ B.offsetx = startpoint.second.x;
+ B.offsety = startpoint.second.y;
+
+ // maintain a list of touching points/edges
+ std::vector<Touch> touching;
+
+ struct V {
+ Coord x, y;
+ Vector *start, *end;
+ operator bool() {
+ return start != nullptr && end != nullptr;
+ }
+ operator Vector() const { return {x, y}; }
+ } prevvector = {0, 0, nullptr, nullptr};
+
+ Cntr NFP;
+ NFP.emplace_back(x(B[0]) + B.offsetx, y(B[0]) + B.offsety);
+
+ auto referencex = x(B[0]) + B.offsetx;
+ auto referencey = y(B[0]) + B.offsety;
+ auto startx = referencex;
+ auto starty = referencey;
+ unsigned counter = 0;
+
+ // sanity check, prevent infinite loop
+ while(counter < 10*(A.size() + B.size())){
+ touching.clear();
+
+ // find touching vertices/edges
+ for(i = 0; i < A.size(); i++){
+ long nexti = (i == A.size() - 1) ? 0 : i + 1;
+ for(j = 0; j < B.size(); j++){
+
+ long nextj = (j == B.size() - 1) ? 0 : j + 1;
+
+ if( _almostEqual(A[i].x, B[j].x+B.offsetx) &&
+ _almostEqual(A[i].y, B[j].y+B.offsety))
+ {
+ touching.emplace_back(0, i, j);
+ }
+ else if( _onSegment(
+ A[i], A[nexti],
+ { B[j].x+B.offsetx, B[j].y + B.offsety}) )
+ {
+ touching.emplace_back(1, nexti, j);
+ }
+ else if( _onSegment(
+ {B[j].x+B.offsetx, B[j].y + B.offsety},
+ {B[nextj].x+B.offsetx, B[nextj].y + B.offsety},
+ A[i]) )
+ {
+ touching.emplace_back(2, i, nextj);
+ }
+ }
+ }
+
+ // generate translation vectors from touching vertices/edges
+ std::vector<V> vectors;
+ for(i=0; i < touching.size(); i++){
+ auto& vertexA = A[touching[i].A];
+ vertexA.marked = true;
+
+ // adjacent A vertices
+ auto prevAindex = touching[i].A - 1;
+ auto nextAindex = touching[i].A + 1;
+
+ prevAindex = (prevAindex < 0) ? A.size() - 1 : prevAindex; // loop
+ nextAindex = (nextAindex >= A.size()) ? 0 : nextAindex; // loop
+
+ auto& prevA = A[prevAindex];
+ auto& nextA = A[nextAindex];
+
+ // adjacent B vertices
+ auto& vertexB = B[touching[i].B];
+
+ auto prevBindex = touching[i].B-1;
+ auto nextBindex = touching[i].B+1;
+
+ prevBindex = (prevBindex < 0) ? B.size() - 1 : prevBindex; // loop
+ nextBindex = (nextBindex >= B.size()) ? 0 : nextBindex; // loop
+
+ auto& prevB = B[prevBindex];
+ auto& nextB = B[nextBindex];
+
+ if(touching[i].type == 0){
+
+ V vA1 = {
+ prevA.x - vertexA.x,
+ prevA.y - vertexA.y,
+ &vertexA,
+ &prevA
+ };
+
+ V vA2 = {
+ nextA.x - vertexA.x,
+ nextA.y - vertexA.y,
+ &vertexA,
+ &nextA
+ };
+
+ // B vectors need to be inverted
+ V vB1 = {
+ vertexB.x - prevB.x,
+ vertexB.y - prevB.y,
+ &prevB,
+ &vertexB
+ };
+
+ V vB2 = {
+ vertexB.x - nextB.x,
+ vertexB.y - nextB.y,
+ &nextB,
+ &vertexB
+ };
+
+ vectors.emplace_back(vA1);
+ vectors.emplace_back(vA2);
+ vectors.emplace_back(vB1);
+ vectors.emplace_back(vB2);
+ }
+ else if(touching[i].type == 1){
+ vectors.emplace_back(V{
+ vertexA.x-(vertexB.x+B.offsetx),
+ vertexA.y-(vertexB.y+B.offsety),
+ &prevA,
+ &vertexA
+ });
+
+ vectors.emplace_back(V{
+ prevA.x-(vertexB.x+B.offsetx),
+ prevA.y-(vertexB.y+B.offsety),
+ &vertexA,
+ &prevA
+ });
+ }
+ else if(touching[i].type == 2){
+ vectors.emplace_back(V{
+ vertexA.x-(vertexB.x+B.offsetx),
+ vertexA.y-(vertexB.y+B.offsety),
+ &prevB,
+ &vertexB
+ });
+
+ vectors.emplace_back(V{
+ vertexA.x-(prevB.x+B.offsetx),
+ vertexA.y-(prevB.y+B.offsety),
+ &vertexB,
+ &prevB
+ });
+ }
+ }
+
+ // TODO: there should be a faster way to reject vectors that
+ // will cause immediate intersection. For now just check them all
+
+ V translate = {0, 0, nullptr, nullptr};
+ double maxd = 0;
+
+ for(i = 0; i < vectors.size(); i++) {
+ if(vectors[i].x == 0 && vectors[i].y == 0){
+ continue;
+ }
+
+ // if this vector points us back to where we came from, ignore it.
+ // ie cross product = 0, dot product < 0
+ if(prevvector && vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0){
+
+ // compare magnitude with unit vectors
+ double vectorlength = sqrt(vectors[i].x*vectors[i].x+vectors[i].y*vectors[i].y);
+ Vector unitv = {Coord(vectors[i].x/vectorlength),
+ Coord(vectors[i].y/vectorlength)};
+
+ double prevlength = sqrt(prevvector.x*prevvector.x+prevvector.y*prevvector.y);
+ Vector prevunit = { prevvector.x/prevlength, prevvector.y/prevlength};
+
+ // we need to scale down to unit vectors to normalize vector length. Could also just do a tan here
+ if(abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001){
+ continue;
+ }
+ }
+
+ V vi = vectors[i];
+ double d = polygonSlideDistance(A, B, vi, true);
+ double vecd2 = vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y;
+
+ if(isnan(d) || d*d > vecd2){
+ double vecd = sqrt(vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y);
+ d = vecd;
+ }
+
+ if(!isnan(d) && d > maxd){
+ maxd = d;
+ translate = vectors[i];
+ }
+ }
+
+ if(!translate || _almostEqual(maxd, 0))
+ {
+ // didn't close the loop, something went wrong here
+ NFP.clear();
+ break;
+ }
+
+ translate.start->marked = true;
+ translate.end->marked = true;
+
+ prevvector = translate;
+
+ // trim
+ double vlength2 = translate.x*translate.x + translate.y*translate.y;
+ if(maxd*maxd < vlength2 && !_almostEqual(maxd*maxd, vlength2)){
+ double scale = sqrt((maxd*maxd)/vlength2);
+ translate.x *= scale;
+ translate.y *= scale;
+ }
+
+ referencex += translate.x;
+ referencey += translate.y;
+
+ if(_almostEqual(referencex, startx) &&
+ _almostEqual(referencey, starty)) {
+ // we've made a full loop
+ break;
+ }
+
+ // if A and B start on a touching horizontal line,
+ // the end point may not be the start point
+ bool looped = false;
+ if(NFP.size() > 0) {
+ for(i = 0; i < NFP.size() - 1; i++) {
+ if(_almostEqual(referencex, NFP[i].x) &&
+ _almostEqual(referencey, NFP[i].y)){
+ looped = true;
+ }
+ }
+ }
+
+ if(looped){
+ // we've made a full loop
+ break;
+ }
+
+ NFP.emplace_back(referencex, referencey);
+
+ B.offsetx += translate.x;
+ B.offsety += translate.y;
+
+ counter++;
+ }
+
+ if(NFP.size() > 0){
+ NFPlist.emplace_back(NFP);
+ }
+
+ if(!searchEdges){
+ // only get outer NFP or first inner NFP
+ break;
+ }
+
+ startpoint =
+ searchStartPoint(A, B, inside, NFPlist);
+
+ }
+
+ return NFPlist;
+ }
+};
+
+template<class S> const double _alg<S>::TOL = std::pow(10, -9);
+
+}
+}
+
+#endif // NFP_SVGNEST_HPP
diff --git a/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp
new file mode 100644
index 000000000..ea1fb4d07
--- /dev/null
+++ b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp
@@ -0,0 +1,75 @@
+#ifndef NFP_SVGNEST_GLUE_HPP
+#define NFP_SVGNEST_GLUE_HPP
+
+#include "nfp_svgnest.hpp"
+
+#include <libnest2d/clipper_backend/clipper_backend.hpp>
+
+namespace libnest2d {
+
+namespace __svgnest {
+
+//template<> struct _Tol<double> {
+// static const BP2D_CONSTEXPR TCoord<PointImpl> Value = 1000000;
+//};
+
+}
+
+namespace nfp {
+
+using NfpR = NfpResult<PolygonImpl>;
+
+template<> struct NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
+ NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
+// return nfpConvexOnly(sh, cother);
+ namespace sl = shapelike;
+ using alg = __svgnest::_alg<PolygonImpl>;
+
+ auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
+ sl::getContour(cother), false, false);
+
+ PolygonImpl nfp_cntr;
+ if(!nfp_p.empty()) nfp_cntr.Contour = nfp_p.front();
+ return {nfp_cntr, referenceVertex(nfp_cntr)};
+ }
+};
+
+template<> struct NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
+ NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
+// return nfpConvexOnly(sh, cother);
+ namespace sl = shapelike;
+ using alg = __svgnest::_alg<PolygonImpl>;
+
+ std::cout << "Itt vagyok" << std::endl;
+ auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
+ sl::getContour(cother), false, false);
+
+ PolygonImpl nfp_cntr;
+ nfp_cntr.Contour = nfp_p.front();
+ return {nfp_cntr, referenceVertex(nfp_cntr)};
+ }
+};
+
+template<>
+struct NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
+ NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
+ namespace sl = shapelike;
+ using alg = __svgnest::_alg<PolygonImpl>;
+
+ auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
+ sl::getContour(cother), true, false);
+
+ PolygonImpl nfp_cntr;
+ nfp_cntr.Contour = nfp_p.front();
+ return {nfp_cntr, referenceVertex(nfp_cntr)};
+ }
+};
+
+template<> struct MaxNfpLevel<PolygonImpl> {
+// static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE;
+ static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
+};
+
+}}
+
+#endif // NFP_SVGNEST_GLUE_HPP
diff --git a/xs/src/libnest2d/tools/svgtools.hpp b/xs/src/libnest2d/tools/svgtools.hpp
index 3a83caa70..776dd5a1a 100644
--- a/xs/src/libnest2d/tools/svgtools.hpp
+++ b/xs/src/libnest2d/tools/svgtools.hpp
@@ -56,14 +56,14 @@ public:
auto d = static_cast<Coord>(
std::round(conf_.height*conf_.mm_in_coord_units) );
- auto& contour = ShapeLike::getContour(tsh);
+ auto& contour = shapelike::getContour(tsh);
for(auto& v : contour) setY(v, -getY(v) + d);
- auto& holes = ShapeLike::holes(tsh);
+ auto& holes = shapelike::holes(tsh);
for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d);
}
- currentLayer() += ShapeLike::serialize<Formats::SVG>(tsh,
+ currentLayer() += shapelike::serialize<Formats::SVG>(tsh,
1.0/conf_.mm_in_coord_units) + "\n";
}
diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp
index 377bdbea4..2d995551c 100644
--- a/xs/src/libslic3r/Config.hpp
+++ b/xs/src/libslic3r/Config.hpp
@@ -1171,6 +1171,7 @@ public:
// Allow DynamicConfig to be instantiated on ints own without a definition.
// If the definition is not defined, the method requiring the definition will throw NoDefinitionException.
const ConfigDef* def() const override { return nullptr; };
+ bool has(const t_config_option_key &opt_key) const { return this->options.find(opt_key) != this->options.end(); }
template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
{ return dynamic_cast<T*>(this->option(opt_key, create)); }
template<class T> const T* opt(const t_config_option_key &opt_key) const
@@ -1214,7 +1215,7 @@ public:
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
-private:
+protected:
typedef std::map<t_config_option_key,ConfigOption*> t_options_map;
t_options_map options;
};
diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp
index 733ff2ad7..9249d0ff1 100644
--- a/xs/src/libslic3r/EdgeGrid.cpp
+++ b/xs/src/libslic3r/EdgeGrid.cpp
@@ -9,7 +9,9 @@
#endif /* SLIC3R_GUI */
#include "libslic3r.h"
+#include "ClipperUtils.hpp"
#include "EdgeGrid.hpp"
+#include "SVG.hpp"
#if 0
// Enable debugging and assert in this file.
@@ -756,8 +758,8 @@ void EdgeGrid::Grid::calculate_sdf()
float search_radius = float(m_resolution<<1);
m_signed_distance_field.assign(nrows * ncols, search_radius);
// For each cell:
- for (size_t r = 0; r < m_rows; ++ r) {
- for (size_t c = 0; c < m_cols; ++ c) {
+ for (int r = 0; r < (int)m_rows; ++ r) {
+ for (int c = 0; c < (int)m_cols; ++ c) {
const Cell &cell = m_cells[r * m_cols + c];
// For each segment in the cell:
for (size_t i = cell.begin; i != cell.end; ++ i) {
@@ -842,6 +844,8 @@ void EdgeGrid::Grid::calculate_sdf()
#if 0
static int iRun = 0;
++ iRun;
+ if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
+ wxImage::AddHandler(new wxPNGHandler);
//#ifdef SLIC3R_GUI
{
wxImage img(ncols, nrows);
@@ -1225,8 +1229,10 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo
return true;
}
-Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const
+Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) const
{
+ assert(std::abs(2 * offset) < m_resolution);
+
typedef std::unordered_multimap<Point, int, PointHash> EndPointMapType;
// 0) Prepare a binary grid.
size_t cell_rows = m_rows + 2;
@@ -1237,7 +1243,7 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const
cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1);
// Fill in empty cells, which have a left / right neighbor filled.
// Fill in empty cells, which have the top / bottom neighbor filled.
- {
+ if (fill_holes) {
std::vector<char> cell_inside2(cell_inside);
for (int r = 1; r + 1 < int(cell_rows); ++ r) {
for (int c = 1; c + 1 < int(cell_cols); ++ c) {
@@ -1354,9 +1360,101 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const
return out;
}
+inline int segments_could_intersect(
+ const Slic3r::Point &ip1, const Slic3r::Point &ip2,
+ const Slic3r::Point &jp1, const Slic3r::Point &jp2)
+{
+ Slic3r::Point iv = ip1.vector_to(ip2);
+ Slic3r::Point vij1 = ip1.vector_to(jp1);
+ Slic3r::Point vij2 = ip1.vector_to(jp2);
+ int64_t tij1 = int64_t(iv.x) * int64_t(vij1.y) - int64_t(iv.y) * int64_t(vij1.x); // cross(iv, vij1)
+ int64_t tij2 = int64_t(iv.x) * int64_t(vij2.y) - int64_t(iv.y) * int64_t(vij2.x); // cross(iv, vij2)
+ int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum
+ int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0);
+ return sij1 * sij2;
+}
+
+inline bool segments_intersect(
+ const Slic3r::Point &ip1, const Slic3r::Point &ip2,
+ const Slic3r::Point &jp1, const Slic3r::Point &jp2)
+{
+ return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 &&
+ segments_could_intersect(jp1, jp2, ip1, ip2) <= 0;
+}
+
+std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> EdgeGrid::Grid::intersecting_edges() const
+{
+ std::vector<std::pair<ContourEdge, ContourEdge>> out;
+ // For each cell:
+ for (int r = 0; r < (int)m_rows; ++ r) {
+ for (int c = 0; c < (int)m_cols; ++ c) {
+ const Cell &cell = m_cells[r * m_cols + c];
+ // For each pair of segments in the cell:
+ for (size_t i = cell.begin; i != cell.end; ++ i) {
+ const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first];
+ size_t ipt = m_cell_data[i].second;
+ // End points of the line segment and their vector.
+ const Slic3r::Point &ip1 = ipts[ipt];
+ const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1];
+ for (size_t j = i + 1; j != cell.end; ++ j) {
+ const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first];
+ size_t jpt = m_cell_data[j].second;
+ // End points of the line segment and their vector.
+ const Slic3r::Point &jp1 = jpts[jpt];
+ const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1];
+ if (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2))
+ // Segments of the same contour share a common vertex.
+ continue;
+ if (segments_intersect(ip1, ip2, jp1, jp2)) {
+ // The two segments intersect. Add them to the output.
+ int jfirst = (&jpts < &ipts) || (&jpts == &ipts && jpt < ipt);
+ out.emplace_back(jfirst ?
+ std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt)) :
+ std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt)));
+ }
+ }
+ }
+ }
+ }
+ Slic3r::sort_remove_duplicates(out);
+ return out;
+}
+
+bool EdgeGrid::Grid::has_intersecting_edges() const
+{
+ // For each cell:
+ for (int r = 0; r < (int)m_rows; ++ r) {
+ for (int c = 0; c < (int)m_cols; ++ c) {
+ const Cell &cell = m_cells[r * m_cols + c];
+ // For each pair of segments in the cell:
+ for (size_t i = cell.begin; i != cell.end; ++ i) {
+ const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first];
+ size_t ipt = m_cell_data[i].second;
+ // End points of the line segment and their vector.
+ const Slic3r::Point &ip1 = ipts[ipt];
+ const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1];
+ for (size_t j = i + 1; j != cell.end; ++ j) {
+ const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first];
+ size_t jpt = m_cell_data[j].second;
+ // End points of the line segment and their vector.
+ const Slic3r::Point &jp1 = jpts[jpt];
+ const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1];
+ if (! (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) &&
+ segments_intersect(ip1, ip2, jp1, jp2))
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
#if 0
void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path)
{
+ if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
+ wxImage::AddHandler(new wxPNGHandler);
+
unsigned int w = (bbox.max.x - bbox.min.x + resolution - 1) / resolution;
unsigned int h = (bbox.max.y - bbox.min.y + resolution - 1) / resolution;
wxImage img(w, h);
@@ -1448,4 +1546,59 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo
}
#endif /* SLIC3R_GUI */
+// Find all pairs of intersectiong edges from the set of polygons.
+std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersecting_edges(const Polygons &polygons)
+{
+ double len = 0;
+ size_t cnt = 0;
+ BoundingBox bbox;
+ for (const Polygon &poly : polygons) {
+ if (poly.points.size() < 2)
+ continue;
+ for (size_t i = 0; i < poly.points.size(); ++ i) {
+ bbox.merge(poly.points[i]);
+ size_t j = (i == 0) ? (poly.points.size() - 1) : i - 1;
+ len += poly.points[i].distance_to(poly.points[j]);
+ ++ cnt;
+ }
+ }
+ len /= double(cnt);
+ bbox.offset(20);
+ EdgeGrid::Grid grid;
+ grid.set_bbox(bbox);
+ grid.create(polygons, len);
+ return grid.intersecting_edges();
+}
+
+// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG.
+void export_intersections_to_svg(const std::string &filename, const Polygons &polygons)
+{
+ std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersections = intersecting_edges(polygons);
+ BoundingBox bbox = get_extents(polygons);
+ SVG svg(filename.c_str(), bbox);
+ svg.draw(union_ex(polygons), "gray", 0.25f);
+ svg.draw_outline(polygons, "black");
+ std::set<const Points*> intersecting_contours;
+ for (const std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge> &ie : intersections) {
+ intersecting_contours.insert(ie.first.first);
+ intersecting_contours.insert(ie.second.first);
+ }
+ // Highlight the contours with intersections.
+ coord_t line_width = coord_t(scale_(0.01));
+ for (const Points *ic : intersecting_contours) {
+ svg.draw_outline(Polygon(*ic), "green");
+ svg.draw_outline(Polygon(*ic), "black", line_width);
+ }
+ // Paint the intersections.
+ for (const std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge> &intersecting_edges : intersections) {
+ auto edge = [](const EdgeGrid::Grid::ContourEdge &e) {
+ return Line(e.first->at(e.second),
+ e.first->at((e.second + 1 == e.first->size()) ? 0 : e.second + 1));
+ };
+ svg.draw(edge(intersecting_edges.first), "red", line_width);
+ svg.draw(edge(intersecting_edges.second), "red", line_width);
+ }
+ svg.Close();
+}
+
} // namespace Slic3r
diff --git a/xs/src/libslic3r/EdgeGrid.hpp b/xs/src/libslic3r/EdgeGrid.hpp
index 3eb741865..f99ab30c4 100644
--- a/xs/src/libslic3r/EdgeGrid.hpp
+++ b/xs/src/libslic3r/EdgeGrid.hpp
@@ -58,7 +58,12 @@ public:
const size_t cols() const { return m_cols; }
// For supports: Contours enclosing the rasterized edges.
- Polygons contours_simplified(coord_t offset) const;
+ Polygons contours_simplified(coord_t offset, bool fill_holes) const;
+
+ typedef std::pair<const Slic3r::Points*, size_t> ContourPoint;
+ typedef std::pair<const Slic3r::Points*, size_t> ContourEdge;
+ std::vector<std::pair<ContourEdge, ContourEdge>> intersecting_edges() const;
+ bool has_intersecting_edges() const;
protected:
struct Cell {
@@ -113,6 +118,13 @@ extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resoluti
#endif /* SLIC3R_GUI */
} // namespace EdgeGrid
+
+// Find all pairs of intersectiong edges from the set of polygons.
+extern std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersecting_edges(const Polygons &polygons);
+
+// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG.
+extern void export_intersections_to_svg(const std::string &filename, const Polygons &polygons);
+
} // namespace Slic3r
#endif /* slic3r_EdgeGrid_hpp_ */
diff --git a/xs/src/libslic3r/ExPolygonCollection.cpp b/xs/src/libslic3r/ExPolygonCollection.cpp
index e52498ecb..6933544b6 100644
--- a/xs/src/libslic3r/ExPolygonCollection.cpp
+++ b/xs/src/libslic3r/ExPolygonCollection.cpp
@@ -61,12 +61,11 @@ ExPolygonCollection::rotate(double angle, const Point &center)
}
template <class T>
-bool
-ExPolygonCollection::contains(const T &item) const
+bool ExPolygonCollection::contains(const T &item) const
{
- for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
- if (it->contains(item)) return true;
- }
+ for (const ExPolygon &poly : this->expolygons)
+ if (poly.contains(item))
+ return true;
return false;
}
template bool ExPolygonCollection::contains<Point>(const Point &item) const;
diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp
index d338771f4..7eefdcdb3 100644
--- a/xs/src/libslic3r/ExtrusionEntity.hpp
+++ b/xs/src/libslic3r/ExtrusionEntity.hpp
@@ -91,6 +91,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
virtual double min_mm3_per_mm() const = 0;
virtual Polyline as_polyline() const = 0;
+ virtual void collect_polylines(Polylines &dst) const = 0;
+ virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; }
virtual double length() const = 0;
virtual double total_volume() const = 0;
};
@@ -123,8 +125,11 @@ public:
ExtrusionPath* clone() const { return new ExtrusionPath (*this); }
void reverse() { this->polyline.reverse(); }
- Point first_point() const { return this->polyline.points.front(); }
- Point last_point() const { return this->polyline.points.back(); }
+ Point first_point() const override { return this->polyline.points.front(); }
+ Point last_point() const override { return this->polyline.points.back(); }
+ size_t size() const { return this->polyline.size(); }
+ bool empty() const { return this->polyline.empty(); }
+ bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
// Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection.
// Currently not used.
void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
@@ -133,8 +138,8 @@ public:
void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
void clip_end(double distance);
void simplify(double tolerance);
- virtual double length() const;
- virtual ExtrusionRole role() const { return m_role; }
+ double length() const override;
+ ExtrusionRole role() const override { return m_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
@@ -149,7 +154,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const { return this->mm3_per_mm; }
Polyline as_polyline() const { return this->polyline; }
- virtual double total_volume() const { return mm3_per_mm * unscale(length()); }
+ void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
+ double total_volume() const override { return mm3_per_mm * unscale(length()); }
private:
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
@@ -178,10 +184,10 @@ public:
bool can_reverse() const { return true; }
ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); }
void reverse();
- Point first_point() const { return this->paths.front().polyline.points.front(); }
- Point last_point() const { return this->paths.back().polyline.points.back(); }
- virtual double length() const;
- virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); }
+ Point first_point() const override { return this->paths.front().polyline.points.front(); }
+ Point last_point() const override { return this->paths.back().polyline.points.back(); }
+ double length() const override;
+ ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
@@ -196,7 +202,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const;
Polyline as_polyline() const;
- virtual double total_volume() const { double volume = 0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
+ void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
+ double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
};
@@ -219,18 +226,18 @@ public:
bool make_clockwise();
bool make_counter_clockwise();
void reverse();
- Point first_point() const { return this->paths.front().polyline.points.front(); }
- Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
+ Point first_point() const override { return this->paths.front().polyline.points.front(); }
+ Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
Polygon polygon() const;
- virtual double length() const;
+ double length() const override;
bool split_at_vertex(const Point &point);
void split_at(const Point &point, bool prefer_non_overhang);
void clip_end(double distance, ExtrusionPaths* paths) const;
// Test, whether the point is extruded by a bridging flow.
// This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead.
bool has_overhang_point(const Point &point) const;
- virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); }
- ExtrusionLoopRole loop_role() const { return m_loop_role; }
+ ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
+ ExtrusionLoopRole loop_role() const { return m_loop_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
@@ -245,7 +252,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const;
Polyline as_polyline() const { return this->polygon().split_at_first_point(); }
- virtual double total_volume() const { double volume = 0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
+ void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
+ double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
private:
ExtrusionLoopRole m_loop_role;
diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp
index a2cdc31ad..b84c7d73e 100644
--- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp
+++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp
@@ -24,7 +24,7 @@ public:
explicit operator ExtrusionPaths() const;
bool is_collection() const { return true; };
- virtual ExtrusionRole role() const {
+ ExtrusionRole role() const override {
ExtrusionRole out = erNone;
for (const ExtrusionEntity *ee : entities) {
ExtrusionRole er = ee->role();
@@ -66,11 +66,11 @@ public:
Point last_point() const { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
- virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
+ void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
- virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const;
+ void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
@@ -81,14 +81,20 @@ public:
void flattenIfSortable(ExtrusionEntityCollection* retval) const;
ExtrusionEntityCollection flattenIfSortable() const;
double min_mm3_per_mm() const;
- virtual double total_volume() const {double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
+ double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
// Following methods shall never be called on an ExtrusionEntityCollection.
Polyline as_polyline() const {
CONFESS("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline();
};
- virtual double length() const {
+
+ void collect_polylines(Polylines &dst) const override {
+ for (ExtrusionEntity* extrusion_entity : this->entities)
+ extrusion_entity->collect_polylines(dst);
+ }
+
+ double length() const override {
CONFESS("Calling length() on a ExtrusionEntityCollection");
return 0.;
}
diff --git a/xs/src/libslic3r/Fill/FillGyroid.cpp b/xs/src/libslic3r/Fill/FillGyroid.cpp
index 89d5d231e..46d6382f7 100644
--- a/xs/src/libslic3r/Fill/FillGyroid.cpp
+++ b/xs/src/libslic3r/Fill/FillGyroid.cpp
@@ -131,7 +131,7 @@ void FillGyroid::_fill_surface_single(
// no rotation is supported for this infill pattern (yet)
BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight.
- double density_adjusted = std::max(0., params.density * 2.);
+ double density_adjusted = std::max(0., params.density * 2.44);
// Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp
index aa0e0f6b0..aa52856ae 100644
--- a/xs/src/libslic3r/Fill/FillHoneycomb.cpp
+++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp
@@ -86,8 +86,8 @@ void FillHoneycomb::_fill_surface_single(
Polylines paths;
{
Polylines p;
- for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it)
- p.push_back((Polyline)(*it));
+ for (Polygon &poly : polygons)
+ p.emplace_back(poly.points);
paths = intersection_pl(p, to_polygons(expolygon));
}
diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp
index b60e26dcc..e92674a17 100644
--- a/xs/src/libslic3r/Flow.cpp
+++ b/xs/src/libslic3r/Flow.cpp
@@ -115,7 +115,8 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
// if object->config.support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config.layer_height.value),
- false);
+ // bridge_flow_ratio
+ 0.f);
}
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
@@ -127,7 +128,8 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig
(width.value > 0) ? width : object->config.extrusion_width,
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)),
- false);
+ // bridge_flow_ratio
+ 0.f);
}
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
@@ -139,7 +141,8 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig
// if object->config.support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config.layer_height.value),
- false);
+ // bridge_flow_ratio
+ 0.f);
}
}
diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp
index 945bb1f86..5f6a48cf8 100644
--- a/xs/src/libslic3r/Format/3mf.cpp
+++ b/xs/src/libslic3r/Format/3mf.cpp
@@ -71,6 +71,7 @@ const char* VOLUME_TYPE = "volume";
const char* NAME_KEY = "name";
const char* MODIFIER_KEY = "modifier";
+const char* VOLUME_TYPE_KEY = "volume_type";
const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
const char* VALID_OBJECT_TYPES[] =
@@ -603,8 +604,6 @@ namespace Slic3r {
if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr))
return false;
-
- object.second->center_around_origin();
}
// fixes the min z of the model if negative
@@ -1501,7 +1500,9 @@ namespace Slic3r {
if (metadata.key == NAME_KEY)
volume->name = metadata.value;
else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
- volume->modifier = true;
+ volume->set_type(ModelVolume::PARAMETER_MODIFIER);
+ else if (metadata.key == VOLUME_TYPE_KEY)
+ volume->set_type(ModelVolume::type_from_string(metadata.value));
else
volume->config.set_deserialize(metadata.key, metadata.value);
}
@@ -2017,9 +2018,12 @@ namespace Slic3r {
if (!volume->name.empty())
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
- // stores volume's modifier field
- if (volume->modifier)
+ // stores volume's modifier field (legacy, to support old slicers)
+ if (volume->is_modifier())
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
+ // stores volume's type (overrides the modifier field above)
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
+ VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";
// stores volume's config data
for (const std::string& key : volume->config.keys())
diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp
index 886bbae97..5aa922f62 100644
--- a/xs/src/libslic3r/Format/AMF.cpp
+++ b/xs/src/libslic3r/Format/AMF.cpp
@@ -458,9 +458,14 @@ void AMFParserContext::endElement(const char * /* name */)
p = end + 1;
}
m_object->layer_height_profile_valid = true;
- } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume && strcmp(opt_key, "modifier") == 0) {
- // Is this volume a modifier volume?
- m_volume->modifier = atoi(m_value[1].c_str()) == 1;
+ } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) {
+ if (strcmp(opt_key, "modifier") == 0) {
+ // Is this volume a modifier volume?
+ // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
+ m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
+ } else if (strcmp(opt_key, "volume_type") == 0) {
+ m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
+ }
}
} else if (m_path.size() == 3) {
if (m_path[1] == NODE_TYPE_MATERIAL) {
@@ -781,8 +786,9 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n";
if (!volume->name.empty())
stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
- if (volume->modifier)
+ if (volume->is_modifier())
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
+ stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
stream << " <triangle>\n";
for (int j = 0; j < 3; ++j)
diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp
index 708fe500c..a5e8bd954 100644
--- a/xs/src/libslic3r/GCode.cpp
+++ b/xs/src/libslic3r/GCode.cpp
@@ -276,7 +276,6 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco
}
-
std::string WipeTowerIntegration::prime(GCode &gcodegen)
{
assert(m_layer_idx == 0);
@@ -665,6 +664,14 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
_write_format(file, "\n");
}
+ // adds tags for time estimators
+ if (print.config.remaining_times.value)
+ {
+ _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag);
+ if (m_silent_time_estimator_enabled)
+ _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
+ }
+
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser;
m_placeholder_parser.update_timestamp();
@@ -724,7 +731,6 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming);
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id);
-
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
// Set extruder(s) temperature before and after start G-code.
@@ -960,17 +966,20 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
// Get filament stats.
print.filament_stats.clear();
- print.total_used_filament = 0.;
- print.total_extruded_volume = 0.;
- print.total_weight = 0.;
- print.total_cost = 0.;
+ print.total_used_filament = 0.;
+ print.total_extruded_volume = 0.;
+ print.total_weight = 0.;
+ print.total_cost = 0.;
+ print.total_wipe_tower_cost = 0.;
+ print.total_wipe_tower_filament = 0.;
print.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms();
print.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
for (const Extruder &extruder : m_writer.extruders()) {
- double used_filament = extruder.used_filament();
- double extruded_volume = extruder.extruded_volume();
+ double used_filament = extruder.used_filament() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] : 0.f);
+ double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
+
print.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament));
_write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001);
if (filament_weight > 0.) {
@@ -981,8 +990,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
_write_format(file, "; filament cost = %.1lf\n", filament_cost);
}
}
- print.total_used_filament = print.total_used_filament + used_filament;
- print.total_extruded_volume = print.total_extruded_volume + extruded_volume;
+ print.total_used_filament += used_filament;
+ print.total_extruded_volume += extruded_volume;
+ print.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
+ print.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
}
_write_format(file, "; total filament cost = %.1lf\n", print.total_cost);
_write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp
index b343b53de..bc004412c 100644
--- a/xs/src/libslic3r/GCode.hpp
+++ b/xs/src/libslic3r/GCode.hpp
@@ -98,6 +98,7 @@ public:
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen);
+ std::vector<float> used_filament_length() const;
private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp
index 9bf350328..21c10969a 100644
--- a/xs/src/libslic3r/GCode/WipeTower.hpp
+++ b/xs/src/libslic3r/GCode/WipeTower.hpp
@@ -155,6 +155,12 @@ public:
// the wipe tower has been completely covered by the tool change extrusions,
// or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
virtual bool layer_finished() const = 0;
+
+ // Returns used filament length per extruder:
+ virtual std::vector<float> get_used_filament() const = 0;
+
+ // Returns total number of toolchanges:
+ virtual int get_number_of_toolchanges() const = 0;
};
}; // namespace Slic3r
diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
index 5f364f641..d3c6b7fdd 100644
--- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
+++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
@@ -111,9 +111,10 @@ public:
const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); }
float elapsed_time() const { return m_elapsed_time; }
+ float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
// Extrude with an explicitely provided amount of extrusion.
- Writer& extrude_explicit(float x, float y, float e, float f = 0.f)
+ Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false)
{
if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate))
// Neither extrusion nor a travel move.
@@ -122,6 +123,8 @@ public:
float dx = x - m_current_pos.x;
float dy = y - m_current_pos.y;
double len = sqrt(dx*dx+dy*dy);
+ if (record_length)
+ m_used_filament_length += e;
// Now do the "internal rotation" with respect to the wipe tower center
@@ -162,8 +165,8 @@ public:
return *this;
}
- Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f)
- { return extrude_explicit(dest.x, dest.y, e, f); }
+ Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false)
+ { return extrude_explicit(dest.x, dest.y, e, f, record_length); }
// Travel to a new XY position. f=0 means use the current value.
Writer& travel(float x, float y, float f = 0.f)
@@ -177,7 +180,7 @@ public:
{
float dx = x - m_current_pos.x;
float dy = y - m_current_pos.y;
- return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f);
+ return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true);
}
Writer& extrude(const WipeTower::xy &dest, const float f = 0.f)
@@ -259,8 +262,8 @@ public:
// extrude quickly amount e to x2 with feed f.
Writer& ram(float x1, float x2, float dy, float e0, float e, float f)
{
- extrude_explicit(x1, m_current_pos.y + dy, e0, f);
- extrude_explicit(x2, m_current_pos.y, e);
+ extrude_explicit(x1, m_current_pos.y + dy, e0, f, true);
+ extrude_explicit(x2, m_current_pos.y, e, 0.f, true);
return *this;
}
@@ -404,6 +407,7 @@ private:
float m_last_fan_speed = 0.f;
int current_temp = -1;
const float m_default_analyzer_line_width;
+ float m_used_filament_length = 0.f;
std::string set_format_X(float x)
{
@@ -525,6 +529,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
++ m_num_tool_changes;
}
+ m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear
+ // in the output gcode - we should not remember emitting them (we will output them twice in the worst case)
+
// Reset the extruder current to a normal value.
writer.set_extruder_trimpot(550)
.feedrate(6000)
@@ -537,6 +544,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
// so that tool_change() will know to extrude the wipe tower brim:
m_print_brim = true;
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
ToolChangeResult result;
result.priming = true;
result.print_z = this->m_z_pos;
@@ -606,10 +616,10 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
toolchange_Load(writer, cleaning_box);
writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
+ ++ m_num_tool_changes;
} else
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
- ++ m_num_tool_changes;
m_depth_traversed += wipe_area;
if (last_change_in_layer) {// draw perimeter line
@@ -632,6 +642,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
";------------------\n"
"\n\n");
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
@@ -685,6 +698,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
m_print_brim = false; // Mark the brim as extruded
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
@@ -806,8 +822,9 @@ void WipeTowerPrusaMM::toolchange_Unload(
.load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed)
.travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/
.resume_preview();
-
- if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait.
+ if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait.
+ // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset)
+ // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off).
writer.set_extruder_temp(new_temperature, false);
m_old_temperature = new_temperature;
}
@@ -851,6 +868,9 @@ void WipeTowerPrusaMM::toolchange_Change(
const unsigned int new_tool,
material_type new_material)
{
+ // Ask the writer about how much of the old filament we consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
// Speed override for the material. Go slow for flex and soluble materials.
int speed_override;
switch (new_material) {
@@ -913,7 +933,6 @@ void WipeTowerPrusaMM::toolchange_Wipe(
const float& xl = cleaning_box.ld.x;
const float& xr = cleaning_box.rd.x;
-
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
// the ordered volume, even if it means violating the box. This can later be removed and simply
// wipe until the end of the assigned area.
@@ -928,7 +947,6 @@ void WipeTowerPrusaMM::toolchange_Wipe(
m_left_to_right = !m_left_to_right;
}
-
// now the wiping itself:
for (int i = 0; true; ++i) {
if (i!=0) {
@@ -937,7 +955,7 @@ void WipeTowerPrusaMM::toolchange_Wipe(
else if (wipe_speed < 2210.f) wipe_speed = 4200.f;
else wipe_speed = std::min(4800.f, wipe_speed + 50.f);
}
-
+
float traversed_x = writer.x();
if (m_left_to_right)
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff);
@@ -1052,6 +1070,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
@@ -1169,6 +1190,8 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
m_layer_info = m_plan.begin();
m_current_tool = (unsigned int)(-2); // we don't know which extruder to start with - we'll set it according to the first toolchange
+ for (auto& used : m_used_filament_length) // reset used filament stats
+ used = 0.f;
std::vector<WipeTower::ToolChangeResult> layer_result;
for (auto layer : m_plan)
@@ -1210,9 +1233,6 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
}
}
-
-
-
void WipeTowerPrusaMM::make_wipe_tower_square()
{
const float width = m_wipe_tower_width - 3 * m_perimeter_width;
@@ -1236,7 +1256,6 @@ void WipeTowerPrusaMM::make_wipe_tower_square()
plan_tower(); // propagates depth downwards again (width has changed)
for (auto& lay : m_plan) // depths set, now the spacing
lay.extra_spacing = lay.depth / lay.toolchanges_depth();
-
}
diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
index c5a6ba841..ee2436051 100644
--- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
+++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
@@ -46,7 +46,7 @@ public:
WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction,
float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging,
const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool, float first_layer_width) :
- m_wipe_tower_pos(x, y),
+ m_wipe_tower_pos(x, y),
m_wipe_tower_width(width),
m_wipe_tower_rotation_angle(rotation_angle),
m_y_shift(0.f),
@@ -95,6 +95,8 @@ public:
m_filpar[idx].ramming_step_multiplicator /= 100;
while (stream >> speed)
m_filpar[idx].ramming_speed.push_back(speed);
+
+ m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
}
@@ -173,6 +175,9 @@ public:
return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed);
}
+ virtual std::vector<float> get_used_filament() const override { return m_used_filament_length; }
+ virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; }
+
private:
WipeTowerPrusaMM();
@@ -333,6 +338,9 @@ private:
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
+ // Stores information about used filament length per extruder:
+ std::vector<float> m_used_filament_length;
+
// Returns gcode for wipe tower brim
// sideOnly -- set to false -- experimental, draw brim on sides of wipe tower
diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp
index c4ffb572a..7471367fe 100644
--- a/xs/src/libslic3r/GCodeTimeEstimator.cpp
+++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp
@@ -168,6 +168,9 @@ namespace Slic3r {
}
#endif // ENABLE_MOVE_STATS
+ const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER";
+ const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; SILENT_FIRST_M73_OUTPUT_PLACEHOLDER";
+
GCodeTimeEstimator::GCodeTimeEstimator(EMode mode)
: _mode(mode)
{
@@ -294,7 +297,15 @@ namespace Slic3r {
throw std::runtime_error(std::string("Remaining times export failed.\nError while reading from file.\n"));
}
- gcode_line += "\n";
+ // replaces placeholders for initial line M73 with the real lines
+ if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) ||
+ ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag)))
+ {
+ sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str());
+ gcode_line = time_line;
+ }
+ else
+ gcode_line += "\n";
// add remaining time lines where needed
_parser.parse_line(gcode_line,
diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp
index 1fa74e304..e9da584c3 100644
--- a/xs/src/libslic3r/GCodeTimeEstimator.hpp
+++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp
@@ -17,6 +17,9 @@ namespace Slic3r {
class GCodeTimeEstimator
{
public:
+ static const std::string Normal_First_M73_Output_Placeholder_Tag;
+ static const std::string Silent_First_M73_Output_Placeholder_Tag;
+
enum EMode : unsigned char
{
Normal,
diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp
index 84bb9cb9e..aee6e22e4 100644
--- a/xs/src/libslic3r/Layer.hpp
+++ b/xs/src/libslic3r/Layer.hpp
@@ -21,47 +21,39 @@ class LayerRegion
friend class Layer;
public:
- Layer* layer() { return this->_layer; }
- const Layer* layer() const { return this->_layer; }
- PrintRegion* region() { return this->_region; }
- const PrintRegion* region() const { return this->_region; }
+ Layer* layer() { return this->_layer; }
+ const Layer* layer() const { return this->_layer; }
+ PrintRegion* region() { return this->_region; }
+ const PrintRegion* region() const { return this->_region; }
- // collection of surfaces generated by slicing the original geometry
- // divided by type top/bottom/internal
- SurfaceCollection slices;
-
- // collection of extrusion paths/loops filling gaps
- // These fills are generated by the perimeter generator.
- // They are not printed on their own, but they are copied to this->fills during infill generation.
- ExtrusionEntityCollection thin_fills;
+ // Collection of surfaces generated by slicing the original geometry, divided by type top/bottom/internal.
+ SurfaceCollection slices;
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
// and for re-starting of infills.
- ExPolygons fill_expolygons;
+ ExPolygons fill_expolygons;
// Unspecified fill polygons, used for interecting when we don't want the infill/perimeter overlap
ExPolygons fill_no_overlap_expolygons;
// collection of surfaces for infill generation
- SurfaceCollection fill_surfaces;
-
- // Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces).
- // While not necessary, the memory consumption is meager and it speeds up calculation.
- // The perimeter_surfaces keep the IDs of the slices (top/bottom/)
- SurfaceCollection perimeter_surfaces;
+ SurfaceCollection fill_surfaces;
+ // Collection of extrusion paths/loops filling gaps.
+ // These fills are generated by the perimeter generator.
+ // They are not printed on their own, but they are copied to this->fills during infill generation.
+ ExtrusionEntityCollection thin_fills;
- // collection of expolygons representing the bridged areas (thus not
- // needing support material)
- Polygons bridged;
+ // Collection of expolygons representing the bridged areas (thus not needing support material).
+ //FIXME Not used as of now.
+ Polygons bridged;
// collection of polylines representing the unsupported bridge edges
- PolylineCollection unsupported_bridge_edges;
-
- // ordered collection of extrusion paths/loops to build all perimeters
- // (this collection contains only ExtrusionEntityCollection objects)
- ExtrusionEntityCollection perimeters;
-
- // ordered collection of extrusion paths to fill surfaces
- // (this collection contains only ExtrusionEntityCollection objects)
- ExtrusionEntityCollection fills;
+ PolylineCollection unsupported_bridge_edges;
+
+ // Ordered collection of extrusion paths/loops to build all perimeters.
+ // This collection contains only ExtrusionEntityCollection objects.
+ ExtrusionEntityCollection perimeters;
+ // Ordered collection of extrusion paths to fill surfaces.
+ // This collection contains only ExtrusionEntityCollection objects.
+ ExtrusionEntityCollection fills;
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
void slices_to_fill_surfaces_clipped();
diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp
index aee94ab17..29629fff0 100644
--- a/xs/src/libslic3r/LayerRegion.cpp
+++ b/xs/src/libslic3r/LayerRegion.cpp
@@ -15,8 +15,7 @@
namespace Slic3r {
-Flow
-LayerRegion::flow(FlowRole role, bool bridge, double width) const
+Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
{
return this->_region->flow(
role,
@@ -51,8 +50,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
}
}
-void
-LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
+void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
{
this->perimeters.clear();
this->thin_fills.clear();
@@ -359,8 +357,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
-void
-LayerRegion::prepare_fill_surfaces()
+void LayerRegion::prepare_fill_surfaces()
{
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial");
@@ -401,8 +398,7 @@ LayerRegion::prepare_fill_surfaces()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
-double
-LayerRegion::infill_area_threshold() const
+double LayerRegion::infill_area_threshold() const
{
double ss = this->flow(frSolidInfill).scaled_spacing();
return ss*ss;
diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
index 09b515c2f..e0097ce2e 100644
--- a/xs/src/libslic3r/Model.cpp
+++ b/xs/src/libslic3r/Model.cpp
@@ -609,7 +609,8 @@ const BoundingBoxf3& ModelObject::bounding_box() const
if (! m_bounding_box_valid) {
BoundingBoxf3 raw_bbox;
for (const ModelVolume *v : this->volumes)
- if (! v->modifier)
+ if (v->is_model_part())
+ // mesh.bounding_box() returns a cached value.
raw_bbox.merge(v->mesh.bounding_box());
BoundingBoxf3 bb;
for (const ModelInstance *i : this->instances)
@@ -640,7 +641,7 @@ TriangleMesh ModelObject::raw_mesh() const
{
TriangleMesh mesh;
for (const ModelVolume *v : this->volumes)
- if (! v->modifier)
+ if (v->is_model_part())
mesh.merge(v->mesh);
return mesh;
}
@@ -651,7 +652,7 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const
{
BoundingBoxf3 bb;
for (const ModelVolume *v : this->volumes)
- if (! v->modifier) {
+ if (v->is_model_part()) {
if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances");
bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true));
}
@@ -663,7 +664,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_
{
BoundingBoxf3 bb;
for (ModelVolume *v : this->volumes)
- if (! v->modifier)
+ if (v->is_model_part())
bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate));
return bb;
}
@@ -674,7 +675,7 @@ void ModelObject::center_around_origin()
// center this object around the origin
BoundingBoxf3 bb;
for (ModelVolume *v : this->volumes)
- if (! v->modifier)
+ if (v->is_model_part())
bb.merge(v->mesh.bounding_box());
// first align to origin on XYZ
@@ -778,7 +779,7 @@ size_t ModelObject::facets_count() const
{
size_t num = 0;
for (const ModelVolume *v : this->volumes)
- if (! v->modifier)
+ if (v->is_model_part())
num += v->mesh.stl.stats.number_of_facets;
return num;
}
@@ -786,7 +787,7 @@ size_t ModelObject::facets_count() const
bool ModelObject::needed_repair() const
{
for (const ModelVolume *v : this->volumes)
- if (! v->modifier && v->mesh.needed_repair())
+ if (v->is_model_part() && v->mesh.needed_repair())
return true;
return false;
}
@@ -802,7 +803,7 @@ void ModelObject::cut(coordf_t z, Model* model) const
lower->input_file = "";
for (ModelVolume *volume : this->volumes) {
- if (volume->modifier) {
+ if (! volume->is_model_part()) {
// don't cut modifiers
upper->add_volume(*volume);
lower->add_volume(*volume);
@@ -854,7 +855,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
ModelVolume* new_volume = new_object->add_volume(*mesh);
new_volume->name = volume->name;
new_volume->config = volume->config;
- new_volume->modifier = volume->modifier;
+ new_volume->set_type(volume->type());
new_volume->material_id(volume->material_id());
new_objects->push_back(new_object);
@@ -868,7 +869,7 @@ void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_
{
for (const ModelVolume* vol : this->volumes)
{
- if (!vol->modifier)
+ if (vol->is_model_part())
{
for (ModelInstance* inst : this->instances)
{
@@ -972,6 +973,42 @@ const TriangleMesh& ModelVolume::get_convex_hull() const
return m_convex_hull;
}
+TriangleMesh& ModelVolume::get_convex_hull()
+{
+ return m_convex_hull;
+}
+
+ModelVolume::Type ModelVolume::type_from_string(const std::string &s)
+{
+ // Legacy support
+ if (s == "0")
+ return MODEL_PART;
+ if (s == "1")
+ return PARAMETER_MODIFIER;
+ // New type (supporting the support enforcers & blockers)
+ if (s == "ModelPart")
+ return MODEL_PART;
+ if (s == "ParameterModifier")
+ return PARAMETER_MODIFIER;
+ if (s == "SupportEnforcer")
+ return SUPPORT_ENFORCER;
+ if (s == "SupportBlocker")
+ return SUPPORT_BLOCKER;
+}
+
+std::string ModelVolume::type_to_string(const Type t)
+{
+ switch (t) {
+ case MODEL_PART: return "ModelPart";
+ case PARAMETER_MODIFIER: return "ParameterModifier";
+ case SUPPORT_ENFORCER: return "SupportEnforcer";
+ case SUPPORT_BLOCKER: return "SupportBlocker";
+ default:
+ assert(false);
+ return "ModelPart";
+ }
+}
+
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.
diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
index dadd515de..353dc5ccc 100644
--- a/xs/src/libslic3r/Model.hpp
+++ b/xs/src/libslic3r/Model.hpp
@@ -165,15 +165,27 @@ public:
// Configuration parameters specific to an object model geometry or a modifier volume,
// overriding the global Slic3r settings and the ModelObject settings.
DynamicPrintConfig config;
- // Is it an object to be printed, or a modifier volume?
- bool modifier;
-
+
+ enum Type {
+ MODEL_TYPE_INVALID = -1,
+ MODEL_PART = 0,
+ PARAMETER_MODIFIER,
+ SUPPORT_ENFORCER,
+ SUPPORT_BLOCKER,
+ };
+
// A parent object owning this modifier volume.
- ModelObject* get_object() const { return this->object; };
+ ModelObject* get_object() const { return this->object; };
+ Type type() const { return m_type; }
+ void set_type(const Type t) { m_type = t; }
+ bool is_model_part() const { return m_type == MODEL_PART; }
+ bool is_modifier() const { return m_type == PARAMETER_MODIFIER; }
+ bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; }
+ bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; }
t_model_material_id material_id() const { return this->_material_id; }
- void material_id(t_model_material_id material_id);
- ModelMaterial* material() const;
- void set_material(t_model_material_id material_id, const ModelMaterial &material);
+ void material_id(t_model_material_id material_id);
+ ModelMaterial* material() const;
+ void set_material(t_model_material_id material_id, const ModelMaterial &material);
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.
@@ -183,25 +195,32 @@ public:
void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const;
+ TriangleMesh& get_convex_hull();
+
+ // Helpers for loading / storing into AMF / 3MF files.
+ static Type type_from_string(const std::string &s);
+ static std::string type_to_string(const Type t);
private:
// Parent object owning this ModelVolume.
- ModelObject* object;
- t_model_material_id _material_id;
+ ModelObject* object;
+ // Is it an object to be printed, or a modifier volume?
+ Type m_type;
+ t_model_material_id _material_id;
- ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object)
+ ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object)
{
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
- ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {}
+ ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {}
ModelVolume(ModelObject *object, const ModelVolume &other) :
- name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object)
+ name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object)
{
this->material_id(other.material_id());
}
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
- name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object)
+ name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object)
{
this->material_id(other.material_id());
if (mesh.stl.stats.number_of_facets > 1)
diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp
index f2d399ac6..b1ccf4d13 100644
--- a/xs/src/libslic3r/ModelArrange.hpp
+++ b/xs/src/libslic3r/ModelArrange.hpp
@@ -99,54 +99,56 @@ namespace bgi = boost::geometry::index;
using SpatElement = std::pair<Box, unsigned>;
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
+using ItemGroup = std::vector<std::reference_wrapper<Item>>;
+template<class TBin>
+using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>;
+
+const double BIG_ITEM_TRESHOLD = 0.02;
+
+Box boundingBox(const Box& pilebb, const Box& ibb ) {
+ auto& pminc = pilebb.minCorner();
+ auto& pmaxc = pilebb.maxCorner();
+ auto& iminc = ibb.minCorner();
+ auto& imaxc = ibb.maxCorner();
+ PointImpl minc, maxc;
+
+ setX(minc, std::min(getX(pminc), getX(iminc)));
+ setY(minc, std::min(getY(pminc), getY(iminc)));
+
+ setX(maxc, std::max(getX(pmaxc), getX(imaxc)));
+ setY(maxc, std::max(getY(pmaxc), getY(imaxc)));
+ return Box(minc, maxc);
+}
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const PointImpl& bincenter,
- double /*bin_area*/,
- ShapeLike::Shapes<PolygonImpl>& pile, // The currently arranged pile
- double /*pile_area*/,
+ const shapelike::Shapes<PolygonImpl>& merged_pile,
+ const Box& pilebb,
+ const ItemGroup& items,
const Item &item,
+ double bin_area,
double norm, // A norming factor for physical dimensions
- std::vector<double>& areacache, // pile item areas will be cached
// a spatial index to quickly get neighbors of the candidate item
- SpatIndex& spatindex
+ const SpatIndex& spatindex,
+ const SpatIndex& smalls_spatindex,
+ const ItemGroup& remaining
)
{
- using pl = PointLike;
- using sl = ShapeLike;
+ using Coord = TCoord<PointImpl>;
- static const double BIG_ITEM_TRESHOLD = 0.2;
static const double ROUNDNESS_RATIO = 0.5;
static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
// We will treat big items (compared to the print bed) differently
- auto normarea = [norm](double area) { return std::sqrt(area)/norm; };
-
- // If a new bin has been created:
- if(pile.size() < areacache.size()) {
- areacache.clear();
- spatindex.clear();
- }
-
- // We must fill the caches:
- int idx = 0;
- for(auto& p : pile) {
- if(idx == areacache.size()) {
- areacache.emplace_back(sl::area(p));
- if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD)
- spatindex.insert({sl::boundingBox(p), idx});
- }
-
- idx++;
- }
+ auto isBig = [bin_area](double a) {
+ return a/bin_area > BIG_ITEM_TRESHOLD ;
+ };
// Candidate item bounding box
- auto ibb = item.boundingBox();
+ auto ibb = sl::boundingBox(item.transformedShape());
// Calculate the full bounding box of the pile with the candidate item
- pile.emplace_back(item.transformedShape());
- auto fullbb = ShapeLike::boundingBox(pile);
- pile.pop_back();
+ auto fullbb = boundingBox(pilebb, ibb);
// The bounding box of the big items (they will accumulate in the center
// of the pile
@@ -157,19 +159,11 @@ objfunc(const PointImpl& bincenter,
boost::geometry::convert(boostbb, bigbb);
}
- // The size indicator of the candidate item. This is not the area,
- // but almost...
- double item_normarea = normarea(item.area());
-
// Will hold the resulting score
double score = 0;
- if(item_normarea > BIG_ITEM_TRESHOLD) {
+ if(isBig(item.area()) || spatindex.empty()) {
// This branch is for the bigger items..
- // Here we will use the closest point of the item bounding box to
- // the already arranged pile. So not the bb center nor the a choosen
- // corner but whichever is the closest to the center. This will
- // prevent some unwanted strange arrangements.
auto minc = ibb.minCorner(); // bottom left corner
auto maxc = ibb.maxCorner(); // top right corner
@@ -190,48 +184,68 @@ objfunc(const PointImpl& bincenter,
// The smalles distance from the arranged pile center:
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
+ auto bindist = pl::distance(ibb.center(), bincenter) / norm;
+ dist = 0.8*dist + 0.2*bindist;
// Density is the pack density: how big is the arranged pile
- auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
-
- // Prepare a variable for the alignment score.
- // This will indicate: how well is the candidate item aligned with
- // its neighbors. We will check the aligment with all neighbors and
- // return the score for the best alignment. So it is enough for the
- // candidate to be aligned with only one item.
- auto alignment_score = std::numeric_limits<double>::max();
-
- auto& trsh = item.transformedShape();
-
- auto querybb = item.boundingBox();
+ double density = 0;
+
+ if(remaining.empty()) {
+
+ auto mp = merged_pile;
+ mp.emplace_back(item.transformedShape());
+ auto chull = sl::convexHull(mp);
+
+ placers::EdgeCache<PolygonImpl> ec(chull);
+
+ double circ = ec.circumference() / norm;
+ double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm;
+ score = 0.5*circ + 0.5*bcirc;
+
+ } else {
+ // Prepare a variable for the alignment score.
+ // This will indicate: how well is the candidate item aligned with
+ // its neighbors. We will check the alignment with all neighbors and
+ // return the score for the best alignment. So it is enough for the
+ // candidate to be aligned with only one item.
+ auto alignment_score = 1.0;
+
+ density = std::sqrt((fullbb.width() / norm )*
+ (fullbb.height() / norm));
+ auto querybb = item.boundingBox();
+
+ // Query the spatial index for the neighbors
+ std::vector<SpatElement> result;
+ result.reserve(spatindex.size());
+ if(isBig(item.area())) {
+ spatindex.query(bgi::intersects(querybb),
+ std::back_inserter(result));
+ } else {
+ smalls_spatindex.query(bgi::intersects(querybb),
+ std::back_inserter(result));
+ }
- // Query the spatial index for the neigbours
- std::vector<SpatElement> result;
- spatindex.query(bgi::intersects(querybb), std::back_inserter(result));
+ for(auto& e : result) { // now get the score for the best alignment
+ auto idx = e.second;
+ Item& p = items[idx];
+ auto parea = p.area();
+ if(std::abs(1.0 - parea/item.area()) < 1e-6) {
+ auto bb = boundingBox(p.boundingBox(), ibb);
+ auto bbarea = bb.area();
+ auto ascore = 1.0 - (item.area() + parea)/bbarea;
- for(auto& e : result) { // now get the score for the best alignment
- auto idx = e.second;
- auto& p = pile[idx];
- auto parea = areacache[idx];
- auto bb = sl::boundingBox(sl::Shapes<PolygonImpl>{p, trsh});
- auto bbarea = bb.area();
- auto ascore = 1.0 - (item.area() + parea)/bbarea;
+ if(ascore < alignment_score) alignment_score = ascore;
+ }
+ }
- if(ascore < alignment_score) alignment_score = ascore;
+ // The final mix of the score is the balance between the distance
+ // from the full pile center, the pack density and the
+ // alignment with the neighbors
+ if(result.empty())
+ score = 0.5 * dist + 0.5 * density;
+ else
+ score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score;
}
-
- // The final mix of the score is the balance between the distance
- // from the full pile center, the pack density and the
- // alignment with the neigbours
- auto C = 0.33;
- score = C * dist + C * density + C * alignment_score;
-
- } else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) {
- // If there are no big items, only small, we should consider the
- // density here as well to not get silly results
- auto bindist = pl::distance(ibb.center(), bincenter) / norm;
- auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
- score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density;
} else {
// Here there are the small items that should be placed around the
// already processed bigger items.
@@ -259,7 +273,9 @@ void fillConfig(PConf& pcfg) {
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
- pcfg.accuracy = 0.6f;
+ pcfg.accuracy = 0.65f;
+
+ pcfg.parallel = true;
}
template<class TBin>
@@ -268,31 +284,65 @@ class AutoArranger {};
template<class TBin>
class _ArrBase {
protected:
- using Placer = strategies::_NofitPolyPlacer<PolygonImpl, TBin>;
+
+ using Placer = TPacker<TBin>;
using Selector = FirstFitSelection;
- using Packer = Arranger<Placer, Selector>;
+ using Packer = Nester<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>;
- using Pile = ShapeLike::Shapes<PolygonImpl>;
+ using Pile = sl::Shapes<PolygonImpl>;
Packer pck_;
PConfig pconf_; // Placement configuration
double bin_area_;
- std::vector<double> areacache_;
SpatIndex rtree_;
+ SpatIndex smallsrtree_;
+ double norm_;
+ Pile merged_pile_;
+ Box pilebb_;
+ ItemGroup remaining_;
+ ItemGroup items_;
public:
_ArrBase(const TBin& bin, Distance dist,
std::function<void(unsigned)> progressind):
- pck_(bin, dist), bin_area_(ShapeLike::area<PolygonImpl>(bin))
+ pck_(bin, dist), bin_area_(sl::area(bin)),
+ norm_(std::sqrt(sl::area(bin)))
{
fillConfig(pconf_);
+
+ pconf_.before_packing =
+ [this](const Pile& merged_pile, // merged pile
+ const ItemGroup& items, // packed items
+ const ItemGroup& remaining) // future items to be packed
+ {
+ items_ = items;
+ merged_pile_ = merged_pile;
+ remaining_ = remaining;
+
+ pilebb_ = sl::boundingBox(merged_pile);
+
+ rtree_.clear();
+ smallsrtree_.clear();
+
+ // We will treat big items (compared to the print bed) differently
+ auto isBig = [this](double a) {
+ return a/bin_area_ > BIG_ITEM_TRESHOLD ;
+ };
+
+ for(unsigned idx = 0; idx < items.size(); ++idx) {
+ Item& itm = items[idx];
+ if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx});
+ smallsrtree_.insert({itm.boundingBox(), idx});
+ }
+ };
+
pck_.progressIndicator(progressind);
}
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
- areacache_.clear();
- return pck_.arrangeIndexed(std::forward<Args>(args)...);
+ rtree_.clear();
+ return pck_.executeIndexed(std::forward<Args>(args)...);
}
};
@@ -304,22 +354,71 @@ public:
std::function<void(unsigned)> progressind):
_ArrBase<Box>(bin, dist, progressind)
{
- pconf_.object_function = [this, bin] (
- Pile& pile,
- const Item &item,
- double pile_area,
- double norm,
- double /*penality*/) {
-
- auto result = objfunc(bin.center(), bin_area_, pile,
- pile_area, item, norm, areacache_, rtree_);
+
+ pconf_.object_function = [this, bin] (const Item &item) {
+
+ auto result = objfunc(bin.center(),
+ merged_pile_,
+ pilebb_,
+ items_,
+ item,
+ bin_area_,
+ norm_,
+ rtree_,
+ smallsrtree_,
+ remaining_);
+
double score = std::get<0>(result);
auto& fullbb = std::get<1>(result);
- auto wdiff = fullbb.width() - bin.width();
- auto hdiff = fullbb.height() - bin.height();
- if(wdiff > 0) score += std::pow(wdiff, 2) / norm;
- if(hdiff > 0) score += std::pow(hdiff, 2) / norm;
+ double miss = Placer::overfit(fullbb, bin);
+ miss = miss > 0? miss : 0;
+ score += miss*miss;
+
+ return score;
+ };
+
+ pck_.configure(pconf_);
+ }
+};
+
+using lnCircle = libnest2d::_Circle<libnest2d::PointImpl>;
+
+template<>
+class AutoArranger<lnCircle>: public _ArrBase<lnCircle> {
+public:
+
+ AutoArranger(const lnCircle& bin, Distance dist,
+ std::function<void(unsigned)> progressind):
+ _ArrBase<lnCircle>(bin, dist, progressind) {
+
+ pconf_.object_function = [this, &bin] (const Item &item) {
+
+ auto result = objfunc(bin.center(),
+ merged_pile_,
+ pilebb_,
+ items_,
+ item,
+ bin_area_,
+ norm_,
+ rtree_,
+ smallsrtree_,
+ remaining_);
+
+ double score = std::get<0>(result);
+
+ auto isBig = [this](const Item& itm) {
+ return itm.area()/bin_area_ > BIG_ITEM_TRESHOLD ;
+ };
+
+ if(isBig(item)) {
+ auto mp = merged_pile_;
+ mp.push_back(item.transformedShape());
+ auto chull = sl::convexHull(mp);
+ double miss = Placer::overfit(chull, bin);
+ if(miss < 0) miss = 0;
+ score += miss*miss;
+ }
return score;
};
@@ -335,27 +434,21 @@ public:
std::function<void(unsigned)> progressind):
_ArrBase<PolygonImpl>(bin, dist, progressind)
{
- pconf_.object_function = [this, &bin] (
- Pile& pile,
- const Item &item,
- double pile_area,
- double norm,
- double /*penality*/) {
-
- auto binbb = ShapeLike::boundingBox(bin);
- auto result = objfunc(binbb.center(), bin_area_, pile,
- pile_area, item, norm, areacache_, rtree_);
+ pconf_.object_function = [this, &bin] (const Item &item) {
+
+ auto binbb = sl::boundingBox(bin);
+ auto result = objfunc(binbb.center(),
+ merged_pile_,
+ pilebb_,
+ items_,
+ item,
+ bin_area_,
+ norm_,
+ rtree_,
+ smallsrtree_,
+ remaining_);
double score = std::get<0>(result);
- pile.emplace_back(item.transformedShape());
- auto chull = ShapeLike::convexHull(pile);
- pile.pop_back();
-
- // If it does not fit into the print bed we will beat it with a
- // large penality. If we would not do this, there would be only one
- // big pile that doesn't care whether it fits onto the print bed.
- if(!Placer::wouldFit(chull, bin)) score += norm;
-
return score;
};
@@ -370,15 +463,18 @@ public:
AutoArranger(Distance dist, std::function<void(unsigned)> progressind):
_ArrBase<Box>(Box(0, 0), dist, progressind)
{
- this->pconf_.object_function = [this] (
- Pile& pile,
- const Item &item,
- double pile_area,
- double norm,
- double /*penality*/) {
-
- auto result = objfunc({0, 0}, 0, pile, pile_area,
- item, norm, areacache_, rtree_);
+ this->pconf_.object_function = [this] (const Item &item) {
+
+ auto result = objfunc({0, 0},
+ merged_pile_,
+ pilebb_,
+ items_,
+ item,
+ 0,
+ norm_,
+ rtree_,
+ smallsrtree_,
+ remaining_);
return std::get<0>(result);
};
@@ -440,16 +536,104 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
return ret;
}
-enum BedShapeHint {
+class Circle {
+ Point center_;
+ double radius_;
+public:
+
+ inline Circle(): center_(0, 0), radius_(std::nan("")) {}
+ inline Circle(const Point& c, double r): center_(c), radius_(r) {}
+
+ inline double radius() const { return radius_; }
+ inline const Point& center() const { return center_; }
+ inline operator bool() { return !std::isnan(radius_); }
+};
+
+enum class BedShapeType {
BOX,
CIRCLE,
IRREGULAR,
WHO_KNOWS
};
-BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) {
+struct BedShapeHint {
+ BedShapeType type;
+ /*union*/ struct { // I know but who cares...
+ Circle circ;
+ BoundingBox box;
+ Polyline polygon;
+ } shape;
+};
+
+BedShapeHint bedShape(const Polyline& bed) {
+ static const double E = 10/SCALING_FACTOR;
+
+ BedShapeHint ret;
+
+ auto width = [](const BoundingBox& box) {
+ return box.max.x - box.min.x;
+ };
+
+ auto height = [](const BoundingBox& box) {
+ return box.max.y - box.min.y;
+ };
+
+ auto area = [&width, &height](const BoundingBox& box) {
+ double w = width(box);
+ double h = height(box);
+ return w*h;
+ };
+
+ auto poly_area = [](Polyline p) {
+ Polygon pp; pp.points.reserve(p.points.size() + 1);
+ pp.points = std::move(p.points);
+ pp.points.emplace_back(pp.points.front());
+ return std::abs(pp.area());
+ };
+
+
+ auto bb = bed.bounding_box();
+
+ auto isCircle = [bb](const Polyline& polygon) {
+ auto center = bb.center();
+ std::vector<double> vertex_distances;
+ double avg_dist = 0;
+ for (auto pt: polygon.points)
+ {
+ double distance = center.distance_to(pt);
+ vertex_distances.push_back(distance);
+ avg_dist += distance;
+ }
+
+ avg_dist /= vertex_distances.size();
+
+ Circle ret(center, avg_dist);
+ for (auto el: vertex_distances)
+ {
+ if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
+ ret = Circle();
+ break;
+ }
+
+ return ret;
+ };
+
+ auto parea = poly_area(bed);
+
+ if( (1.0 - parea/area(bb)) < 1e-3 ) {
+ ret.type = BedShapeType::BOX;
+ ret.shape.box = bb;
+ }
+ else if(auto c = isCircle(bed)) {
+ ret.type = BedShapeType::CIRCLE;
+ ret.shape.circ = c;
+ } else {
+ ret.type = BedShapeType::IRREGULAR;
+ ret.shape.polygon = bed;
+ }
+
// Determine the bed shape by hand
- return BOX;
+ return ret;
}
void applyResult(
@@ -525,7 +709,10 @@ bool arrange(Model &model, coordf_t min_obj_distance,
});
IndexedPackGroup result;
- BoundingBox bbb(bed.points);
+
+ if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed);
+
+ BoundingBox bbb(bed);
auto binbb = Box({
static_cast<libnest2d::Coord>(bbb.min.x),
@@ -536,8 +723,8 @@ bool arrange(Model &model, coordf_t min_obj_distance,
static_cast<libnest2d::Coord>(bbb.max.y)
});
- switch(bedhint) {
- case BOX: {
+ switch(bedhint.type) {
+ case BedShapeType::BOX: {
// Create the arranger for the box shaped bed
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind);
@@ -547,16 +734,22 @@ bool arrange(Model &model, coordf_t min_obj_distance,
result = arrange(shapes.begin(), shapes.end());
break;
}
- case CIRCLE:
+ case BedShapeType::CIRCLE: {
+
+ auto c = bedhint.shape.circ;
+ auto cc = lnCircle({c.center().x, c.center().y} , c.radius());
+
+ AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind);
+ result = arrange(shapes.begin(), shapes.end());
break;
- case IRREGULAR:
- case WHO_KNOWS: {
+ }
+ case BedShapeType::IRREGULAR:
+ case BedShapeType::WHO_KNOWS: {
+
using P = libnest2d::PolygonImpl;
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
- P irrbed = ShapeLike::create<PolygonImpl>(std::move(ctour));
-
-// std::cout << ShapeLike::toString(irrbed) << std::endl;
+ P irrbed = sl::create<PolygonImpl>(std::move(ctour));
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind);
@@ -567,6 +760,8 @@ bool arrange(Model &model, coordf_t min_obj_distance,
}
};
+ if(result.empty()) return false;
+
if(first_bin_only) {
applyResult(result.front(), 0, shapemap);
} else {
diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp
index d97b6b1aa..87b031851 100644
--- a/xs/src/libslic3r/MultiPoint.hpp
+++ b/xs/src/libslic3r/MultiPoint.hpp
@@ -34,8 +34,10 @@ public:
Point first_point() const;
virtual Point last_point() const = 0;
virtual Lines lines() const = 0;
+ size_t size() const { return points.size(); }
+ bool empty() const { return points.empty(); }
double length() const;
- bool is_valid() const { return this->points.size() >= 2; }
+ bool is_valid() const { return this->points.size() >= 2; }
int find_point(const Point &point) const;
bool has_boundary_point(const Point &point) const;
@@ -105,6 +107,23 @@ extern BoundingBox get_extents(const MultiPoint &mp);
extern BoundingBox get_extents_rotated(const std::vector<Point> &points, double angle);
extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
+inline double length(const Points &pts) {
+ double total = 0;
+ if (! pts.empty()) {
+ auto it = pts.begin();
+ for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev)
+ total += it->distance_to(*it_prev);
+ }
+ return total;
+}
+
+inline double area(const Points &polygon) {
+ double area = 0.;
+ for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++)
+ area += double(polygon[j].x + polygon[i].x) * double(polygon[i].y - polygon[j].y);
+ return area;
+}
+
} // namespace Slic3r
#endif
diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp
index 11e9fd12a..4801ea654 100644
--- a/xs/src/libslic3r/PerimeterGenerator.cpp
+++ b/xs/src/libslic3r/PerimeterGenerator.cpp
@@ -722,12 +722,10 @@ get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPoly
}
-int id = 0;
ExtrusionLoop
PerimeterGenerator::_extrude_and_cut_loop(const PerimeterGeneratorLoop &loop, const Point entry_point, const Line &direction) const
{
- const int my_id = ++id;
bool need_to_reverse = false;
Polyline initial_polyline;
const coord_t dist_cut = (coord_t)scale_(this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1));
@@ -772,7 +770,6 @@ PerimeterGenerator::_extrude_and_cut_loop(const PerimeterGeneratorLoop &loop, co
//std::vector<PerimeterPolylineNode> myPolylines;
ExtrusionLoop my_loop;
- ExtrusionLoop svg_out(elrDefault);
//overhang / notoverhang
{
bool is_external = loop.is_external();
@@ -886,8 +883,18 @@ ExtrusionLoop
PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, const PerimeterGeneratorLoops &children, const Point entry_point) const
{
//std::cout << " === ==== _traverse_and_join_loops ==== ===\n";
+ // other perimeters
+ //this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
+ //coord_t perimeter_width = this->perimeter_flow.scaled_width();
+ const coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
- const coord_t dist_cut = (coord_t)scale_(this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1));
+ //// external perimeters
+ //this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
+ //coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
+ const coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
+ //coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow);
+
+ //const coord_t dist_cut = (coord_t)scale_(this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1));
//TODO change this->external_perimeter_flow.scaled_width() if it's the first one!
const coord_t max_width_extrusion = this->perimeter_flow.scaled_width();
ExtrusionLoop my_loop = _extrude_and_cut_loop(loop, entry_point);
@@ -900,20 +907,13 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
//Polylines myPolylines = { myPolyline };
//iterate on each point ot find the best place to go into the child
vector<PerimeterGeneratorLoop> childs = children;
- int child_idx = 0;
while (!childs.empty()) {
- child_idx++;
PerimeterIntersectionPoint nearest = get_nearest_point(childs, my_loop, this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_width()* 0.8);
if (nearest.idx_children == (size_t)-1) {
//return ExtrusionEntityCollection();
break;
} else {
- stringstream log_bef;
const PerimeterGeneratorLoop &child = childs[nearest.idx_children];
- log_bef << "dist travel @search swap is : " << unscale(nearest.outter_best.distance_to(nearest.child_best))
- << " from " << unscale(nearest.outter_best.x) << ":" << unscale(nearest.outter_best.y)
- << " to " << unscale(nearest.child_best.x) << ":" << unscale(nearest.child_best.y)
- << "\n";
//std::cout << "c." << child_idx << " === i have " << my_loop.paths.size() << " paths" << " == cut_path_is_ccw size " << path_is_ccw.size() << "\n";
//std::cout << "change to child " << nearest.idx_children << " @ " << unscale(nearest.outter_best.x) << ":" << unscale(nearest.outter_best.y)
// << ", idxpolyline = " << nearest.idx_polyline_outter << "\n";
@@ -948,7 +948,10 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
break;
}
}
- if (idx_before == (size_t)-1) std::cout << "ERROR: idx_before can't be finded\n";
+ if (idx_before == (size_t)-1) {
+ std::cout << "ERROR: idx_before can't be finded\n";
+ continue;
+ }
Points &my_polyline_points = outer_start->polyline.points;
my_polyline_points.erase(my_polyline_points.begin() + idx_before + 1, my_polyline_points.end());
@@ -960,10 +963,6 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
outer_end->polyline.points.erase(outer_end->polyline.points.begin()+1, outer_end->polyline.points.end());
outer_end->polyline.points.insert(outer_end->polyline.points.begin(), nearest.outter_best);
}
- log_bef << "dist travel before child loop get is : " << unscale(outer_start->polyline.points.back().distance_to(nearest.child_best))
- << " from " << unscale(outer_start->polyline.points.back().x) << ":" << unscale(outer_start->polyline.points.back().y)
- << " to " << unscale(nearest.child_best.x) << ":" << unscale(nearest.child_best.y)
- << "\n";
Polyline to_reduce = outer_start->polyline;
if (to_reduce.points.size()>1) to_reduce.clip_end(SCALED_RESOLUTION);
deletedSection.a = to_reduce.points.back();
@@ -974,8 +973,13 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
//get the inner loop to connect to us.
ExtrusionLoop child_loop = _extrude_and_cut_loop(child, nearest.child_best, deletedSection);
- //FIXME: if child_loophas no point or 1 point or not enough space !!!!!!!
+ const coord_t inner_child_spacing = child.is_external() ? ext_perimeter_spacing : perimeter_spacing;
+ const coord_t outer_start_spacing = scale_(outer_start->width - outer_start->height * (1. - 0.25 * PI));
+ const coord_t outer_end_spacing = scale_(outer_end->width - outer_end->height * (1. - 0.25 * PI));
+
+ //FIXME: if child_loop has no point or 1 point or not enough space !!!!!!!
const size_t child_paths_size = child_loop.paths.size();
+ if (child_paths_size == 0) continue;
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, child_loop.paths.begin(), child_loop.paths.end());
for (size_t i = 0; i < child_paths_size; i++) path_is_ccw.insert(path_is_ccw.begin() + nearest.idx_polyline_outter + 1, !cut_path_is_ccw);
@@ -984,23 +988,27 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
outer_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size + 1];
ExtrusionPath *inner_start = &my_loop.paths[nearest.idx_polyline_outter+1];
ExtrusionPath *inner_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size];
- log_bef << "dist travel before trim is : " << unscale(outer_start->polyline.points.back().distance_to(inner_start->polyline.points.front()))
- << " from " << unscale(outer_start->polyline.points.back().x) << ":" << unscale(outer_start->polyline.points.back().y)
- << " to " << unscale(inner_start->polyline.points.front().x) << ":" << unscale(inner_start->polyline.points.front().y)
- << "\n";
//TRIM
//choose trim direction
- if (outer_start->polyline.points.size() == 1) {
- outer_end->polyline.clip_start(dist_cut);
- my_loop.paths[nearest.idx_polyline_outter + child_paths_size ].polyline.clip_end(dist_cut);
+ if (outer_start->polyline.points.size() == 1 && outer_end->polyline.points.size() == 1) {
+ //do nothing
+ } else if (outer_start->polyline.points.size() == 1) {
+ outer_end->polyline.clip_start(outer_end_spacing);
+ if (inner_end->polyline.length() > inner_child_spacing)
+ inner_end->polyline.clip_end(inner_child_spacing);
+ else
+ inner_end->polyline.clip_end(inner_end->polyline.length() / 2);
} else if (outer_end->polyline.points.size() == 1) {
- outer_start->polyline.clip_end(dist_cut);
- inner_start->polyline.clip_start(dist_cut);
+ outer_start->polyline.clip_end(outer_start_spacing);
+ if (inner_start->polyline.length() > inner_child_spacing)
+ inner_start->polyline.clip_start(inner_child_spacing);
+ else
+ inner_start->polyline.clip_start(inner_start->polyline.length()/2);
} else {
coord_t length_poly_1 = outer_start->polyline.length();
coord_t length_poly_2 = outer_end->polyline.length();
- coord_t length_trim_1 = dist_cut / 2;
- coord_t length_trim_2 = dist_cut / 2;
+ coord_t length_trim_1 = outer_start_spacing / 2;
+ coord_t length_trim_2 = outer_end_spacing / 2;
if (length_poly_1 < length_trim_1) {
length_trim_2 = length_trim_1 + length_trim_2 - length_poly_1;
}
@@ -1020,8 +1028,8 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
length_poly_1 = inner_start->polyline.length();
length_poly_2 = inner_end->polyline.length();
- length_trim_1 = dist_cut / 2;
- length_trim_2 = dist_cut / 2;
+ length_trim_1 = inner_child_spacing / 2;
+ length_trim_2 = inner_child_spacing / 2;
if (length_poly_1 < length_trim_1) {
length_trim_2 = length_trim_1 + length_trim_2 - length_poly_1;
}
@@ -1046,10 +1054,6 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
//last check to see if we need a reverse
{
- log_bef << "dist travel before swap is : " << unscale(outer_start->polyline.points.back().distance_to(inner_start->polyline.points.front()))
- << " from " << unscale(outer_start->polyline.points.back().x) << ":" << unscale(outer_start->polyline.points.back().y)
- << " to " << unscale(inner_start->polyline.points.front().x) << ":" << unscale(inner_start->polyline.points.front().y)
- << "\n";
Line l1(outer_start->polyline.points.back(), inner_start->polyline.points.front());
Line l2(inner_end->polyline.points.back(), outer_end->polyline.points.front());
Point p_inter(0, 0);
@@ -1074,15 +1078,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
ExtrusionPaths travel_path_end;// (ExtrusionRole::erNone, 0, outer_end->width, outer_end->height);
//travel_path_end.extruder_id = -1;
double dist_travel = outer_start->polyline.points.back().distance_to(inner_start->polyline.points.front());
- if (dist_travel > max_width_extrusion * 10) {
- std::cout << "ERROR: dist travel is to high : " << unscale(dist_travel)
- << " from " << unscale(outer_start->polyline.points.back().x) << ":" << unscale(outer_start->polyline.points.back().y)
- << " to " << unscale(inner_start->polyline.points.front().x) << ":" << unscale(inner_start->polyline.points.front().y)
- << "\n";
- std::cout << log_bef.str();
- //std::cout << log.str();
- }
- if (dist_travel > max_width_extrusion*1.5) {
+ if (dist_travel > max_width_extrusion*1.5 && this->config->fill_density.value > 0) {
travel_path_begin.emplace_back(ExtrusionRole::erPerimeter, outer_start->mm3_per_mm, outer_start->width, outer_start->height);
travel_path_begin.emplace_back(ExtrusionRole::erNone, 0, outer_start->width, outer_start->height);
travel_path_begin.emplace_back(ExtrusionRole::erPerimeter, outer_start->mm3_per_mm, outer_start->width, outer_start->height);
@@ -1109,7 +1105,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
} else {
// the path is small enough to extrude all along.
double flow_mult = 1;
- if (dist_travel > max_width_extrusion) {
+ if (dist_travel > max_width_extrusion && this->config->fill_density.value > 0) {
// the path is a bit too long, reduce the extrusion flow.
flow_mult = max_width_extrusion / dist_travel;
}
@@ -1119,7 +1115,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
travel_path_begin[0].polyline.append(inner_start->polyline.points.front());
}
dist_travel = inner_end->polyline.points.back().distance_to(outer_end->polyline.points.front());
- if (dist_travel > max_width_extrusion*1.5) {
+ if (dist_travel > max_width_extrusion*1.5 && this->config->fill_density.value > 0) {
travel_path_end.emplace_back(ExtrusionRole::erPerimeter, outer_end->mm3_per_mm, outer_end->width, outer_end->height);
travel_path_end.emplace_back(ExtrusionRole::erNone, 0, outer_end->width, outer_end->height);
travel_path_end.emplace_back(ExtrusionRole::erPerimeter, outer_end->mm3_per_mm, outer_end->width, outer_end->height);
@@ -1146,7 +1142,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
} else {
// the path is small enough to extrude all along.
double flow_mult = 1;
- if (dist_travel > max_width_extrusion) {
+ if (dist_travel > max_width_extrusion && this->config->fill_density.value > 0) {
// the path is a bit too long, reduce the extrusion flow.
flow_mult = max_width_extrusion / dist_travel;
}
diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
index ff1f3ecc9..5a2ed2718 100644
--- a/xs/src/libslic3r/Point.hpp
+++ b/xs/src/libslic3r/Point.hpp
@@ -152,6 +152,24 @@ public:
m_map.emplace(std::make_pair(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2), std::move(value)));
}
+ // Erase a data point equal to value. (ValueType has to declare the operator==).
+ // Returns true if the data point equal to value was found and removed.
+ bool erase(const ValueType &value) {
+ const Point *pt = m_point_accessor(value);
+ if (pt != nullptr) {
+ // Range of fragment starts around grid_corner, close to pt.
+ auto range = m_map.equal_range(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2));
+ // Remove the first item.
+ for (auto it = range.first; it != range.second; ++ it) {
+ if (it->second == value) {
+ m_map.erase(it);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
// Return a pair of <ValueType*, distance_squared>
std::pair<const ValueType*, double> find(const Point &pt) {
// Iterate over 4 closest grid cells around pt,
@@ -179,7 +197,7 @@ public:
}
}
}
- return (value_min != nullptr && dist_min < coordf_t(m_search_radius * m_search_radius)) ?
+ return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ?
std::make_pair(value_min, dist_min) :
std::make_pair(nullptr, std::numeric_limits<double>::max());
}
diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp
index 1a02d78b7..2d624e71a 100644
--- a/xs/src/libslic3r/Polygon.hpp
+++ b/xs/src/libslic3r/Polygon.hpp
@@ -103,6 +103,12 @@ inline void polygons_rotate(Polygons &polys, double angle)
p.rotate(cos_angle, sin_angle);
}
+inline void polygons_reverse(Polygons &polys)
+{
+ for (Polygon &p : polys)
+ p.reverse();
+}
+
inline Points to_points(const Polygon &poly)
{
return poly.points;
diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp
index c94421922..53f59c026 100644
--- a/xs/src/libslic3r/Polyline.cpp
+++ b/xs/src/libslic3r/Polyline.cpp
@@ -194,23 +194,19 @@ Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
}
}
-bool
-Polyline::is_straight() const
+bool Polyline::is_straight() const
{
- /* Check that each segment's direction is equal to the line connecting
- first point and last point. (Checking each line against the previous
- one would cause the error to accumulate.) */
+ // Check that each segment's direction is equal to the line connecting
+ // first point and last point. (Checking each line against the previous
+ // one would cause the error to accumulate.)
double dir = Line(this->first_point(), this->last_point()).direction();
-
- Lines lines = this->lines();
- for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
- if (!line->parallel_to(dir)) return false;
- }
+ for (const auto &line: this->lines())
+ if (! line.parallel_to(dir))
+ return false;
return true;
}
-std::string
-Polyline::wkt() const
+std::string Polyline::wkt() const
{
std::ostringstream wkt;
wkt << "LINESTRING((";
diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp
index 860c49a7a..2935e6450 100644
--- a/xs/src/libslic3r/Polyline.hpp
+++ b/xs/src/libslic3r/Polyline.hpp
@@ -19,6 +19,8 @@ public:
Polyline() {};
Polyline(const Polyline &other) : MultiPoint(other.points) {}
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {}
+ explicit Polyline(const Points &points) : MultiPoint(points) {}
+ explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {}
Polyline& operator=(const Polyline &other) { points = other.points; return *this; }
Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; }
static Polyline new_scale(std::vector<Pointf> points) {
@@ -79,8 +81,8 @@ extern BoundingBox get_extents(const Polylines &polylines);
inline double total_length(const Polylines &polylines) {
double total = 0;
- for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
- total += it->length();
+ for (const Polyline &pl : polylines)
+ total += pl.length();
return total;
}
diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
index 55dc4db39..3f91d4487 100644
--- a/xs/src/libslic3r/Print.cpp
+++ b/xs/src/libslic3r/Print.cpp
@@ -363,9 +363,12 @@ void Print::add_model_object(ModelObject* model_object, int idx)
// Invalidate all print steps.
this->invalidate_all_steps();
- for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
+ size_t volume_id = 0;
+ for (const ModelVolume *volume : model_object->volumes) {
+ if (! volume->is_model_part() && ! volume->is_modifier())
+ continue;
// Get the config applied to this volume.
- PrintRegionConfig config = this->_region_config_from_model_volume(*model_object->volumes[volume_id]);
+ PrintRegionConfig config = this->_region_config_from_model_volume(*volume);
// Find an existing print region with the same config.
size_t region_id = size_t(-1);
for (size_t i = 0; i < this->regions.size(); ++ i)
@@ -380,6 +383,7 @@ void Print::add_model_object(ModelObject* model_object, int idx)
}
// Assign volume to a region.
object->add_region_volume(region_id, volume_id);
+ ++ volume_id;
}
// Apply config to print object.
@@ -854,7 +858,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
ModelVolume *volume = model_object->volumes[volume_id];
//FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned.
- if (! volume->material_id().empty() && ! volume->config.has("extruder"))
+ if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder"))
volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1);
}
}
@@ -1194,6 +1198,9 @@ void Print::_make_wipe_tower()
}
m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
wipe_tower.tool_change((unsigned int)-1, false));
+
+ m_wipe_tower_used_filament = wipe_tower.get_used_filament();
+ m_wipe_tower_number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
}
std::string Print::output_filename()
diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
index bfc058a78..898086e92 100644
--- a/xs/src/libslic3r/Print.hpp
+++ b/xs/src/libslic3r/Print.hpp
@@ -80,7 +80,10 @@ public:
Print* print() { return this->_print; }
Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
+ // Average diameter of nozzles participating on extruding this region.
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
+ // Average diameter of nozzles participating on extruding this region.
+ coordf_t bridging_height_avg(const PrintConfig &print_config) const;
private:
Print* _print;
@@ -214,6 +217,10 @@ public:
bool is_printable() const { return !this->_shifted_copies.empty(); }
+ // Helpers to slice support enforcer / blocker meshes by the support generator.
+ std::vector<ExPolygons> slice_support_enforcers() const;
+ std::vector<ExPolygons> slice_support_blockers() const;
+
private:
Print* _print;
ModelObject* _model_object;
@@ -225,6 +232,7 @@ private:
~PrintObject() {}
std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier);
+ std::vector<ExPolygons> _slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
};
typedef std::vector<PrintObject*> PrintObjectPtrs;
@@ -243,7 +251,7 @@ public:
// TODO: status_cb
std::string estimated_normal_print_time;
std::string estimated_silent_print_time;
- double total_used_filament, total_extruded_volume, total_cost, total_weight;
+ double total_used_filament, total_extruded_volume, total_cost, total_weight, total_wipe_tower_cost, total_wipe_tower_filament;
std::map<size_t, float> filament_stats;
PrintState<PrintStep, psCount> state;
@@ -312,6 +320,8 @@ public:
std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_priming;
std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes;
std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_final_purge;
+ std::vector<float> m_wipe_tower_used_filament;
+ int m_wipe_tower_number_of_toolchanges = -1;
std::string output_filename();
std::string output_filepath(const std::string &path);
diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
index 463dc7eeb..5fd932cc7 100644
--- a/xs/src/libslic3r/PrintConfig.cpp
+++ b/xs/src/libslic3r/PrintConfig.cpp
@@ -1,6 +1,7 @@
#include "PrintConfig.hpp"
#include "I18N.hpp"
+#include <algorithm>
#include <set>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/case_conv.hpp>
@@ -25,16 +26,16 @@ PrintConfigDef::PrintConfigDef()
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
const int max_temp = 1500;
- def = this->add("avoid_crossing_perimeters", coBool);
+ def = this->add("avoid_crossing_perimeters", coBool);
def->label = L("Avoid crossing perimeters");
- def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. "
+ def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. "
"This is mostly useful with Bowden extruders which suffer from oozing. "
"This feature slows down both the print and the G-code generation.");
def->cli = "avoid-crossing-perimeters!";
def->default_value = new ConfigOptionBool(false);
def = this->add("bed_shape", coPoints);
- def->label = L("Bed shape");
+ def->label = L("Bed shape");
def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) };
def = this->add("bed_temperature", coInts);
@@ -91,7 +92,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated "
"automatically. Otherwise the provided angle will be used for all bridges. "
"Use 180° for zero angle.");
- def->sidetext = L("°");
+ def->sidetext = L("°");
def->cli = "bridge-angle=f";
def->min = 0;
def->default_value = new ConfigOptionFloat(0.);
@@ -123,8 +124,8 @@ PrintConfigDef::PrintConfigDef()
"with cooling (use a fan) before tweaking this.");
def->cli = "bridge-flow-ratio=f";
def->min = 0;
- def->max = 2;
- def->default_value = new ConfigOptionFloat(1);
+ def->max = 2;
+ def->default_value = new ConfigOptionFloat(1);
def = this->add("over_bridge_flow_ratio", coFloat);
def->label = L("Above the bridges");
@@ -132,7 +133,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("This factor affects the amount of plastic to overextrude "
"when we are filling on top of a bridge surface."
"With a number >1, we can retrieve a correct z-height "
- "even if the bridged layer has fallen a bit. "
+ "even if the bridged layer has fallen a bit. "
"It's useful if you want to have a nice flat top layer.");
def->cli = "over-bridge-flow-ratio=f";
def->min = 0;
@@ -144,7 +145,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Speed for printing bridges.");
def->sidetext = L("mm/s");
def->cli = "bridge-speed=f";
- def->aliases.push_back("bridge_feed_rate");
+ def->aliases = { "bridge_feed_rate" };
def->min = 0;
def->default_value = new ConfigOptionFloat(60);
@@ -257,7 +258,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Distance used for the auto-arrange feature of the plater.");
def->sidetext = L("mm");
def->cli = "duplicate-distance=f";
- def->aliases.push_back("multiply_distance");
+ def->aliases = { "multiply_distance" };
def->min = 0;
def->default_value = new ConfigOptionFloat(6);
@@ -309,7 +310,7 @@ PrintConfigDef::PrintConfigDef()
def->enum_values.push_back("concentric");
def->enum_values.push_back("hilbertcurve");
def->enum_values.push_back("archimedeanchords");
- def->enum_values.push_back("octagramspiral");
+ def->enum_values.push_back("octagramspiral");
def->enum_values.push_back("smooth");
def->enum_values.push_back("smoothtriple");
def->enum_values.push_back("smoothhilbert");
@@ -318,11 +319,9 @@ PrintConfigDef::PrintConfigDef()
def->enum_labels.push_back(L("Hilbert Curve"));
def->enum_labels.push_back(L("Archimedean Chords"));
def->enum_labels.push_back(L("Octagram Spiral"));
- def->enum_labels.push_back("Ironing");
- //def->enum_labels.push_back("Ironing (triple)");
- //def->enum_labels.push_back("Ironing (hilbert)");
+ def->enum_labels.push_back("Ironing");
// solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern.
- def->aliases.push_back("solid_fill_pattern");
+ def->aliases = { "solid_fill_pattern" };
def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
def = this->add("bottom_fill_pattern", coEnum);
@@ -642,7 +641,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters ");
def->cli = "filament-ramming-parameters=s@";
def->default_value = new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|"
- " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" };
+ " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" };
def = this->add("filament_unload_time", coFloats);
def->label = L("Filament unload time");
@@ -727,7 +726,7 @@ PrintConfigDef::PrintConfigDef()
def->cli = "fill-density=s";
def->min = 0;
def->max = 100;
- /*
+ /*
def->enum_values.push_back("0");
def->enum_values.push_back("5");
def->enum_values.push_back("10");
@@ -756,7 +755,7 @@ PrintConfigDef::PrintConfigDef()
def->enum_labels.push_back("80%");
def->enum_labels.push_back("90%");
def->enum_labels.push_back("100%");
- */
+ */
def->enum_values.push_back("0");
def->enum_values.push_back("4");
def->enum_values.push_back("5.5");
@@ -1028,8 +1027,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Speed for printing the internal fill. Set to zero for auto.");
def->sidetext = L("mm/s");
def->cli = "infill-speed=f";
- def->aliases.push_back("print_feed_rate");
- def->aliases.push_back("infill_feed_rate");
+ def->aliases = { "print_feed_rate", "infill_feed_rate" };
def->min = 0;
def->default_value = new ConfigOptionFloat(80);
@@ -1149,8 +1147,8 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Minimum feedrate when extruding") + " (M205 S)";
def->sidetext = L("mm/s");
def->min = 0;
- def->width = machine_limits_opt_width;
- def->default_value = new ConfigOptionFloats{ 0., 0. };
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats{ 0., 0. };
// M205 T... [mm/sec]
def = this->add("machine_min_travel_rate", coFloats);
@@ -1159,8 +1157,8 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Minimum travel feedrate") + " (M205 T)";
def->sidetext = L("mm/s");
def->min = 0;
- def->width = machine_limits_opt_width;
- def->default_value = new ConfigOptionFloats{ 0., 0. };
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats{ 0., 0. };
// M204 S... [mm/sec^2]
def = this->add("machine_max_acceleration_extruding", coFloats);
@@ -1169,7 +1167,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Maximum acceleration when extruding") + " (M204 S)";
def->sidetext = L("mm/s²");
def->min = 0;
- def->width = machine_limits_opt_width;
+ def->width = machine_limits_opt_width;
def->default_value = new ConfigOptionFloats{ 1500., 1250. };
// M204 T... [mm/sec^2]
@@ -1179,7 +1177,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Maximum acceleration when retracting") + " (M204 T)";
def->sidetext = L("mm/s²");
def->min = 0;
- def->width = machine_limits_opt_width;
+ def->width = machine_limits_opt_width;
def->default_value = new ConfigOptionFloats{ 1500., 1250. };
def = this->add("max_fan_speed", coInts);
@@ -1423,7 +1421,7 @@ PrintConfigDef::PrintConfigDef()
def->category = L("Extruders");
def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1.");
def->cli = "perimeter-extruder=i";
- def->aliases.push_back("perimeters_extruder");
+ def->aliases = { "perimeters_extruder" };
def->min = 1;
def->default_value = new ConfigOptionInt(1);
@@ -1436,7 +1434,7 @@ PrintConfigDef::PrintConfigDef()
"If expressed as percentage (for example 200%) it will be computed over layer height.");
def->sidetext = L("mm or % (leave 0 for default)");
def->cli = "perimeter-extrusion-width=s";
- def->aliases.push_back("perimeters_extrusion_width");
+ def->aliases = { "perimeters_extrusion_width" };
def->default_value = new ConfigOptionFloatOrPercent(0, false);
def = this->add("perimeter_speed", coFloat);
@@ -1445,7 +1443,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto.");
def->sidetext = L("mm/s");
def->cli = "perimeter-speed=f";
- def->aliases.push_back("perimeter_feed_rate");
+ def->aliases = { "perimeter_feed_rate" };
def->min = 0;
def->default_value = new ConfigOptionFloat(60);
@@ -1458,7 +1456,7 @@ PrintConfigDef::PrintConfigDef()
"if the Extra Perimeters option is enabled.");
def->sidetext = L("(minimum)");
def->cli = "perimeters=i";
- def->aliases.push_back("perimeter_offsets");
+ def->aliases = { "perimeter_offsets" };
def->min = 0;
def->default_value = new ConfigOptionInt(3);
@@ -1472,8 +1470,8 @@ PrintConfigDef::PrintConfigDef()
def->gui_flags = "serialized";
def->multiline = true;
def->full_width = true;
- def->height = 60;
- def->default_value = new ConfigOptionStrings();
+ def->height = 60;
+ def->default_value = new ConfigOptionStrings();
def = this->add("printer_model", coString);
def->label = L("Printer type");
@@ -1793,7 +1791,7 @@ PrintConfigDef::PrintConfigDef()
def->sidetext = L("mm/s or %");
def->cli = "solid-infill-speed=s";
def->ratio_over = "infill_speed";
- def->aliases.push_back("solid_infill_feed_rate");
+ def->aliases = { "solid_infill_feed_rate" };
def->min = 0;
def->default_value = new ConfigOptionFloatOrPercent(20, false);
@@ -1819,7 +1817,7 @@ PrintConfigDef::PrintConfigDef()
def->label = L("Temperature variation");
def->tooltip = L("Temperature difference to be applied when an extruder is not active. "
"Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped.");
- def->sidetext = "∆°C";
+ def->sidetext = "∆°C";
def->cli = "standby-temperature-delta=i";
def->min = -max_temp;
def->max = max_temp;
@@ -1875,6 +1873,14 @@ PrintConfigDef::PrintConfigDef()
def->cli = "support-material!";
def->default_value = new ConfigOptionBool(false);
+ def = this->add("support_material_auto", coBool);
+ def->label = L("Auto generated supports");
+ def->category = L("Support material");
+ def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\
+ " If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only.");
+ def->cli = "support-material-auto!";
+ def->default_value = new ConfigOptionBool(true);
+
def = this->add("support_material_xy_spacing", coFloatOrPercent);
def->label = L("XY separation between an object and its support");
def->category = L("Support material");
@@ -1913,11 +1919,11 @@ PrintConfigDef::PrintConfigDef()
"for the first object layer.");
def->sidetext = L("mm");
def->cli = "support-material-contact-distance=f";
- def->min = 0;
+// def->min = 0;
def->enum_values.push_back("0");
def->enum_values.push_back("0.2");
- def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str());
- def->enum_labels.push_back((boost::format("0.2 (%1%)") % L("detachable")).str());
+ def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str());
+ def->enum_labels.push_back((boost::format("0.2 (%1%)") % L("detachable")).str());
def->default_value = new ConfigOptionFloat(0.2);
def = this->add("support_material_enforce_layers", coInt);
@@ -2006,7 +2012,7 @@ PrintConfigDef::PrintConfigDef()
def->enum_values.push_back("rectilinear");
def->enum_values.push_back("rectilinear-grid");
def->enum_values.push_back("honeycomb");
- def->enum_labels.push_back(L("Rectilinear"));
+ def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Rectilinear grid"));
def->enum_labels.push_back(L("Honeycomb"));
def->default_value = new ConfigOptionEnum<SupportMaterialPattern>(smpRectilinear);
@@ -2139,7 +2145,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = L("Speed for travel moves (jumps between distant extrusion points).");
def->sidetext = L("mm/s");
def->cli = "travel-speed=f";
- def->aliases.push_back("travel_feed_rate");
+ def->aliases = { "travel_feed_rate" };
def->min = 1;
def->default_value = new ConfigOptionFloat(130);
@@ -2427,6 +2433,24 @@ std::string DynamicPrintConfig::validate()
return fpc.validate();
}
+size_t DynamicPrintConfig::remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message)
+{
+ size_t n_removed_keys = 0;
+ for (const std::string &key : this->keys()) {
+ if (! default_config.has(key)) {
+ if (removed_keys_message.empty())
+ removed_keys_message = key;
+ else {
+ removed_keys_message += ", ";
+ removed_keys_message += key;
+ }
+ this->erase(key);
+ ++ n_removed_keys;
+ }
+ }
+ return n_removed_keys;
+}
+
double PrintConfig::min_object_distance() const
{
return PrintConfig::min_object_distance(static_cast<const ConfigBase*>(this));
@@ -2507,8 +2531,8 @@ std::string FullPrintConfig::validate()
// --fill-density
if (fabs(this->fill_density.value - 100.) < EPSILON &&
(! print_config_def.get("top_fill_pattern")->has_enum_value(this->fill_pattern.serialize())
- || ! print_config_def.get("bottom_fill_pattern")->has_enum_value(this->fill_pattern.serialize())
- ))
+ || ! print_config_def.get("bottom_fill_pattern")->has_enum_value(this->fill_pattern.serialize())
+ ))
return "The selected fill pattern is not supposed to work at 100% density";
// --infill-every-layers
diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
index 95a21a89c..d2cc3166f 100644
--- a/xs/src/libslic3r/PrintConfig.hpp
+++ b/xs/src/libslic3r/PrintConfig.hpp
@@ -186,6 +186,10 @@ public:
// Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned.
std::string validate();
+ // Remove all keys not in "valid_keys", return number of removed keys and add the list of keys to "removed_keys_message.
+ // valid_keys has to be sorted lexicographically.
+ size_t remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message);
+
// Verify whether the opt_key has not been obsoleted or renamed.
// Both opt_key and value may be modified by handle_legacy().
// If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy().
@@ -341,6 +345,7 @@ public:
ConfigOptionFloatOrPercent extrusion_width;
ConfigOptionFloatOrPercent first_layer_height;
ConfigOptionBool infill_only_where_needed;
+ // Force the generation of solid shells between adjacent materials/volumes.
ConfigOptionBool interface_shells;
ConfigOptionFloat layer_height;
ConfigOptionBool exact_last_layer_height;
@@ -349,6 +354,9 @@ public:
// ConfigOptionFloat seam_preferred_direction;
// ConfigOptionFloat seam_preferred_direction_jitter;
ConfigOptionBool support_material;
+ // Automatic supports (generated based on support_material_threshold).
+ ConfigOptionBool support_material_auto;
+ // Direction of the support pattern (in XY plane).
ConfigOptionFloat support_material_angle;
ConfigOptionBool support_material_buildplate_only;
ConfigOptionFloat support_material_contact_distance;
@@ -358,13 +366,16 @@ public:
ConfigOptionBool support_material_interface_contact_loops;
ConfigOptionInt support_material_interface_extruder;
ConfigOptionInt support_material_interface_layers;
+ // Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
ConfigOptionFloat support_material_interface_spacing;
ConfigOptionFloatOrPercent support_material_interface_speed;
ConfigOptionEnum<SupportMaterialPattern> support_material_pattern;
+ // Spacing between support material lines (the hatching distance).
ConfigOptionFloat support_material_spacing;
ConfigOptionFloat support_material_speed;
ConfigOptionBool support_material_solid_first_layer;
ConfigOptionBool support_material_synchronize_layers;
+ // Overhang angle threshold.
ConfigOptionInt support_material_threshold;
ConfigOptionBool support_material_with_sheath;
ConfigOptionFloatOrPercent support_material_xy_spacing;
@@ -389,6 +400,7 @@ protected:
// OPT_PTR(seam_preferred_direction);
// OPT_PTR(seam_preferred_direction_jitter);
OPT_PTR(support_material);
+ OPT_PTR(support_material_auto);
OPT_PTR(support_material_angle);
OPT_PTR(support_material_buildplate_only);
OPT_PTR(support_material_contact_distance);
@@ -449,6 +461,7 @@ public:
ConfigOptionBool infill_dense;
ConfigOptionEnum<DenseInfillAlgo> infill_dense_algo;
ConfigOptionBool infill_first;
+ // Detect bridging perimeters
ConfigOptionBool overhangs;
ConfigOptionBool no_perimeter_unsupported;
ConfigOptionInt min_perimeter_unsupported;
@@ -456,6 +469,7 @@ public:
ConfigOptionInt perimeter_extruder;
ConfigOptionFloatOrPercent perimeter_extrusion_width;
ConfigOptionFloat perimeter_speed;
+ // Total number of perimeters.
ConfigOptionInt perimeters;
ConfigOptionFloatOrPercent small_perimeter_speed;
ConfigOptionFloat solid_infill_below_area;
@@ -463,6 +477,7 @@ public:
ConfigOptionFloatOrPercent solid_infill_extrusion_width;
ConfigOptionInt solid_infill_every_layers;
ConfigOptionFloatOrPercent solid_infill_speed;
+ // Detect thin walls.
ConfigOptionBool thin_walls;
ConfigOptionFloatOrPercent top_infill_extrusion_width;
ConfigOptionInt top_solid_layers;
diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp
index 3a3423645..4c32a5c78 100644
--- a/xs/src/libslic3r/PrintObject.cpp
+++ b/xs/src/libslic3r/PrintObject.cpp
@@ -181,6 +181,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
steps.emplace_back(posSlice);
} else if (
opt_key == "support_material"
+ || opt_key == "support_material_auto"
|| opt_key == "support_material_angle"
|| opt_key == "support_material_buildplate_only"
|| opt_key == "support_material_enforce_layers"
@@ -1191,7 +1192,7 @@ void PrintObject::discover_vertical_shells()
#if 1
// Intentionally inflate a bit more than how much the region has been shrunk,
// so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill).
- shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare);
+ shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare);
if (shell.empty())
continue;
#else
@@ -1800,29 +1801,62 @@ void PrintObject::_offsetHoles(float hole_delta, LayerRegion *layerm) {
std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier)
{
- std::vector<ExPolygons> layers;
+ std::vector<const ModelVolume*> volumes;
if (region_id < this->region_volumes.size()) {
- std::vector<int> &volumes = this->region_volumes[region_id];
- if (! volumes.empty()) {
- // Compose mesh.
- //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
- TriangleMesh mesh;
- for (int volume_id : volumes) {
- ModelVolume *volume = this->model_object()->volumes[volume_id];
- if (volume->modifier == modifier)
- mesh.merge(volume->mesh);
- }
- if (mesh.stl.stats.number_of_facets > 0) {
- // transform mesh
- // we ignore the per-instance transformations currently and only
- // consider the first one
- this->model_object()->instances.front()->transform_mesh(&mesh, true);
- // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
- mesh.translate(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z));
- // perform actual slicing
- TriangleMeshSlicer mslicer(&mesh);
- mslicer.slice(z, &layers);
- }
+ for (int volume_id : this->region_volumes[region_id]) {
+ const ModelVolume *volume = this->model_object()->volumes[volume_id];
+ if (modifier ? volume->is_modifier() : volume->is_model_part())
+ volumes.emplace_back(volume);
+ }
+ }
+ return this->_slice_volumes(z, volumes);
+}
+
+std::vector<ExPolygons> PrintObject::slice_support_enforcers() const
+{
+ std::vector<const ModelVolume*> volumes;
+ for (const ModelVolume *volume : this->model_object()->volumes)
+ if (volume->is_support_enforcer())
+ volumes.emplace_back(volume);
+ std::vector<float> zs;
+ zs.reserve(this->layers.size());
+ for (const Layer *l : this->layers)
+ zs.emplace_back(l->slice_z);
+ return this->_slice_volumes(zs, volumes);
+}
+
+std::vector<ExPolygons> PrintObject::slice_support_blockers() const
+{
+ std::vector<const ModelVolume*> volumes;
+ for (const ModelVolume *volume : this->model_object()->volumes)
+ if (volume->is_support_blocker())
+ volumes.emplace_back(volume);
+ std::vector<float> zs;
+ zs.reserve(this->layers.size());
+ for (const Layer *l : this->layers)
+ zs.emplace_back(l->slice_z);
+ return this->_slice_volumes(zs, volumes);
+}
+
+std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const
+{
+ std::vector<ExPolygons> layers;
+ if (! volumes.empty()) {
+ // Compose mesh.
+ //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
+ TriangleMesh mesh;
+ for (const ModelVolume *v : volumes)
+ mesh.merge(v->mesh);
+ if (mesh.stl.stats.number_of_facets > 0) {
+ // transform mesh
+ // we ignore the per-instance transformations currently and only
+ // consider the first one
+ this->model_object()->instances.front()->transform_mesh(&mesh, true);
+ // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
+ mesh.translate(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z));
+ // perform actual slicing
+ TriangleMeshSlicer mslicer(&mesh);
+ mslicer.slice(z, &layers);
}
}
return layers;
diff --git a/xs/src/libslic3r/PrintRegion.cpp b/xs/src/libslic3r/PrintRegion.cpp
index 4874c71bc..5bb1fffb3 100644
--- a/xs/src/libslic3r/PrintRegion.cpp
+++ b/xs/src/libslic3r/PrintRegion.cpp
@@ -57,4 +57,9 @@ coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
print_config.nozzle_diameter.get_at(this->config.solid_infill_extruder.value - 1)) / 3.;
}
+coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const
+{
+ return this->nozzle_dmr_avg(print_config) * sqrt(this->config.bridge_flow_ratio.value);
+}
+
}
diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp
index 24043c824..2cae04db0 100644
--- a/xs/src/libslic3r/Slicing.cpp
+++ b/xs/src/libslic3r/Slicing.cpp
@@ -226,9 +226,9 @@ std::vector<coordf_t> layer_height_profile_adaptive(
// 1) Initialize the SlicingAdaptive class with the object meshes.
SlicingAdaptive as;
as.set_slicing_parameters(slicing_params);
- for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it)
- if (! (*it)->modifier)
- as.add_mesh(&(*it)->mesh);
+ for (const ModelVolume *volume : volumes)
+ if (volume->is_model_part())
+ as.add_mesh(&volume->mesh);
as.prepare();
// 2) Generate layers using the algorithm of @platsch
diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp
index 15d39fadb..404f3e2c9 100644
--- a/xs/src/libslic3r/SupportMaterial.cpp
+++ b/xs/src/libslic3r/SupportMaterial.cpp
@@ -248,10 +248,10 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
#ifdef SLIC3R_DEBUG
static int iRun = 0;
iRun ++;
- for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it)
+ for (const MyLayer *layer : top_contacts)
Slic3r::SVG::export_expolygons(
- debug_out_path("support-top-contacts-%d-%lf.svg", iRun, (*it)->print_z),
- union_ex((*it)->polygons, false));
+ debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z),
+ union_ex(layer->polygons, false));
#endif /* SLIC3R_DEBUG */
BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts";
@@ -282,7 +282,17 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers(
object, bottom_contacts, top_contacts, layer_storage);
- this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
+// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
+ this->trim_support_layers_by_object(object, top_contacts,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
+
+#ifdef SLIC3R_DEBUG
+ for (const MyLayer *layer : top_contacts)
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z),
+ union_ex(layer->polygons, false));
+#endif
BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers";
@@ -420,29 +430,17 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t
{
// 1) Count the new polygons first.
size_t n_polygons_new = 0;
- for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) {
- const LayerRegion &region = *(*it_region);
- const SurfaceCollection &slices = region.slices;
- for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) {
- const Surface &surface = *it;
+ for (const LayerRegion *region : layer.regions)
+ for (const Surface &surface : region->slices.surfaces)
if (surface.surface_type == surface_type)
n_polygons_new += surface.expolygon.holes.size() + 1;
- }
- }
-
// 2) Collect the new polygons.
Polygons out;
out.reserve(n_polygons_new);
- for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) {
- const LayerRegion &region = *(*it_region);
- const SurfaceCollection &slices = region.slices;
- for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) {
- const Surface &surface = *it;
+ for (const LayerRegion *region : layer.regions)
+ for (const Surface &surface : region->slices.surfaces)
if (surface.surface_type == surface_type)
polygons_append(out, surface.expolygon);
- }
- }
-
return out;
}
@@ -452,17 +450,22 @@ Polygons collect_slices_outer(const Layer &layer)
{
Polygons out;
out.reserve(out.size() + layer.slices.expolygons.size());
- for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it)
- out.push_back(it->contour);
+ for (const ExPolygon &expoly : layer.slices.expolygons)
+ out.emplace_back(expoly.contour);
return out;
}
class SupportGridPattern
{
public:
+ // Achtung! The support_polygons need to be trimmed by trimming_polygons, otherwise
+ // the selection by island_samples (see the island_samples() method) will not work!
SupportGridPattern(
+ // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy)
const Polygons &support_polygons,
- const Polygons &trimming_polygons,
+ // Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons.
+ const Polygons &trimming_polygons,
+ // Grid spacing, given by "support_material_spacing" + m_support_material_flow.spacing()
coordf_t support_spacing,
coordf_t support_angle) :
m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons),
@@ -484,8 +487,21 @@ public:
bbox.align_to_grid(grid_resolution);
m_grid.set_bbox(bbox);
m_grid.create(*m_support_polygons, grid_resolution);
+#if 0
+ if (m_grid.has_intersecting_edges()) {
+ // EdgeGrid fails to produce valid signed distance function for self-intersecting polygons.
+ m_support_polygons_rotated = simplify_polygons(*m_support_polygons);
+ m_support_polygons = &m_support_polygons_rotated;
+ m_grid.set_bbox(bbox);
+ m_grid.create(*m_support_polygons, grid_resolution);
+// assert(! m_grid.has_intersecting_edges());
+ printf("SupportGridPattern: fixing polygons with intersection %s\n",
+ m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED");
+ }
+#endif
m_grid.calculate_sdf();
- // Extract a bounding contour from the grid, trim by the object.
+ // Sample a single point per input support polygon, keep it as a reference to maintain corresponding
+ // polygons if ever these polygons get split into parts by the trimming polygons.
m_island_samples = island_samples(*m_support_polygons);
}
@@ -493,22 +509,25 @@ public:
// and trim the extracted polygons by trimming_polygons.
// Trimming by the trimming_polygons may split the extracted polygons into pieces.
// Remove all the pieces, which do not contain any of the island_samples.
- Polygons extract_support(const coord_t offset_in_grid)
+ Polygons extract_support(const coord_t offset_in_grid, bool fill_holes)
{
// Generate islands, so each island may be tested for overlap with m_island_samples.
- ExPolygons islands = diff_ex(
- m_grid.contours_simplified(offset_in_grid),
- *m_trimming_polygons, false);
+ assert(std::abs(2 * offset_in_grid) < m_grid.resolution());
+#ifdef SLIC3R_DEBUG
+ Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes);
+ ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false);
+#else
+ ExPolygons islands = diff_ex(m_grid.contours_simplified(offset_in_grid, fill_holes), *m_trimming_polygons, false);
+#endif
// Extract polygons, which contain some of the m_island_samples.
Polygons out;
- std::vector<std::pair<Point,bool>> samples_inside;
-
for (ExPolygon &island : islands) {
BoundingBox bbox = get_extents(island.contour);
+ // Samples are sorted lexicographically.
auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), bbox.min - Point(1, 1));
auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), bbox.max + Point(1, 1));
- samples_inside.clear();
+ std::vector<std::pair<Point,bool>> samples_inside;
for (auto it = it_lower; it != it_upper; ++ it)
if (bbox.contains(*it))
samples_inside.push_back(std::make_pair(*it, false));
@@ -549,7 +568,10 @@ public:
bbox.merge(get_extents(islands));
if (!out.empty())
bbox.merge(get_extents(out));
+ if (!support_polygons_simplified.empty())
+ bbox.merge(get_extents(support_polygons_simplified));
SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox);
+ svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f);
svg.draw(islands, "red", 0.5f);
svg.draw(union_ex(out), "green", 0.5f);
svg.draw(union_ex(*m_support_polygons), "blue", 0.5f);
@@ -566,11 +588,127 @@ public:
return out;
}
+#ifdef SLIC3R_DEBUG
+ void serialize(const std::string &path)
+ {
+ FILE *file = ::fopen(path.c_str(), "wb");
+ ::fwrite(&m_support_spacing, 8, 1, file);
+ ::fwrite(&m_support_angle, 8, 1, file);
+ uint32_t n_polygons = m_support_polygons->size();
+ ::fwrite(&n_polygons, 4, 1, file);
+ for (uint32_t i = 0; i < n_polygons; ++ i) {
+ const Polygon &poly = (*m_support_polygons)[i];
+ uint32_t n_points = poly.size();
+ ::fwrite(&n_points, 4, 1, file);
+ for (uint32_t j = 0; j < n_points; ++ j) {
+ const Point &pt = poly.points[j];
+ ::fwrite(&pt.x, sizeof(coord_t), 1, file);
+ ::fwrite(&pt.y, sizeof(coord_t), 1, file);
+ }
+ }
+ n_polygons = m_trimming_polygons->size();
+ ::fwrite(&n_polygons, 4, 1, file);
+ for (uint32_t i = 0; i < n_polygons; ++ i) {
+ const Polygon &poly = (*m_trimming_polygons)[i];
+ uint32_t n_points = poly.size();
+ ::fwrite(&n_points, 4, 1, file);
+ for (uint32_t j = 0; j < n_points; ++ j) {
+ const Point &pt = poly.points[j];
+ ::fwrite(&pt.x, sizeof(coord_t), 1, file);
+ ::fwrite(&pt.y, sizeof(coord_t), 1, file);
+ }
+ }
+ ::fclose(file);
+ }
+
+ static SupportGridPattern deserialize(const std::string &path, int which = -1)
+ {
+ SupportGridPattern out;
+ out.deserialize_(path, which);
+ return out;
+ }
+
+ // Deserialization constructor
+ bool deserialize_(const std::string &path, int which = -1)
+ {
+ FILE *file = ::fopen(path.c_str(), "rb");
+ if (file == nullptr)
+ return false;
+
+ m_support_polygons = &m_support_polygons_deserialized;
+ m_trimming_polygons = &m_trimming_polygons_deserialized;
+
+ ::fread(&m_support_spacing, 8, 1, file);
+ ::fread(&m_support_angle, 8, 1, file);
+ //FIXME
+ //m_support_spacing *= 0.01 / 2;
+ uint32_t n_polygons;
+ ::fread(&n_polygons, 4, 1, file);
+ m_support_polygons_deserialized.reserve(n_polygons);
+ int32_t scale = 1;
+ for (uint32_t i = 0; i < n_polygons; ++ i) {
+ Polygon poly;
+ uint32_t n_points;
+ ::fread(&n_points, 4, 1, file);
+ poly.points.reserve(n_points);
+ for (uint32_t j = 0; j < n_points; ++ j) {
+ coord_t x, y;
+ ::fread(&x, sizeof(coord_t), 1, file);
+ ::fread(&y, sizeof(coord_t), 1, file);
+ poly.points.emplace_back(Point(x * scale, y * scale));
+ }
+ if (which == -1 || which == i)
+ m_support_polygons_deserialized.emplace_back(std::move(poly));
+ printf("Polygon %d, area: %lf\n", i, area(poly.points));
+ }
+ ::fread(&n_polygons, 4, 1, file);
+ m_trimming_polygons_deserialized.reserve(n_polygons);
+ for (uint32_t i = 0; i < n_polygons; ++ i) {
+ Polygon poly;
+ uint32_t n_points;
+ ::fread(&n_points, 4, 1, file);
+ poly.points.reserve(n_points);
+ for (uint32_t j = 0; j < n_points; ++ j) {
+ coord_t x, y;
+ ::fread(&x, sizeof(coord_t), 1, file);
+ ::fread(&y, sizeof(coord_t), 1, file);
+ poly.points.emplace_back(Point(x * scale, y * scale));
+ }
+ m_trimming_polygons_deserialized.emplace_back(std::move(poly));
+ }
+ ::fclose(file);
+
+ m_support_polygons_deserialized = simplify_polygons(m_support_polygons_deserialized, false);
+ //m_support_polygons_deserialized = to_polygons(union_ex(m_support_polygons_deserialized, false));
+
+ // Create an EdgeGrid, initialize it with projection, initialize signed distance field.
+ coord_t grid_resolution = coord_t(scale_(m_support_spacing));
+ BoundingBox bbox = get_extents(*m_support_polygons);
+ bbox.offset(20);
+ bbox.align_to_grid(grid_resolution);
+ m_grid.set_bbox(bbox);
+ m_grid.create(*m_support_polygons, grid_resolution);
+ m_grid.calculate_sdf();
+ // Sample a single point per input support polygon, keep it as a reference to maintain corresponding
+ // polygons if ever these polygons get split into parts by the trimming polygons.
+ m_island_samples = island_samples(*m_support_polygons);
+ return true;
+ }
+
+ const Polygons& support_polygons() const { return *m_support_polygons; }
+ const Polygons& trimming_polygons() const { return *m_trimming_polygons; }
+ const EdgeGrid::Grid& grid() const { return m_grid; }
+
+#endif /* SLIC3R_DEBUG */
+
private:
+ SupportGridPattern() {}
SupportGridPattern& operator=(const SupportGridPattern &rhs);
+#if 0
// Get some internal point of an expolygon, to be used as a representative
// sample to test, whether this island is inside another island.
+ //FIXME this was quick, but not sufficiently robust.
static Point island_sample(const ExPolygon &expoly)
{
// Find the lowest point lexicographically.
@@ -591,7 +729,10 @@ private:
double coef = 20. / sqrt(l2);
return Point(p2.x + coef * v.x, p2.y + coef * v.y);
}
+#endif
+ // Sample one internal point per expolygon.
+ // FIXME this is quite an overkill to calculate a complete offset just to get a single point, but at least it is robust.
static Points island_samples(const ExPolygons &expolygons)
{
Points pts;
@@ -629,23 +770,193 @@ private:
coordf_t m_support_spacing;
Slic3r::EdgeGrid::Grid m_grid;
+ // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding
+ // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons.
Points m_island_samples;
+
+#ifdef SLIC3R_DEBUG
+ // support for deserialization of m_support_polygons, m_trimming_polygons
+ Polygons m_support_polygons_deserialized;
+ Polygons m_trimming_polygons_deserialized;
+#endif /* SLIC3R_DEBUG */
};
-void push_entity_as_polyline(Polylines &push_into, ExtrusionEntity* entity) {
- if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(entity)) {
- push_into.push_back(path->polyline);
- } else if (const ExtrusionMultiPath* multipath = dynamic_cast<const ExtrusionMultiPath*>(entity)) {
- push_into.push_back(multipath->as_polyline());
- } else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(entity)) {
- push_into.push_back(loop->as_polyline());
- } else if (const ExtrusionEntityCollection* coll = dynamic_cast<const ExtrusionEntityCollection*>(entity)) {
- for (ExtrusionEntity* child_entity : coll->entities) {
- push_entity_as_polyline(push_into, child_entity);
+namespace SupportMaterialInternal {
+ static inline bool has_bridging_perimeters(const ExtrusionLoop &loop)
+ {
+ for (const ExtrusionPath &ep : loop.paths)
+ if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty())
+ return ep.size() >= (ep.is_closed() ? 3 : 2);
+ return false;
+ }
+ static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters)
+ {
+ for (const ExtrusionEntity *ee : perimeters.entities) {
+ if (ee->is_collection()) {
+ for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
+ assert(! ee2->is_collection());
+ if (ee2->is_loop())
+ if (has_bridging_perimeters(*static_cast<const ExtrusionLoop*>(ee2)))
+ return true;
+ }
+ } else if (ee->is_loop() && has_bridging_perimeters(*static_cast<const ExtrusionLoop*>(ee)))
+ return true;
+ }
+ return false;
+ }
+ static bool has_bridging_fills(const ExtrusionEntityCollection &fills)
+ {
+ for (const ExtrusionEntity *ee : fills.entities) {
+ assert(ee->is_collection());
+ for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
+ assert(! ee2->is_collection());
+ assert(! ee2->is_loop());
+ if (ee2->role() == erBridgeInfill)
+ return true;
+ }
+ }
+ return false;
+ }
+ static bool has_bridging_extrusions(const Layer &layer)
+ {
+ for (const LayerRegion *region : layer.regions) {
+ if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters))
+ return true;
+ if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills))
+ return true;
+ }
+ return false;
+ }
+
+ static inline void collect_bridging_perimeter_areas(const ExtrusionLoop &loop, const float expansion_scaled, Polygons &out)
+ {
+ assert(expansion_scaled >= 0.f);
+ for (const ExtrusionPath &ep : loop.paths)
+ if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) {
+ float exp = 0.5f * scale_(ep.width) + expansion_scaled;
+ if (ep.is_closed()) {
+ if (ep.size() >= 3) {
+ // This is a complete loop.
+ // Add the outer contour first.
+ Polygon poly;
+ poly.points = ep.polyline.points;
+ poly.points.pop_back();
+ if (poly.area() < 0)
+ poly.reverse();
+ polygons_append(out, offset(poly, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ Polygons holes = offset(poly, - exp, SUPPORT_SURFACES_OFFSET_PARAMETERS);
+ polygons_reverse(holes);
+ polygons_append(out, holes);
+ }
+ } else if (ep.size() >= 2) {
+ // Offset the polyline.
+ polygons_append(out, offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ }
+ }
+ }
+ static void collect_bridging_perimeter_areas(const ExtrusionEntityCollection &perimeters, const float expansion_scaled, Polygons &out)
+ {
+ for (const ExtrusionEntity *ee : perimeters.entities) {
+ if (ee->is_collection()) {
+ for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
+ assert(! ee2->is_collection());
+ if (ee2->is_loop())
+ collect_bridging_perimeter_areas(*static_cast<const ExtrusionLoop*>(ee2), expansion_scaled, out);
+ }
+ } else if (ee->is_loop())
+ collect_bridging_perimeter_areas(*static_cast<const ExtrusionLoop*>(ee), expansion_scaled, out);
+ }
+ }
+
+ static void remove_bridges_from_contacts(
+ const PrintConfig &print_config,
+ const Layer &lower_layer,
+ const Polygons &lower_layer_polygons,
+ LayerRegion *layerm,
+ float fw,
+ Polygons &contact_polygons)
+ {
+ // compute the area of bridging perimeters
+ Polygons bridges;
+ {
+ // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
+ Polygons lower_grown_slices = offset(lower_layer_polygons,
+ //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
+ 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1))),
+ SUPPORT_SURFACES_OFFSET_PARAMETERS);
+ // Collect perimeters of this layer.
+ //FIXME split_at_first_point() could split a bridge mid-way
+ #if 0
+ Polylines overhang_perimeters = layerm->perimeters.as_polylines();
+ // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
+ for (Polyline &polyline : overhang_perimeters)
+ polyline.points[0].x += 1;
+ // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters.
+ overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices);
+ #else
+ Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices);
+ #endif
+
+ // only consider straight overhangs
+ // only consider overhangs having endpoints inside layer's slices
+ // convert bridging polylines into polygons by inflating them with their thickness
+ // since we're dealing with bridges, we can't assume width is larger than spacing,
+ // so we take the largest value and also apply safety offset to be ensure no gaps
+ // are left in between
+ Flow bridge_flow = layerm->flow(frPerimeter, true);
+ float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()));
+ for (Polyline &polyline : overhang_perimeters)
+ if (polyline.is_straight()) {
+ // This is a bridge
+ polyline.extend_start(fw);
+ polyline.extend_end(fw);
+ // Is the straight perimeter segment supported at both sides?
+ if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point()))
+ // Offset a polyline into a thick line.
+ polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
+ }
+ bridges = union_(bridges);
}
+ // remove the entire bridges and only support the unsupported edges
+ //FIXME the brided regions are already collected as layerm->bridged. Use it?
+ for (const Surface &surface : layerm->fill_surfaces.surfaces)
+ if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1)
+ polygons_append(bridges, surface.expolygon);
+ //FIXME add the gap filled areas. Extrude the gaps with a bridge flow?
+ // Remove the unsupported ends of the bridges from the bridged areas.
+ //FIXME add supports at regular intervals to support long bridges!
+ bridges = diff(bridges,
+ // Offset unsupported edges into polygons.
+ offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ // Remove bridged areas from the supported areas.
+ contact_polygons = diff(contact_polygons, bridges, true);
}
}
+#ifdef SLIC3R_DEBUG
+static int Test()
+{
+// for (int i = 0; i < 30; ++ i)
+ {
+ int i = -1;
+// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000-prev.bin", i);
+// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000.bin", i);
+ auto grid = SupportGridPattern::deserialize("d:\\temp\\support-top-contacts-final-run1-layer27-z5.650000.bin", i);
+ std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersections = grid.grid().intersecting_edges();
+ if (! intersections.empty())
+ printf("Intersections between contours!\n");
+ Slic3r::export_intersections_to_svg("d:\\temp\\support_polygon_intersections.svg", grid.support_polygons());
+ Slic3r::SVG::export_expolygons("d:\\temp\\support_polygons.svg", union_ex(grid.support_polygons(), false));
+ Slic3r::SVG::export_expolygons("d:\\temp\\trimming_polygons.svg", union_ex(grid.trimming_polygons(), false));
+ Polygons extracted = grid.extract_support(scale_(0.21 / 2), true);
+ Slic3r::SVG::export_expolygons("d:\\temp\\extracted.svg", union_ex(extracted, false));
+ printf("hu!");
+ }
+ return 0;
+}
+static int run_support_test = Test();
+#endif /* SLIC3R_DEBUG */
+
// Generate top contact layers supporting overhangs.
// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
// If supports over bed surface only are requested, don't generate contact layers over an object.
@@ -657,9 +968,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
++ iRun;
#endif /* SLIC3R_DEBUG */
+ // Slice support enforcers / support blockers.
+ std::vector<ExPolygons> enforcers = object.slice_support_enforcers();
+ std::vector<ExPolygons> blockers = object.slice_support_blockers();
+
// Output layers, sorted by top Z.
MyLayersPtr contact_out;
+ const bool support_auto = m_object_config->support_material_auto.value;
// If user specified a custom angle threshold, convert it to radians.
// Zero means automatic overhang detection.
const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ?
@@ -694,10 +1010,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers.
// So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers.
size_t num_layers = this->has_support() ? object.layer_count() : 1;
- contact_out.assign(num_layers, nullptr);
+ // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow,
+ // and the other for the overhangs extruded with a normal flow.
+ contact_out.assign(num_layers * 2, nullptr);
tbb::spin_mutex layer_storage_mutex;
tbb::parallel_for(tbb::blocked_range<size_t>(this->has_raft() ? 0 : 1, num_layers),
- [this, &object, &buildplate_covered, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range<size_t>& range) {
+ [this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out]
+ (const tbb::blocked_range<size_t>& range) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id)
{
const Layer &layer = *object.layers[layer_id];
@@ -708,6 +1027,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
Polygons contact_polygons;
Polygons slices_margin_cached;
float slices_margin_cached_offset = -1.;
+ Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers[layer_id-1]->slices.expolygons);
+ // Offset of the lower layer, to trim the support polygons with to calculate dense supports.
+ float no_interface_offset = 0.f;
if (layer_id == 0) {
// This is the first object layer, so the object is being printed on a raft and
// we're here just to get the object footprint for the raft.
@@ -722,6 +1044,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Extrusion width accounts for the roundings of the extrudates.
// It is the maximum widh of the extrudate.
float fw = float(layerm->flow(frExternalPerimeter).scaled_width());
+ no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw);
float lower_layer_offset =
(layer_id < this->m_object_config->support_material_enforce_layers.value) ?
// Enforce a full possible support, ignore the overhang angle.
@@ -734,7 +1057,6 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Overhang polygons for this layer and region.
Polygons diff_polygons;
Polygons layerm_polygons = to_polygons(layerm->slices);
- Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons);
if (lower_layer_offset == 0.f) {
// Support everything.
diff_polygons = diff(layerm_polygons, lower_layer_polygons);
@@ -744,28 +1066,61 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]);
}
} else {
- // Get the regions needing a suport, collapse very tiny spots.
- //FIXME cache the lower layer offset if this layer has multiple regions.
- diff_polygons = offset2(
- diff(layerm_polygons,
- offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
- -0.1f*fw, +0.1f*fw);
- if (! buildplate_covered.empty()) {
- // Don't support overhangs above the top surfaces.
- // This step is done before the contact surface is calculated by growing the overhang region.
- diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]);
+ if (support_auto) {
+ // Get the regions needing a suport, collapse very tiny spots.
+ //FIXME cache the lower layer offset if this layer has multiple regions.
+ #if 1
+ diff_polygons = offset2(
+ diff(layerm_polygons,
+ offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
+ //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to
+ // no support at all for not so steep overhangs.
+ - 0.1f * fw, 0.1f * fw);
+ #else
+ diff_polygons =
+ diff(layerm_polygons,
+ offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ #endif
+ if (! buildplate_covered.empty()) {
+ // Don't support overhangs above the top surfaces.
+ // This step is done before the contact surface is calculated by growing the overhang region.
+ diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]);
+ }
+ if (! diff_polygons.empty()) {
+ // Offset the support regions back to a full overhang, restrict them to the full overhang.
+ // This is done to increase size of the supporting columns below, as they are calculated by
+ // propagating these contact surfaces downwards.
+ diff_polygons = diff(
+ intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons),
+ lower_layer_polygons);
+ }
+ }
+ if (! enforcers.empty()) {
+ // Apply the "support enforcers".
+ //FIXME add the "enforcers" to the sparse support regions only.
+ const ExPolygons &enforcer = enforcers[layer_id - 1];
+ if (! enforcer.empty()) {
+ // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
+ Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(enforcer)),
+ offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ if (! new_contacts.empty()) {
+ if (diff_polygons.empty())
+ diff_polygons = std::move(new_contacts);
+ else
+ diff_polygons = union_(diff_polygons, new_contacts);
+ }
+ }
}
- if (diff_polygons.empty())
- continue;
- // Offset the support regions back to a full overhang, restrict them to the full overhang.
- diff_polygons = diff(
- intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons),
- lower_layer_polygons);
+ }
+ // Apply the "support blockers".
+ if (! diff_polygons.empty() && ! blockers.empty() && ! blockers[layer_id].empty()) {
+ // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
+ diff_polygons = diff(diff_polygons, to_polygons(blockers[layer_id]));
}
if (diff_polygons.empty())
continue;
- #ifdef SLIC3R_DEBUG
+ #ifdef SLIC3R_DEBUG
{
::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg",
iRun, layer_id,
@@ -776,65 +1131,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
}
#endif /* SLIC3R_DEBUG */
- if (this->m_object_config->dont_support_bridges) {
- // compute the area of bridging perimeters
- // Note: this is duplicate code from GCode.pm, we need to refactor
- if (true) {
- Polygons bridged_perimeters;
- {
- Flow bridge_flow = layerm->flow(frPerimeter, true);
- coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1);
- Polygons lower_grown_slices = offset(lower_layer_polygons, 0.5f*float(scale_(nozzle_diameter)), SUPPORT_SURFACES_OFFSET_PARAMETERS);
-
- // Collect perimeters of this layer.
- // TODO: split_at_first_point() could split a bridge mid-way
- Polylines overhang_perimeters;
- for (ExtrusionEntity* extrusion_entity : layerm->perimeters.entities) {
- push_entity_as_polyline(overhang_perimeters, extrusion_entity);
- }
-
- // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
- for (Polyline &polyline : overhang_perimeters)
- polyline.points[0].x += 1;
- // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters.
- overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices);
-
- // only consider straight overhangs
- // only consider overhangs having endpoints inside layer's slices
- // convert bridging polylines into polygons by inflating them with their thickness
- // since we're dealing with bridges, we can't assume width is larger than spacing,
- // so we take the largest value and also apply safety offset to be ensure no gaps
- // are left in between
- float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()));
- for (Polyline &polyline : overhang_perimeters)
- if (polyline.is_straight()) {
- // This is a bridge
- polyline.extend_start(fw);
- polyline.extend_end(fw);
- // Is the straight perimeter segment supported at both sides?
- if (layer.slices.contains(polyline.first_point()) && layer.slices.contains(polyline.last_point()))
- // Offset a polyline into a thick line.
- polygons_append(bridged_perimeters, offset(polyline, 0.5f * w + 10.f));
- }
- bridged_perimeters = union_(bridged_perimeters);
- }
- // remove the entire bridges and only support the unsupported edges
- Polygons bridges;
- for (const Surface &surface : layerm->fill_surfaces.surfaces)
- if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1)
- polygons_append(bridges, surface.expolygon);
- diff_polygons = diff(diff_polygons, bridges, true);
- polygons_append(bridges, bridged_perimeters);
- polygons_append(diff_polygons,
- intersection(
- // Offset unsupported edges into polygons.
- offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS),
- bridges));
- } else {
- // just remove bridged areas
- diff_polygons = diff(diff_polygons, layerm->bridged, true);
- }
- } // if (m_objconfig->dont_support_bridges)
+ if (this->m_object_config->dont_support_bridges)
+ SupportMaterialInternal::remove_bridges_from_contacts(
+ *m_print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons);
if (diff_polygons.empty())
continue;
@@ -848,7 +1147,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
union_ex(diff_polygons, false));
#endif /* SLIC3R_DEBUG */
- if (this->has_contact_loops())
+ //FIXME the overhang_polygons are used to construct the support towers as well.
+ //if (this->has_contact_loops())
+ // Store the exact contour of the overhang for the contact loops.
polygons_append(overhang_polygons, diff_polygons);
// Let's define the required contact area by using a max gap of half the upper
@@ -857,12 +1158,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// on the other side of the object (if it's very thin).
{
//FIMXE 1) Make the offset configurable, 2) Make the Z span configurable.
+ //FIXME one should trim with the layer span colliding with the support layer, this layer
+ // may be lower than lower_layer, so the support area needed may need to be actually bigger!
+ // For the same reason, the non-bridging support area may be smaller than the bridging support area!
float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy)));
if (slices_margin_cached_offset != slices_margin_offset) {
slices_margin_cached_offset = slices_margin_offset;
slices_margin_cached = (slices_margin_offset == 0.f) ?
- to_polygons(lower_layer.slices.expolygons) :
- offset(lower_layer.slices.expolygons, slices_margin_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS);
+ lower_layer_polygons :
+ offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! buildplate_covered.empty()) {
// Trim the inflated contact surfaces by the top surfaces as well.
polygons_append(slices_margin_cached, buildplate_covered[layer_id]);
@@ -885,88 +1189,220 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
} // for each layer.region
} // end of Generate overhang/contact_polygons for non-raft layers.
- // now apply the contact areas to the layer were they need to be made
+ // Now apply the contact areas to the layer where they need to be made.
if (! contact_polygons.empty()) {
- // get the average nozzle diameter used on this layer
MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact);
new_layer.idx_object_layer_above = layer_id;
- if (m_slicing_params.soluble_interface) {
+ MyLayer *bridging_layer = nullptr;
+ if (layer_id == 0) {
+ // This is a raft contact layer sitting directly on the print bed.
+ assert(this->has_raft());
+ new_layer.print_z = m_slicing_params.raft_contact_top_z;
+ new_layer.bottom_z = m_slicing_params.raft_interface_top_z;
+ new_layer.height = m_slicing_params.contact_raft_layer_height;
+ } else if (m_slicing_params.soluble_interface) {
// Align the contact surface height with a layer immediately below the supported layer.
- new_layer.print_z = layer.print_z - layer.height;
- if (layer_id == 0) {
- // This is a raft contact layer sitting directly on the print bed.
- new_layer.height = m_slicing_params.contact_raft_layer_height;
- new_layer.bottom_z = m_slicing_params.raft_interface_top_z;
- } else {
- // Interface layer will be synchronized with the object.
- assert(layer_id > 0);
- new_layer.height = object.layers[layer_id - 1]->height;
- new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers[layer_id - 2]->print_z;
- }
+ // Interface layer will be synchronized with the object.
+ new_layer.print_z = layer.print_z - layer.height;
+ new_layer.height = object.layers[layer_id - 1]->height;
+ new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers[layer_id - 2]->print_z;
} else {
- // Contact layer will be printed with a normal flow, but
- // it will support layers printed with a bridging flow.
- //FIXME Probably printing with the bridge flow? How about the unsupported perimeters? Are they printed with the bridging flow?
- // In the future we may switch to a normal extrusion flow for the supported bridges.
- // Get the average nozzle diameter used on this layer.
- coordf_t nozzle_dmr = 0.;
- for (const LayerRegion *region : layer.regions)
- nozzle_dmr += region->region()->nozzle_dmr_avg(*m_print_config);
- nozzle_dmr /= coordf_t(layer.regions.size());
- new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance;
+ new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance;
new_layer.bottom_z = new_layer.print_z;
new_layer.height = 0.;
- if (layer_id == 0) {
- // This is a raft contact layer sitting directly on the print bed.
- assert(this->has_raft());
- new_layer.bottom_z = m_slicing_params.raft_interface_top_z;
- new_layer.height = m_slicing_params.contact_raft_layer_height;
+ // Ignore this contact area if it's too low.
+ // Don't want to print a layer below the first layer height as it may not stick well.
+ //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact
+ // and it may actually make sense to do it with a thinner layer than the first layer height.
+ if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) {
+ // This contact layer is below the first layer height, therefore not printable. Don't support this surface.
+ continue;
+ } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) {
+ // Align the layer with the 1st layer height.
+ new_layer.print_z = m_slicing_params.first_print_layer_height;
+ new_layer.bottom_z = 0;
+ new_layer.height = m_slicing_params.first_print_layer_height;
} else {
- // Ignore this contact area if it's too low.
- // Don't want to print a layer below the first layer height as it may not stick well.
- //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact
- // and it may actually make sense to do it with a thinner layer than the first layer height.
- if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) {
- // This contact layer is below the first layer height, therefore not printable. Don't support this surface.
- continue;
- } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) {
- // Align the layer with the 1st layer height.
- new_layer.print_z = m_slicing_params.first_print_layer_height;
- new_layer.bottom_z = 0;
- new_layer.height = m_slicing_params.first_print_layer_height;
- } else {
- // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and
- // its height will be set adaptively later on.
+ // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and
+ // its height will be set adaptively later on.
+ }
+
+ // Contact layer will be printed with a normal flow, but
+ // it will support layers printed with a bridging flow.
+ if (SupportMaterialInternal::has_bridging_extrusions(layer)) {
+ coordf_t bridging_height = 0.;
+ for (const LayerRegion *region : layer.regions)
+ bridging_height += region->region()->bridging_height_avg(*m_print_config);
+ bridging_height /= coordf_t(layer.regions.size());
+ coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance;
+ if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) {
+ // Not below the first layer height means this layer is printable.
+ if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) {
+ // Align the layer with the 1st layer height.
+ bridging_print_z = m_slicing_params.first_print_layer_height;
+ }
+ if (bridging_print_z < new_layer.print_z - EPSILON) {
+ // Allocate the new layer.
+ bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact);
+ bridging_layer->idx_object_layer_above = layer_id;
+ bridging_layer->print_z = bridging_print_z;
+ if (bridging_print_z == m_slicing_params.first_print_layer_height) {
+ bridging_layer->bottom_z = 0;
+ bridging_layer->height = m_slicing_params.first_print_layer_height;
+ } else {
+ // Don't know the height yet.
+ bridging_layer->bottom_z = bridging_print_z;
+ bridging_layer->height = 0;
+ }
+ }
}
}
}
+ // Achtung! The contact_polygons need to be trimmed by slices_margin_cached, otherwise
+ // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work!
SupportGridPattern support_grid_pattern(
// Support islands, to be stretched into a grid.
contact_polygons,
// Trimming polygons, to trim the stretched support islands.
slices_margin_cached,
- // How much to offset the extracted contour outside of the grid.
+ // Grid resolution.
m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
Geometry::deg2rad(m_object_config->support_material_angle.value));
- // 1) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
- new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5);
- // 2) Contact polygons will be projected down. To keep the interface and base layers to grow, return a contour a tiny bit smaller than the grid cells.
- new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3));
+ // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells.
+ new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true));
+ // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
+ if (layer_id == 0 || m_slicing_params.soluble_interface) {
+ // if (no_interface_offset == 0.f) {
+ new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true);
+ } else {
+ // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
+ Polygons dense_interface_polygons = diff(overhang_polygons,
+ offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ if (! dense_interface_polygons.empty()) {
+ dense_interface_polygons =
+ // Achtung! The dense_interface_polygons need to be trimmed by slices_margin_cached, otherwise
+ // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work!
+ diff(
+ // Regularize the contour.
+ offset(dense_interface_polygons, no_interface_offset * 0.1f),
+ slices_margin_cached);
+ SupportGridPattern support_grid_pattern(
+ // Support islands, to be stretched into a grid.
+ dense_interface_polygons,
+ // Trimming polygons, to trim the stretched support islands.
+ slices_margin_cached,
+ // Grid resolution.
+ m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
+ Geometry::deg2rad(m_object_config->support_material_angle.value));
+ new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false);
+ #ifdef SLIC3R_DEBUG
+ {
+ support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z));
+
+ BoundingBox bbox = get_extents(contact_polygons);
+ bbox.merge(get_extents(new_layer.polygons));
+ ::Slic3r::SVG svg(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z));
+ svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f);
+ svg.draw(union_ex(contact_polygons, false), "blue", 0.5f);
+ svg.draw(union_ex(dense_interface_polygons, false), "green", 0.5f);
+ svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f);
+ svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f));
+ }
+ #endif /* SLIC3R_DEBUG */
+ }
+ }
+ #ifdef SLIC3R_DEBUG
+ {
+ BoundingBox bbox = get_extents(contact_polygons);
+ bbox.merge(get_extents(new_layer.polygons));
+ ::Slic3r::SVG svg(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z));
+ svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f);
+ svg.draw(union_ex(contact_polygons, false), "blue", 0.5f);
+ svg.draw(union_ex(overhang_polygons, false), "green", 0.5f);
+ svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f);
+ svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f));
+ }
+ #endif /* SLIC3R_DEBUG */
// Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded.
// Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons.
// Store the overhang polygons.
// The overhang polygons are used in the path generator for planning of the contact loops.
- // if (this->has_contact_loops())
+ // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug.
new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons));
- contact_out[layer_id] = &new_layer;
+ contact_out[layer_id * 2] = &new_layer;
+ if (bridging_layer != nullptr) {
+ bridging_layer->polygons = new_layer.polygons;
+ bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons);
+ bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons);
+ contact_out[layer_id * 2 + 1] = bridging_layer;
+ }
}
}
});
+
// Compress contact_out, remove the nullptr items.
remove_nulls(contact_out);
+ // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z.
+ std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; });
+
+ // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter),
+ // the top contact layer is merged into the bottom contact layer.
+ {
+ int i = 0;
+ int k = 0;
+ {
+ // Find the span of layers, which are to be printed at the first layer height.
+ int j = 0;
+ for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j);
+ if (j > 0) {
+ // Merge the contact_out layers (0) to (j - 1) into the contact_out[0].
+ MyLayer &dst = *contact_out.front();
+ for (int u = 1; u < j; ++ u) {
+ MyLayer &src = *contact_out[u];
+ // The union_() does not support move semantic yet, but maybe one day it will.
+ dst.polygons = union_(dst.polygons, std::move(src.polygons));
+ *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons));
+ *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons));
+ // Source polygon is no more needed, it will not be refrenced. Release its data.
+ src.reset();
+ }
+ // Snap the first layer to the 1st layer height.
+ dst.print_z = m_slicing_params.first_print_layer_height;
+ dst.height = m_slicing_params.first_print_layer_height;
+ dst.bottom_z = 0;
+ ++ k;
+ }
+ i = j;
+ }
+ for (; i < int(contact_out.size()); ++ k) {
+ // Find the span of layers closer than m_support_layer_height_min.
+ int j = i + 1;
+ coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON;
+ for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ;
+ if (i + 1 < j) {
+ // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i].
+ MyLayer &dst = *contact_out[i];
+ for (int u = i + 1; u < j; ++ u) {
+ MyLayer &src = *contact_out[u];
+ // The union_() does not support move semantic yet, but maybe one day it will.
+ dst.polygons = union_(dst.polygons, std::move(src.polygons));
+ *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons));
+ *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons));
+ // Source polygon is no more needed, it will not be refrenced. Release its data.
+ src.reset();
+ }
+ }
+ if (k < i)
+ contact_out[k] = contact_out[i];
+ i = j;
+ }
+ if (k < contact_out.size())
+ contact_out.erase(contact_out.begin() + k, contact_out.end());
+ }
+
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end";
return contact_out;
@@ -1002,7 +1438,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id;
const Layer &layer = *object.get_layer(layer_id);
// Collect projections of all contact areas above or at the same level as this top surface.
- for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) {
+ for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) {
Polygons polygons_new;
// Contact surfaces are expanded away from the object, trimmed by the object.
// Use a slight positive offset to overlap the touching regions.
@@ -1010,7 +1446,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
// Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form.
polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON));
#else
- // Consume the contact_polygons. The contact polygons are already expanded into a grid form.
+ // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller
+ // than the grid cells.
polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons));
#endif
// These are the overhang surfaces. They are touching the object and they are not expanded away from the object.
@@ -1022,9 +1459,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
continue;
Polygons projection_raw = union_(projection);
- // Top surfaces of this layer, to be used to stop the surface volume from growing down.
tbb::task_group task_group;
if (! m_object_config->support_material_buildplate_only)
+ // Find the bottom contact layers above the top surfaces of this layer.
task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] {
Polygons top = collect_region_slices_by_type(layer, stTop);
#ifdef SLIC3R_DEBUG
@@ -1052,28 +1489,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
// Grow top surfaces so that interface and support generation are generated
// with some spacing from object - it looks we don't need the actual
// top shapes so this can be done here
+ //FIXME calculate layer height based on the actual thickness of the layer:
+ // If the layer is extruded with no bridging flow, support just the normal extrusions.
layer_new.height = m_slicing_params.soluble_interface ?
// Align the interface layer with the object's layer height.
object.layers[layer_id + 1]->height :
// Place a bridge flow interface layer over the top surface.
+ //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?)
+ // According to Jindrich the bottom surfaces work well.
+ //FIXME test the bridging flow instead?
m_support_material_interface_flow.nozzle_diameter;
layer_new.print_z = m_slicing_params.soluble_interface ? object.layers[layer_id + 1]->print_z :
layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value;
layer_new.bottom_z = layer.print_z;
layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = ! m_slicing_params.soluble_interface;
- //FIXME how much to inflate the top surface?
+ //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
+ //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! m_slicing_params.soluble_interface) {
// Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
// so there will be no support surfaces generated with thickness lower than m_support_layer_height_min.
for (size_t top_idx = size_t(std::max<int>(0, contact_idx));
- top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min;
+ top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min + EPSILON;
++ top_idx) {
- if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min) {
+ if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min - EPSILON) {
// A top layer has been found, which is close to the new bottom layer.
coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z;
- assert(std::abs(diff) <= this->m_support_layer_height_min);
+ assert(std::abs(diff) <= this->m_support_layer_height_min + EPSILON);
if (diff > 0.) {
// The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer.
assert(diff < layer_new.height + EPSILON);
@@ -1097,10 +1540,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
union_ex(layer_new.polygons, false));
#endif /* SLIC3R_DEBUG */
// Trim the already created base layers above the current layer intersecting with the new bottom contacts layer.
+ //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage?
touching = offset(touching, float(SCALED_EPSILON));
for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
const Layer &layer_above = *object.layers[layer_id_above];
- if (layer_above.print_z > layer_new.print_z + EPSILON)
+ if (layer_above.print_z > layer_new.print_z - EPSILON)
break;
if (! layer_support_areas[layer_id_above].empty()) {
#ifdef SLIC3R_DEBUG
@@ -1153,7 +1597,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
projection,
// Trimming polygons, to trim the stretched support islands.
trimming,
- // How much to offset the extracted contour outside of the grid.
+ // Grid spacing.
m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
Geometry::deg2rad(m_object_config->support_material_angle.value));
tbb::task_group task_group_inner;
@@ -1164,7 +1608,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
, &layer
#endif /* SLIC3R_DEBUG */
] {
- layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25);
+ layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true);
#ifdef SLIC3R_DEBUG
Slic3r::SVG::export_expolygons(
debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z),
@@ -1178,7 +1622,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
, &layer
#endif /* SLIC3R_DEBUG */
] {
- projection_new = support_grid_pattern.extract_support(-5);
+ projection_new = support_grid_pattern.extract_support(-5, true);
#ifdef SLIC3R_DEBUG
Slic3r::SVG::export_expolygons(
debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z),
@@ -1191,7 +1635,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
task_group.wait();
}
std::reverse(bottom_contacts.begin(), bottom_contacts.end());
- trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
+// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy);
+ trim_support_layers_by_object(object, bottom_contacts,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
+
} // ! top_contacts.empty()
return bottom_contacts;
@@ -1508,9 +1956,6 @@ void PrintObjectSupportMaterial::generate_base_layers(
assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z);
// Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new.
- idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above,
- [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; });
-
// New polygons for layer_intermediate.
Polygons polygons_new;
@@ -1529,12 +1974,10 @@ void PrintObjectSupportMaterial::generate_base_layers(
// 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here.
// 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen.
// 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top.
- int idx_top_contact_overlapping = idx_top_contact_above;
- while (idx_top_contact_overlapping >= 0 &&
- top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON)
- -- idx_top_contact_overlapping;
+ idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above,
+ [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; });
// Collect all the top_contact layer intersecting with this layer.
- for (; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) {
+ for ( int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) {
MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping];
if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON)
break;
@@ -1614,7 +2057,10 @@ void PrintObjectSupportMaterial::generate_base_layers(
++ iRun;
#endif /* SLIC3R_DEBUG */
- trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_gap_xy);
+// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy);
+ this->trim_support_layers_by_object(object, intermediate_layers,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
}
void PrintObjectSupportMaterial::trim_support_layers_by_object(
@@ -1659,19 +2105,23 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
const Layer &object_layer = *object.layers[i];
if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
break;
- polygons_append(polygons_trimming, (Polygons)object_layer.slices);
+ polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
if (! this->m_slicing_params.soluble_interface) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers.size(); ++ i) {
const Layer &object_layer = *object.layers[i];
bool some_region_overlaps = false;
- for (LayerRegion* region : object_layer.regions) {
- coordf_t nozzle_dmr = region->region()->nozzle_dmr_avg(*this->m_print_config);
- if (object_layer.print_z - nozzle_dmr > support_layer.print_z + gap_extra_above - EPSILON)
+ for (LayerRegion *region : object_layer.regions) {
+ coordf_t bridging_height = region->region()->bridging_height_avg(*this->m_print_config);
+ if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON)
break;
some_region_overlaps = true;
- polygons_append(polygons_trimming, to_polygons(region->slices.filter_by_type(stBottomBridge)));
+ polygons_append(polygons_trimming,
+ offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)),
+ gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ if (region->region()->config.overhangs.value)
+ SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming);
}
if (! some_region_overlaps)
break;
@@ -1681,9 +2131,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
// perimeter's width. $support contains the full shape of support
// material, thus including the width of its foremost extrusion.
// We leave a gap equal to a full extrusion width.
- support_layer.polygons = diff(
- support_layer.polygons,
- offset(polygons_trimming, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ support_layer.polygons = diff(support_layer.polygons, polygons_trimming);
}
});
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end";
@@ -1806,11 +2254,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_int
coordf_t top_z = intermediate_layers[std::min<int>(intermediate_layers.size()-1, idx_intermediate_layer + m_object_config->support_material_interface_layers - 1)]->print_z;
coordf_t bottom_z = intermediate_layers[std::max<int>(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z;
// Move idx_top_contact_first up until above the current print_z.
- idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; });
+ idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
// Collect the top contact areas above this intermediate layer, below top_z.
Polygons polygons_top_contact_projected;
for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) {
const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
+ //FIXME maybe this adds one interface layer in excess?
if (top_contact_layer.bottom_z - EPSILON > top_z)
break;
polygons_append(polygons_top_contact_projected, top_contact_layer.polygons);
@@ -1867,8 +2316,8 @@ static inline void fill_expolygons_generate_paths(
fill_params.density = density;
fill_params.complete = true;
fill_params.dont_adjust = true;
- for (ExPolygons::const_iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) {
- Surface surface(stInternal, *it_expolygon);
+ for (const ExPolygon &expoly : expolygons) {
+ Surface surface(stInternal, expoly);
filler->fill_surface_extrusion(&surface, fill_params, flow, role, dst);
}
}
@@ -1885,8 +2334,8 @@ static inline void fill_expolygons_generate_paths(
fill_params.density = density;
fill_params.complete = true;
fill_params.dont_adjust = true;
- for (ExPolygons::iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++it_expolygon) {
- Surface surface(stInternal, std::move(*it_expolygon));
+ for (ExPolygon &expoly : expolygons) {
+ Surface surface(stInternal, std::move(expoly));
filler->fill_surface_extrusion(&surface, fill_params, flow, role, dst);
}
}
@@ -2359,7 +2808,7 @@ void modulate_extrusion_by_overlapping_layers(
(fragment_end.is_start ? &polyline.points.front() : &polyline.points.back());
}
private:
- ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&);
+ ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {}
const std::vector<ExtrusionPathFragment> &m_path_fragments;
};
const coord_t search_radius = 7;
@@ -2719,6 +3168,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
continue;
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0;
+ //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
+ // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
Flow interface_flow(
float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)),
float(layer_ex.layer->height),
@@ -2812,13 +3263,13 @@ void PrintObjectSupportMaterial::generate_toolpaths(
}
layer_cache.overlaps.reserve(4);
- if (! bottom_contact_layer.empty())
+ if (!bottom_contact_layer.empty() && !bottom_contact_layer.extrusions.empty())
layer_cache.overlaps.push_back(&bottom_contact_layer);
- if (! top_contact_layer.empty())
+ if (!top_contact_layer.empty() && !top_contact_layer.extrusions.empty())
layer_cache.overlaps.push_back(&top_contact_layer);
- if (! interface_layer.empty())
+ if (!interface_layer.empty() && !interface_layer.extrusions.empty())
layer_cache.overlaps.push_back(&interface_layer);
- if (! base_layer.empty())
+ if (!base_layer.empty() && !base_layer.extrusions.empty())
layer_cache.overlaps.push_back(&base_layer);
// Sort the layers with the same print_z coordinate by their heights, thickest first.
std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; });
diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp
index 968763446..dcb3bd5b3 100644
--- a/xs/src/libslic3r/SupportMaterial.hpp
+++ b/xs/src/libslic3r/SupportMaterial.hpp
@@ -12,6 +12,7 @@ class PrintConfig;
class PrintObjectConfig;
// how much we extend support around the actual contact area
+//FIXME this should be dependent on the nozzle diameter!
#define SUPPORT_MATERIAL_MARGIN 1.5
// This class manages raft and supports for a single PrintObject.
@@ -71,6 +72,21 @@ public:
overhang_polygons = nullptr;
}
+ void reset() {
+ layer_type = sltUnknown;
+ print_z = 0.;
+ bottom_z = 0.;
+ height = 0.;
+ idx_object_layer_above = size_t(-1);
+ idx_object_layer_below = size_t(-1);
+ bridging = false;
+ polygons.clear();
+ delete contact_polygons;
+ contact_polygons = nullptr;
+ delete overhang_polygons;
+ overhang_polygons = nullptr;
+ }
+
bool operator==(const MyLayer &layer2) const {
return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
}
diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp
index 29cfeb1db..9544748e9 100644
--- a/xs/src/libslic3r/SurfaceCollection.hpp
+++ b/xs/src/libslic3r/SurfaceCollection.hpp
@@ -37,6 +37,11 @@ public:
void clear() { surfaces.clear(); }
bool empty() const { return surfaces.empty(); }
+ bool has(SurfaceType type) const {
+ for (const Surface &surface : this->surfaces)
+ if (surface.surface_type == type) return true;
+ return false;
+ }
void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; }
void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); }
diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
index 4c45680b6..544a5d00b 100644
--- a/xs/src/libslic3r/TriangleMesh.cpp
+++ b/xs/src/libslic3r/TriangleMesh.cpp
@@ -1,6 +1,7 @@
#include "TriangleMesh.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
+#include "MultiPoint.hpp"
#include "qhull/src/libqhullcpp/Qhull.h"
#include "qhull/src/libqhullcpp/QhullFacetList.h"
#include "qhull/src/libqhullcpp/QhullVertexSet.h"
@@ -21,16 +22,20 @@
#include <Eigen/Dense>
+// for SLIC3R_DEBUG_SLICE_PROCESSING
+#include "libslic3r.h"
+
#if 0
#define DEBUG
#define _DEBUG
#undef NDEBUG
+ #define SLIC3R_DEBUG
+// #define SLIC3R_TRIANGLEMESH_DEBUG
#endif
#include <assert.h>
-#ifdef SLIC3R_DEBUG
-// #define SLIC3R_TRIANGLEMESH_DEBUG
+#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
#include "SVG.hpp"
#endif
@@ -203,10 +208,14 @@ TriangleMesh::repair() {
}
// fill_holes
+#if 0
+ // Don't fill holes, the current algorithm does more harm than good on complex holes.
+ // Rather let the slicing algorithm close gaps in 2D slices.
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
stl_fill_holes(&stl);
stl_clear_error(&stl);
}
+#endif
// normal_directions
stl_fix_normal_directions(&stl);
@@ -225,7 +234,6 @@ TriangleMesh::repair() {
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
}
-
float TriangleMesh::volume()
{
if (this->stl.stats.volume == -1)
@@ -440,7 +448,7 @@ bool TriangleMesh::has_multiple_patches() const
facet_visited[facet_idx] = true;
for (int j = 0; j < 3; ++ j) {
int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
- if (! facet_visited[neighbor_idx])
+ if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
facet_queue[facet_queue_cnt ++] = neighbor_idx;
}
}
@@ -483,7 +491,7 @@ size_t TriangleMesh::number_of_patches() const
facet_visited[facet_idx] = true;
for (int j = 0; j < 3; ++ j) {
int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
- if (! facet_visited[neighbor_idx])
+ if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
facet_queue[facet_queue_cnt ++] = neighbor_idx;
}
}
@@ -492,8 +500,7 @@ size_t TriangleMesh::number_of_patches() const
return num_bodies;
}
-TriangleMeshPtrs
-TriangleMesh::split() const
+TriangleMeshPtrs TriangleMesh::split() const
{
TriangleMeshPtrs meshes;
std::set<int> seen_facets;
@@ -545,8 +552,7 @@ TriangleMesh::split() const
return meshes;
}
-void
-TriangleMesh::merge(const TriangleMesh &mesh)
+void TriangleMesh::merge(const TriangleMesh &mesh)
{
// reset stats and metadata
int number_of_facets = this->stl.stats.number_of_facets;
@@ -600,8 +606,7 @@ Polygon TriangleMesh::convex_hull()
return Slic3r::Geometry::convex_hull(pp);
}
-BoundingBoxf3
-TriangleMesh::bounding_box() const
+BoundingBoxf3 TriangleMesh::bounding_box() const
{
BoundingBoxf3 bb;
bb.defined = true;
@@ -748,8 +753,7 @@ const float* TriangleMesh::first_vertex() const
return stl.facet_start ? &stl.facet_start->vertex[0].x : nullptr;
}
-void
-TriangleMesh::require_shared_vertices()
+void TriangleMesh::require_shared_vertices()
{
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start";
if (!this->repaired)
@@ -758,10 +762,23 @@ TriangleMesh::require_shared_vertices()
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices";
stl_generate_shared_vertices(&(this->stl));
}
+#ifdef _DEBUG
+ // Verify validity of neighborship data.
+ for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) {
+ const stl_neighbors &nbr = stl.neighbors_start[facet_idx];
+ const int *vertices = stl.v_indices[facet_idx].vertex;
+ for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) {
+ int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx];
+ if (nbr_face != -1) {
+ assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]);
+ assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]);
+ }
+ }
+ }
+#endif /* _DEBUG */
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
}
-
TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
mesh(_mesh)
{
@@ -847,8 +864,7 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
}
}
-void
-TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers) const
+void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers) const
{
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice";
@@ -910,13 +926,30 @@ TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* la
{
static int iRun = 0;
for (size_t i = 0; i < z.size(); ++ i) {
- Polygons &polygons = (*layers)[i];
- SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), union_ex(polygons, true));
+ Polygons &polygons = (*layers)[i];
+ ExPolygons expolygons = union_ex(polygons, true);
+ SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons);
+ {
+ BoundingBox bbox;
+ for (const IntersectionLine &l : lines[i]) {
+ bbox.merge(l.a);
+ bbox.merge(l.b);
+ }
+ SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox);
+ svg.draw(expolygons);
+ for (const IntersectionLine &l : lines[i])
+ svg.draw(l, "red", 0);
+ svg.draw_outline(expolygons, "black", "blue", 0);
+ svg.Close();
+ }
+#if 0
+//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
for (Polygon &poly : polygons) {
for (size_t i = 1; i < poly.points.size(); ++ i)
assert(poly.points[i-1] != poly.points[i]);
assert(poly.points.front() != poly.points.back());
}
+#endif
}
++ iRun;
}
@@ -932,54 +965,36 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLin
const float min_z = fminf(facet.vertex[0].z, fminf(facet.vertex[1].z, facet.vertex[2].z));
const float max_z = fmaxf(facet.vertex[0].z, fmaxf(facet.vertex[1].z, facet.vertex[2].z));
- #ifdef SLIC3R_DEBUG
+ #ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx,
facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z,
facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z,
facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z);
printf("z: min = %.2f, max = %.2f\n", min_z, max_z);
- #endif
+ #endif /* SLIC3R_TRIANGLEMESH_DEBUG */
// find layer extents
std::vector<float>::const_iterator min_layer, max_layer;
min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z
max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z
- #ifdef SLIC3R_DEBUG
+ #ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin()));
- #endif
+ #endif /* SLIC3R_TRIANGLEMESH_DEBUG */
for (std::vector<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) {
std::vector<float>::size_type layer_idx = it - z.begin();
IntersectionLine il;
- if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il)) {
+ if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) {
boost::lock_guard<boost::mutex> l(*lines_mutex);
if (il.edge_type == feHorizontal) {
- // Insert all three edges of the face.
- const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
- const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0;
- for (int j = 0; j < 3; ++ j) {
- int a_id = vertices[j % 3];
- int b_id = vertices[(j+1) % 3];
- if (reverse)
- std::swap(a_id, b_id);
- const stl_vertex *a = &this->v_scaled_shared[a_id];
- const stl_vertex *b = &this->v_scaled_shared[b_id];
- il.a.x = a->x;
- il.a.y = a->y;
- il.b.x = b->x;
- il.b.y = b->y;
- il.a_id = a_id;
- il.b_id = b_id;
- (*lines)[layer_idx].push_back(il);
- }
+ // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume.
} else
- (*lines)[layer_idx].push_back(il);
+ (*lines)[layer_idx].emplace_back(il);
}
}
}
-void
-TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const
+void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const
{
std::vector<Polygons> layers_p;
this->slice(z, &layers_p);
@@ -1000,23 +1015,22 @@ TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>*
}
// Return true, if the facet has been sliced and line_out has been filled.
-bool TriangleMeshSlicer::slice_facet(
+TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
float slice_z, const stl_facet &facet, const int facet_idx,
const float min_z, const float max_z,
IntersectionLine *line_out) const
{
IntersectionPoint points[3];
size_t num_points = 0;
- size_t points_on_layer[3];
- size_t num_points_on_layer = 0;
+ size_t point_on_layer = size_t(-1);
// Reorder vertices so that the first one is the one with lowest Z.
// This is needed to get all intersection lines in a consistent order
// (external on the right of the line)
+ const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0);
- for (int j = i; j - i < 3; ++ j) { // loop through facet edges
+ for (int j = i; j - i < 3; ++j) { // loop through facet edges
int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)];
- const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
int a_id = vertices[j % 3];
int b_id = vertices[(j+1) % 3];
const stl_vertex *a = &this->v_scaled_shared[a_id];
@@ -1028,22 +1042,36 @@ bool TriangleMeshSlicer::slice_facet(
const stl_vertex &v0 = this->v_scaled_shared[vertices[0]];
const stl_vertex &v1 = this->v_scaled_shared[vertices[1]];
const stl_vertex &v2 = this->v_scaled_shared[vertices[2]];
+ const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal;
+ // We may ignore this edge for slicing purposes, but we may still use it for object cutting.
+ FacetSliceType result = Slicing;
+ const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx];
if (min_z == max_z) {
// All three vertices are aligned with slice_z.
line_out->edge_type = feHorizontal;
- if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) {
+ result = Cutting;
+ if (normal.z < 0) {
// If normal points downwards this is a bottom horizontal facet so we reverse its point order.
std::swap(a, b);
std::swap(a_id, b_id);
}
- } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) {
- // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
- line_out->edge_type = feTop;
- std::swap(a, b);
- std::swap(a_id, b_id);
} else {
- // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
- line_out->edge_type = feBottom;
+ // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane.
+ int nbr_idx = j % 3;
+ int nbr_face = nbr.neighbor[nbr_idx];
+ // Is the third vertex below the cutting plane?
+ bool third_below = v0.z < slice_z || v1.z < slice_z || v2.z < slice_z;
+ // Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice
+ // only if it is the upper edge.
+ // (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
+ // in respect to the cutting plane).
+ result = third_below ? Slicing : Cutting;
+ if (third_below) {
+ line_out->edge_type = feTop;
+ std::swap(a, b);
+ std::swap(a_id, b_id);
+ } else
+ line_out->edge_type = feBottom;
}
line_out->a.x = a->x;
line_out->a.y = a->y;
@@ -1051,322 +1079,586 @@ bool TriangleMeshSlicer::slice_facet(
line_out->b.y = b->y;
line_out->a_id = a_id;
line_out->b_id = b_id;
- return true;
+ assert(line_out->a != line_out->b);
+ return result;
}
if (a->z == slice_z) {
// Only point a alings with the cutting plane.
- points_on_layer[num_points_on_layer ++] = num_points;
- IntersectionPoint &point = points[num_points ++];
- point.x = a->x;
- point.y = a->y;
- point.point_id = a_id;
+ if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
+ point_on_layer = num_points;
+ IntersectionPoint &point = points[num_points ++];
+ point.x = a->x;
+ point.y = a->y;
+ point.point_id = a_id;
+ }
} else if (b->z == slice_z) {
// Only point b alings with the cutting plane.
- points_on_layer[num_points_on_layer ++] = num_points;
- IntersectionPoint &point = points[num_points ++];
- point.x = b->x;
- point.y = b->y;
- point.point_id = b_id;
+ if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
+ point_on_layer = num_points;
+ IntersectionPoint &point = points[num_points ++];
+ point.x = b->x;
+ point.y = b->y;
+ point.point_id = b_id;
+ }
} else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) {
// A general case. The face edge intersects the cutting plane. Calculate the intersection point.
- IntersectionPoint &point = points[num_points ++];
- point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z);
- point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z);
- point.edge_id = edge_id;
+ assert(a_id != b_id);
+ // Sort the edge to give a consistent answer.
+ if (a_id > b_id) {
+ std::swap(a_id, b_id);
+ std::swap(a, b);
+ }
+ IntersectionPoint &point = points[num_points];
+ double t = (double(slice_z) - double(b->z)) / (double(a->z) - double(b->z));
+ if (t <= 0.) {
+ if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
+ point.x = a->x;
+ point.y = a->y;
+ point_on_layer = num_points ++;
+ point.point_id = a_id;
+ }
+ } else if (t >= 1.) {
+ if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
+ point.x = b->x;
+ point.y = b->y;
+ point_on_layer = num_points ++;
+ point.point_id = b_id;
+ }
+ } else {
+ point.x = coord_t(floor(double(b->x) + (double(a->x) - double(b->x)) * t + 0.5));
+ point.y = coord_t(floor(double(b->y) + (double(a->y) - double(b->y)) * t + 0.5));
+ point.edge_id = edge_id;
+ ++ num_points;
+ }
}
}
- // We can't have only one point on layer because each vertex gets detected
- // twice (once for each edge), and we can't have three points on layer,
- // because we assume this code is not getting called for horizontal facets.
- assert(num_points_on_layer == 0 || num_points_on_layer == 2);
- if (num_points_on_layer > 0) {
- assert(points[points_on_layer[0]].point_id == points[points_on_layer[1]].point_id);
- assert(num_points == 2 || num_points == 3);
- if (num_points < 3)
- // This triangle touches the cutting plane with a single vertex. Ignore it.
- return false;
- // Erase one of the duplicate points.
- -- num_points;
- for (int i = points_on_layer[1]; i < num_points; ++ i)
- points[i] = points[i + 1];
- }
-
- // Facets must intersect each plane 0 or 2 times.
- assert(num_points == 0 || num_points == 2);
+ // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only.
+ assert(num_points < 3);
if (num_points == 2) {
- line_out->edge_type = feNone;
+ line_out->edge_type = feGeneral;
line_out->a = (Point)points[1];
line_out->b = (Point)points[0];
line_out->a_id = points[1].point_id;
line_out->b_id = points[0].point_id;
line_out->edge_a_id = points[1].edge_id;
line_out->edge_b_id = points[0].edge_id;
- return true;
+ // Not a zero lenght edge.
+ //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
+ //assert(line_out->a != line_out->b);
+ // The plane cuts at least one edge in a general position.
+ assert(line_out->a_id == -1 || line_out->b_id == -1);
+ assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1);
+ // General slicing position, use the segment for both slicing and object cutting.
+#if 0
+ if (line_out->a_id != -1 && line_out->b_id != -1) {
+ // Solving a degenerate case, where both the intersections snapped to an edge.
+ // Correctly classify the face as below or above based on the position of the 3rd point.
+ int i = vertices[0];
+ if (i == line_out->a_id || i == line_out->b_id)
+ i = vertices[1];
+ if (i == line_out->a_id || i == line_out->b_id)
+ i = vertices[2];
+ assert(i != line_out->a_id && i != line_out->b_id);
+ line_out->edge_type = (this->v_scaled_shared[i].z < slice_z) ? feTop : feBottom;
+ }
+#endif
+ return Slicing;
}
- return false;
+ return NoSlice;
}
-void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const
+//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing
+// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces.
+// So the following code makes only sense now to handle degenerate meshes with more than two faces
+// sharing a single edge.
+static inline void remove_tangent_edges(std::vector<IntersectionLine> &lines)
{
- // Remove tangent edges.
- //FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane.
- for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line)
- if (! line->skip && line->edge_type != feNone) {
- // This line is af facet edge. There may be a duplicate line with the same end vertices.
- // If the line is is an edge connecting two facets, find another facet edge
- // having the same endpoints but in reverse order.
- for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2)
- if (! line2->skip && line2->edge_type != feNone) {
- // Are these facets adjacent? (sharing a common edge on this layer)
- if (line->a_id == line2->a_id && line->b_id == line2->b_id) {
- line2->skip = true;
- /* if they are both oriented upwards or downwards (like a 'V')
- then we can remove both edges from this layer since it won't
- affect the sliced shape */
- /* if one of them is oriented upwards and the other is oriented
- downwards, let's only keep one of them (it doesn't matter which
- one since all 'top' lines were reversed at slicing) */
- if (line->edge_type == line2->edge_type) {
- line->skip = true;
- break;
- }
- } else if (line->a_id == line2->b_id && line->b_id == line2->a_id) {
- /* if this edge joins two horizontal facets, remove both of them */
- if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) {
- line->skip = true;
- line2->skip = true;
- break;
- }
+ std::vector<IntersectionLine*> by_vertex_pair;
+ by_vertex_pair.reserve(lines.size());
+ for (IntersectionLine& line : lines)
+ if (line.edge_type != feGeneral && line.a_id != -1)
+ // This is a face edge. Check whether there is its neighbor stored in lines.
+ by_vertex_pair.emplace_back(&line);
+ auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) {
+ // Sort vertices of l1, l2 lexicographically
+ int l1a = l1->a_id;
+ int l1b = l1->b_id;
+ int l2a = l2->a_id;
+ int l2b = l2->b_id;
+ if (l1a > l1b)
+ std::swap(l1a, l1b);
+ if (l2a > l2b)
+ std::swap(l2a, l2b);
+ // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored.
+ return l1a < l2a || (l1a == l2a && l1b < l2b);
+ };
+ std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted);
+ for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) {
+ IntersectionLine &l1 = **line;
+ if (! l1.skip()) {
+ // Iterate as long as line and line2 edges share the same end points.
+ for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) {
+ // Lines must share the end points.
+ assert(! edges_lower_sorted(*line, *line2));
+ assert(! edges_lower_sorted(*line2, *line));
+ IntersectionLine &l2 = **line2;
+ if (l2.skip())
+ continue;
+ if (l1.a_id == l2.a_id) {
+ assert(l1.b_id == l2.b_id);
+ l2.set_skip();
+ // If they are both oriented upwards or downwards (like a 'V'),
+ // then we can remove both edges from this layer since it won't
+ // affect the sliced shape.
+ // If one of them is oriented upwards and the other is oriented
+ // downwards, let's only keep one of them (it doesn't matter which
+ // one since all 'top' lines were reversed at slicing).
+ if (l1.edge_type == l2.edge_type) {
+ l1.set_skip();
+ break;
+ }
+ } else {
+ assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id);
+ // If this edge joins two horizontal facets, remove both of them.
+ if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) {
+ l1.set_skip();
+ l2.set_skip();
+ break;
}
}
+ }
}
+ }
+}
- struct OpenPolyline {
- OpenPolyline() {};
- OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) :
- start(start), end(end), points(std::move(points)), consumed(false) {}
- void reverse() {
- std::swap(start, end);
- std::reverse(points.begin(), points.end());
+struct OpenPolyline {
+ OpenPolyline() {};
+ OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) :
+ start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); }
+ void reverse() {
+ std::swap(start, end);
+ std::reverse(points.begin(), points.end());
+ }
+ IntersectionReference start;
+ IntersectionReference end;
+ Points points;
+ double length;
+ bool consumed;
+};
+
+// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity.
+// Only connects segments crossing triangles of the same orientation.
+static void chain_lines_by_triangle_connectivity(std::vector<IntersectionLine> &lines, Polygons &loops, std::vector<OpenPolyline> &open_polylines)
+{
+ // Build a map of lines by edge_a_id and a_id.
+ std::vector<IntersectionLine*> by_edge_a_id;
+ std::vector<IntersectionLine*> by_a_id;
+ by_edge_a_id.reserve(lines.size());
+ by_a_id.reserve(lines.size());
+ for (IntersectionLine &line : lines) {
+ if (! line.skip()) {
+ if (line.edge_a_id != -1)
+ by_edge_a_id.emplace_back(&line);
+ if (line.a_id != -1)
+ by_a_id.emplace_back(&line);
}
- IntersectionReference start;
- IntersectionReference end;
- Points points;
- bool consumed;
- };
- std::vector<OpenPolyline> open_polylines;
- {
- // Build a map of lines by edge_a_id and a_id.
- std::vector<IntersectionLine*> by_edge_a_id;
- std::vector<IntersectionLine*> by_a_id;
- by_edge_a_id.reserve(lines.size());
- by_a_id.reserve(lines.size());
- for (IntersectionLine &line : lines) {
- if (! line.skip) {
- if (line.edge_a_id != -1)
- by_edge_a_id.emplace_back(&line);
- if (line.a_id != -1)
- by_a_id.emplace_back(&line);
+ }
+ auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; };
+ auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; };
+ std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
+ std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
+ // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines.
+ IntersectionLines::iterator it_line_seed = lines.begin();
+ for (;;) {
+ // take first spare line and start a new loop
+ IntersectionLine *first_line = nullptr;
+ for (; it_line_seed != lines.end(); ++ it_line_seed)
+ if (it_line_seed->is_seed_candidate()) {
+ //if (! it_line_seed->skip()) {
+ first_line = &(*it_line_seed ++);
+ break;
}
- }
- auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; };
- auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; };
- std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
- std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
- // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines.
- IntersectionLines::iterator it_line_seed = lines.begin();
+ if (first_line == nullptr)
+ break;
+ first_line->set_skip();
+ Points loop_pts;
+ loop_pts.emplace_back(first_line->a);
+ IntersectionLine *last_line = first_line;
+
+ /*
+ printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
+ first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id,
+ first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y);
+ */
+
+ IntersectionLine key;
for (;;) {
- // take first spare line and start a new loop
- IntersectionLine *first_line = nullptr;
- for (; it_line_seed != lines.end(); ++ it_line_seed)
- if (! it_line_seed->skip) {
- first_line = &(*it_line_seed ++);
- break;
- }
- if (first_line == nullptr)
- break;
- first_line->skip = true;
- Points loop_pts;
- loop_pts.emplace_back(first_line->a);
- IntersectionLine *last_line = first_line;
-
- /*
- printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
- first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id,
- first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y);
- */
-
- IntersectionLine key;
- for (;;) {
- // find a line starting where last one finishes
- IntersectionLine* next_line = nullptr;
- if (last_line->edge_b_id != -1) {
- key.edge_a_id = last_line->edge_b_id;
- auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower);
- if (it_begin != by_edge_a_id.end()) {
- auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower);
- for (auto it_line = it_begin; it_line != it_end; ++ it_line)
- if (! (*it_line)->skip) {
- next_line = *it_line;
- break;
- }
- }
+ // find a line starting where last one finishes
+ IntersectionLine* next_line = nullptr;
+ if (last_line->edge_b_id != -1) {
+ key.edge_a_id = last_line->edge_b_id;
+ auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower);
+ if (it_begin != by_edge_a_id.end()) {
+ auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower);
+ for (auto it_line = it_begin; it_line != it_end; ++ it_line)
+ if (! (*it_line)->skip()) {
+ next_line = *it_line;
+ break;
+ }
}
- if (next_line == nullptr && last_line->b_id != -1) {
- key.a_id = last_line->b_id;
- auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower);
- if (it_begin != by_a_id.end()) {
- auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower);
- for (auto it_line = it_begin; it_line != it_end; ++ it_line)
- if (! (*it_line)->skip) {
- next_line = *it_line;
- break;
- }
- }
+ }
+ if (next_line == nullptr && last_line->b_id != -1) {
+ key.a_id = last_line->b_id;
+ auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower);
+ if (it_begin != by_a_id.end()) {
+ auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower);
+ for (auto it_line = it_begin; it_line != it_end; ++ it_line)
+ if (! (*it_line)->skip()) {
+ next_line = *it_line;
+ break;
+ }
}
- if (next_line == nullptr) {
- // Check whether we closed this loop.
- if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
- (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
- // The current loop is complete. Add it to the output.
- loops->emplace_back(std::move(loop_pts));
- #ifdef SLIC3R_TRIANGLEMESH_DEBUG
- printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
- #endif
- } else {
- // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later.
- loop_pts.emplace_back(last_line->b);
- open_polylines.emplace_back(OpenPolyline(
- IntersectionReference(first_line->a_id, first_line->edge_a_id),
- IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts)));
- }
- break;
+ }
+ if (next_line == nullptr) {
+ // Check whether we closed this loop.
+ if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
+ (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
+ // The current loop is complete. Add it to the output.
+ loops.emplace_back(std::move(loop_pts));
+ #ifdef SLIC3R_TRIANGLEMESH_DEBUG
+ printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
+ #endif
+ } else {
+ // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later.
+ loop_pts.emplace_back(last_line->b);
+ open_polylines.emplace_back(OpenPolyline(
+ IntersectionReference(first_line->a_id, first_line->edge_a_id),
+ IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts)));
}
- /*
- printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
- next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
- next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
- */
- loop_pts.emplace_back(next_line->a);
- last_line = next_line;
- next_line->skip = true;
+ break;
}
+ /*
+ printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
+ next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
+ next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
+ */
+ loop_pts.emplace_back(next_line->a);
+ last_line = next_line;
+ next_line->set_skip();
}
}
+}
- // Now process the open polylines.
- if (! open_polylines.empty()) {
- // Store the end points of open_polylines into vectors sorted
- struct OpenPolylineEnd {
- OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
- OpenPolyline *polyline;
- // Is it the start or end point?
- bool start;
- const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; }
- int point_id() const { return ipref().point_id; }
- int edge_id () const { return ipref().edge_id; }
- };
- auto by_edge_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.edge_id() < ope2.edge_id(); };
- auto by_point_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.point_id() < ope2.point_id(); };
- std::vector<OpenPolylineEnd> by_edge_id;
- std::vector<OpenPolylineEnd> by_point_id;
- by_edge_id.reserve(2 * open_polylines.size());
- by_point_id.reserve(2 * open_polylines.size());
- for (OpenPolyline &opl : open_polylines) {
- if (opl.start.edge_id != -1)
- by_edge_id .emplace_back(OpenPolylineEnd(&opl, true));
- if (opl.end.edge_id != -1)
- by_edge_id .emplace_back(OpenPolylineEnd(&opl, false));
- if (opl.start.point_id != -1)
- by_point_id.emplace_back(OpenPolylineEnd(&opl, true));
- if (opl.end.point_id != -1)
- by_point_id.emplace_back(OpenPolylineEnd(&opl, false));
+std::vector<OpenPolyline*> open_polylines_sorted(std::vector<OpenPolyline> &open_polylines, bool update_lengths)
+{
+ std::vector<OpenPolyline*> out;
+ out.reserve(open_polylines.size());
+ for (OpenPolyline &opl : open_polylines)
+ if (! opl.consumed) {
+ if (update_lengths)
+ opl.length = Slic3r::length(opl.points);
+ out.emplace_back(&opl);
}
- std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower);
- std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower);
+ std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; });
+ return out;
+}
- // Try to connect the loops.
- for (OpenPolyline &opl : open_polylines) {
- if (opl.consumed)
- continue;
- opl.consumed = true;
- OpenPolylineEnd end(&opl, false);
- for (;;) {
- // find a line starting where last one finishes
- OpenPolylineEnd* next_start = nullptr;
- if (end.edge_id() != -1) {
- auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower);
- if (it_begin != by_edge_id.end()) {
- auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower);
- for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge)
- if (! it_edge->polyline->consumed) {
- next_start = &(*it_edge);
- break;
- }
- }
- }
- if (next_start == nullptr && end.point_id() != -1) {
- auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower);
- if (it_begin != by_point_id.end()) {
- auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower);
- for (auto it_point = it_begin; it_point != it_end; ++ it_point)
- if (! it_point->polyline->consumed) {
- next_start = &(*it_point);
- break;
- }
- }
+// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices.
+// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation.
+static void chain_open_polylines_exact(std::vector<OpenPolyline> &open_polylines, Polygons &loops, bool try_connect_reversed)
+{
+ // Store the end points of open_polylines into vectors sorted
+ struct OpenPolylineEnd {
+ OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
+ OpenPolyline *polyline;
+ // Is it the start or end point?
+ bool start;
+ const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; }
+ // Return a unique ID for the intersection point.
+ // Return a positive id for a point, or a negative id for an edge.
+ int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; }
+ bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; }
+ };
+ auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); };
+ std::vector<OpenPolylineEnd> by_id;
+ by_id.reserve(2 * open_polylines.size());
+ for (OpenPolyline &opl : open_polylines) {
+ if (opl.start.point_id != -1 || opl.start.edge_id != -1)
+ by_id.emplace_back(OpenPolylineEnd(&opl, true));
+ if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1))
+ by_id.emplace_back(OpenPolylineEnd(&opl, false));
+ }
+ std::sort(by_id.begin(), by_id.end(), by_id_lower);
+ // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute).
+ auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector<OpenPolylineEnd>::iterator {
+ for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower);
+ it != by_id.end() && it->id() == end.id(); ++ it)
+ if (*it == end)
+ return it;
+ return by_id.end();
+ };
+ // Try to connect the loops.
+ std::vector<OpenPolyline*> sorted_by_length = open_polylines_sorted(open_polylines, false);
+ for (OpenPolyline *opl : sorted_by_length) {
+ if (opl->consumed)
+ continue;
+ opl->consumed = true;
+ OpenPolylineEnd end(opl, false);
+ for (;;) {
+ // find a line starting where last one finishes
+ auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower);
+ for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start)
+ if (! it_next_start->polyline->consumed)
+ goto found;
+ // The current loop could not be closed. Unmark the segment.
+ opl->consumed = false;
+ break;
+ found:
+ // Attach this polyline to the end of the initial polyline.
+ if (it_next_start->start) {
+ auto it = it_next_start->polyline->points.begin();
+ std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points));
+ } else {
+ auto it = it_next_start->polyline->points.rbegin();
+ std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points));
+ }
+ opl->length += it_next_start->polyline->length;
+ // Mark the next polyline as consumed.
+ it_next_start->polyline->points.clear();
+ it_next_start->polyline->length = 0.;
+ it_next_start->polyline->consumed = true;
+ if (try_connect_reversed) {
+ // Running in a mode, where the polylines may be connected by mixing their orientations.
+ // Update the end point lookup structure after the end point of the current polyline was extended.
+ auto it_end = find_polyline_end(end);
+ auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start));
+ // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag.
+ std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end);
+ // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions.
+ std::swap(*it_end, *it_next_end);
+ }
+ // Check whether we closed this loop.
+ if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) ||
+ (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) {
+ // The current loop is complete. Add it to the output.
+ //assert(opl->points.front().point_id == opl->points.back().point_id);
+ //assert(opl->points.front().edge_id == opl->points.back().edge_id);
+ // Remove the duplicate last point.
+ opl->points.pop_back();
+ if (opl->points.size() >= 3) {
+ if (try_connect_reversed && area(opl->points) < 0)
+ // The closed polygon is patched from pieces with messed up orientation, therefore
+ // the orientation of the patched up polygon is not known.
+ // Orient the patched up polygons CCW. This heuristic may close some holes and cavities.
+ std::reverse(opl->points.begin(), opl->points.end());
+ loops.emplace_back(std::move(opl->points));
}
- if (next_start == nullptr) {
- // The current loop could not be closed. Unmark the segment.
- opl.consumed = false;
- break;
- }
- // Attach this polyline to the end of the initial polyline.
- if (next_start->start) {
- auto it = next_start->polyline->points.begin();
- std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points));
- //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end());
+ opl->points.clear();
+ break;
+ }
+ // Continue with the current loop.
+ }
+ }
+}
+
+// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices,
+// possibly closing small gaps.
+// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation.
+static void chain_open_polylines_close_gaps(std::vector<OpenPolyline> &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed)
+{
+ const coord_t max_gap_scaled = (coord_t)scale_(max_gap);
+
+ // Sort the open polylines by their length, so the new loops will be seeded from longer chains.
+ // Update the polyline lengths, return only not yet consumed polylines.
+ std::vector<OpenPolyline*> sorted_by_length = open_polylines_sorted(open_polylines, true);
+
+ // Store the end points of open_polylines into ClosestPointInRadiusLookup<OpenPolylineEnd>.
+ struct OpenPolylineEnd {
+ OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
+ OpenPolyline *polyline;
+ // Is it the start or end point?
+ bool start;
+ const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); }
+ bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; }
+ };
+ struct OpenPolylineEndAccessor {
+ const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); }
+ };
+ typedef ClosestPointInRadiusLookup<OpenPolylineEnd, OpenPolylineEndAccessor> ClosestPointLookupType;
+ ClosestPointLookupType closest_end_point_lookup(max_gap_scaled);
+ for (OpenPolyline *opl : sorted_by_length) {
+ closest_end_point_lookup.insert(OpenPolylineEnd(opl, true));
+ if (try_connect_reversed)
+ closest_end_point_lookup.insert(OpenPolylineEnd(opl, false));
+ }
+ // Try to connect the loops.
+ for (OpenPolyline *opl : sorted_by_length) {
+ if (opl->consumed)
+ continue;
+ OpenPolylineEnd end(opl, false);
+ if (try_connect_reversed)
+ // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it.
+ closest_end_point_lookup.erase(end);
+ opl->consumed = true;
+ size_t n_segments_joined = 1;
+ for (;;) {
+ // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed).
+ std::pair<const OpenPolylineEnd*, double> next_start_and_dist = closest_end_point_lookup.find(end.point());
+ const OpenPolylineEnd *next_start = next_start_and_dist.first;
+ // Check whether we closed this loop.
+ double current_loop_closing_distance2 = opl->points.front().distance_to_sq(opl->points.back());
+ bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled);
+ if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) {
+ // Heuristics to decide, whether to close the loop, or connect another polyline.
+ // One should avoid closing loops shorter than max_gap_scaled.
+ loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points);
+ }
+ if (loop_closed) {
+ // Remove the start point of the current polyline from the lookup.
+ // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail.
+ opl->consumed = false;
+ closest_end_point_lookup.erase(OpenPolylineEnd(opl, true));
+ if (current_loop_closing_distance2 == 0.) {
+ // Remove the duplicate last point.
+ opl->points.pop_back();
} else {
- auto it = next_start->polyline->points.rbegin();
- std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points));
- //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.rend());
+ // The end points are different, keep both of them.
}
- end = *next_start;
- end.start = !end.start;
- next_start->polyline->points.clear();
- next_start->polyline->consumed = true;
- // Check whether we closed this loop.
- const IntersectionReference &ip1 = opl.start;
- const IntersectionReference &ip2 = end.ipref();
- if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) ||
- (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) {
- // The current loop is complete. Add it to the output.
- assert(opl.points.front().point_id == opl.points.back().point_id);
- assert(opl.points.front().edge_id == opl.points.back().edge_id);
- // Remove the duplicate last point.
- opl.points.pop_back();
- if (opl.points.size() >= 3) {
+ if (opl->points.size() >= 3) {
+ if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0)
// The closed polygon is patched from pieces with messed up orientation, therefore
// the orientation of the patched up polygon is not known.
// Orient the patched up polygons CCW. This heuristic may close some holes and cavities.
- double area = 0.;
- for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++)
- area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y);
- if (area < 0)
- std::reverse(opl.points.begin(), opl.points.end());
- loops->emplace_back(std::move(opl.points));
- }
- opl.points.clear();
- break;
+ std::reverse(opl->points.begin(), opl->points.end());
+ loops.emplace_back(std::move(opl->points));
}
- // Continue with the current loop.
+ opl->points.clear();
+ opl->consumed = true;
+ break;
}
+ if (next_start == nullptr) {
+ // The current loop could not be closed. Unmark the segment.
+ opl->consumed = false;
+ if (try_connect_reversed)
+ // Re-insert the end point.
+ closest_end_point_lookup.insert(OpenPolylineEnd(opl, false));
+ break;
+ }
+ // Attach this polyline to the end of the initial polyline.
+ if (next_start->start) {
+ auto it = next_start->polyline->points.begin();
+ if (*it == opl->points.back())
+ ++ it;
+ std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points));
+ } else {
+ auto it = next_start->polyline->points.rbegin();
+ if (*it == opl->points.back())
+ ++ it;
+ std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points));
+ }
+ ++ n_segments_joined;
+ // Remove the end points of the consumed polyline segment from the lookup.
+ OpenPolyline *opl2 = next_start->polyline;
+ closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true));
+ if (try_connect_reversed)
+ closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false));
+ opl2->points.clear();
+ opl2->consumed = true;
+ // Continue with the current loop.
}
}
}
+void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const
+{
+#if 0
+//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
+//#ifdef _DEBUG
+ for (const Line &l : lines)
+ assert(l.a != l.b);
+#endif /* _DEBUG */
+
+ // There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane,
+ // only the bottom triangle is considered to be cutting the plane.
+// remove_tangent_edges(lines);
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ BoundingBox bbox_svg;
+ {
+ static int iRun = 0;
+ for (const Line &line : lines) {
+ bbox_svg.merge(line.a);
+ bbox_svg.merge(line.b);
+ }
+ SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg);
+ for (const Line &line : lines)
+ svg.draw(line);
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ std::vector<OpenPolyline> open_polylines;
+ chain_lines_by_triangle_connectivity(lines, *loops, open_polylines);
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ static int iRun = 0;
+ SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg);
+ svg.draw(union_ex(*loops));
+ for (const OpenPolyline &pl : open_polylines)
+ svg.draw(Polyline(pl.points), "red");
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // Now process the open polylines.
+ // Do it in two rounds, first try to connect in the same direction only,
+ // then try to connect the open polylines in reversed order as well.
+ chain_open_polylines_exact(open_polylines, *loops, false);
+ chain_open_polylines_exact(open_polylines, *loops, true);
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ static int iRun = 0;
+ SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg);
+ svg.draw(union_ex(*loops));
+ for (const OpenPolyline &pl : open_polylines) {
+ if (pl.points.empty())
+ continue;
+ svg.draw(Polyline(pl.points), "red");
+ svg.draw(pl.points.front(), "blue");
+ svg.draw(pl.points.back(), "blue");
+ }
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // Try to close gaps.
+ // Do it in two rounds, first try to connect in the same direction only,
+ // then try to connect the open polylines in reversed order as well.
+ const double max_gap = 2.; //mm
+ chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false);
+ chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true);
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ static int iRun = 0;
+ SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg);
+ svg.draw(union_ex(*loops));
+ for (const OpenPolyline &pl : open_polylines) {
+ if (pl.points.empty())
+ continue;
+ svg.draw(Polyline(pl.points), "red");
+ svg.draw(pl.points.front(), "blue");
+ svg.draw(pl.points.back(), "blue");
+ }
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+}
+
// Only used to cut the mesh into two halves.
void TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &lines, ExPolygons* slices) const
{
@@ -1544,7 +1836,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
// intersect facet with cutting plane
IntersectionLine line;
- if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line)) {
+ if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) {
// Save intersection lines for generating correct triangulations.
if (line.edge_type == feTop) {
lower_lines.push_back(line);
diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
index 72e541afc..24e903c0a 100644
--- a/xs/src/libslic3r/TriangleMesh.hpp
+++ b/xs/src/libslic3r/TriangleMesh.hpp
@@ -82,7 +82,7 @@ private:
enum FacetEdgeType {
// A general case, the cutting plane intersect a face at two different edges.
- feNone,
+ feGeneral,
// Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
feTop,
// Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
@@ -116,6 +116,14 @@ public:
class IntersectionLine : public Line
{
public:
+ IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {}
+
+ bool skip() const { return (this->flags & SKIP) != 0; }
+ void set_skip() { this->flags |= SKIP; }
+
+ bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); }
+ void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; }
+
// Inherits Point a, b
// For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1.
// Vertex indices of the line end points.
@@ -124,11 +132,23 @@ public:
// Source mesh edges of the line end points.
int edge_a_id;
int edge_b_id;
- // feNone, feTop, feBottom, feHorizontal
+ // feGeneral, feTop, feBottom, feHorizontal
FacetEdgeType edge_type;
- // Used by TriangleMeshSlicer::make_loops() to skip duplicate edges.
- bool skip;
- IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {};
+ // Used by TriangleMeshSlicer::slice() to skip duplicate edges.
+ enum {
+ // Triangle edge added, because it has no neighbor.
+ EDGE0_NO_NEIGHBOR = 0x001,
+ EDGE1_NO_NEIGHBOR = 0x002,
+ EDGE2_NO_NEIGHBOR = 0x004,
+ // Triangle edge added, because it makes a fold with another horizontal edge.
+ EDGE0_FOLD = 0x010,
+ EDGE1_FOLD = 0x020,
+ EDGE2_FOLD = 0x040,
+ // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds).
+ NO_SEED = 0x100,
+ SKIP = 0x200,
+ };
+ uint32_t flags;
};
typedef std::vector<IntersectionLine> IntersectionLines;
typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
@@ -139,7 +159,12 @@ public:
TriangleMeshSlicer(TriangleMesh* _mesh);
void slice(const std::vector<float> &z, std::vector<Polygons>* layers) const;
void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const;
- bool slice_facet(float slice_z, const stl_facet &facet, const int facet_idx,
+ enum FacetSliceType {
+ NoSlice = 0,
+ Slicing = 1,
+ Cutting = 2
+ };
+ FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx,
const float min_z, const float max_z, IntersectionLine *line_out) const;
void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const;
diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp
index 349222854..9334573e8 100644
--- a/xs/src/libslic3r/Utils.hpp
+++ b/xs/src/libslic3r/Utils.hpp
@@ -9,6 +9,7 @@ namespace Slic3r {
extern void set_logging_level(unsigned int level);
extern void trace(unsigned int level, const char *message);
+extern void disable_multi_threading();
// Set a path with GUI resource files.
void set_var_dir(const std::string &path);
@@ -43,6 +44,11 @@ extern local_encoded_string encode_path(const char *src);
extern std::string decode_path(const char *src);
extern std::string normalize_utf8_nfc(const char *src);
+// Safely rename a file even if the target exists.
+// On Windows, the file explorer (or anti-virus or whatever else) often locks the file
+// for a short while, so the file may not be movable. Retry while we see recoverable errors.
+extern int rename_file(const std::string &from, const std::string &to);
+
// File path / name / extension splitting utilities, working with UTF-8,
// to be published to Perl.
namespace PerlUtils {
diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h
index 7dc86ff33..2917ef576 100644
--- a/xs/src/libslic3r/libslic3r.h
+++ b/xs/src/libslic3r/libslic3r.h
@@ -14,7 +14,7 @@
#include <boost/thread.hpp>
#define SLIC3R_FORK_NAME "Slic3r++"
-#define SLIC3R_VERSION "1.41.0"
+#define SLIC3R_VERSION "1.41.1"
#define SLIC3R_BUILD "UNKNOWN"
typedef int32_t coord_t;
diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp
index 55164bbdd..45a39bbad 100644
--- a/xs/src/libslic3r/utils.cpp
+++ b/xs/src/libslic3r/utils.cpp
@@ -23,6 +23,9 @@
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/integration/filesystem.hpp>
#include <boost/nowide/convert.hpp>
+#include <boost/nowide/cstdio.hpp>
+
+#include <tbb/task_scheduler_init.h>
namespace Slic3r {
@@ -82,6 +85,14 @@ void trace(unsigned int level, const char *message)
(::boost::log::keywords::severity = severity)) << message;
}
+void disable_multi_threading()
+{
+ // Disable parallelization so the Shiny profiler works
+ static tbb::task_scheduler_init *tbb_init = nullptr;
+ if (tbb_init == nullptr)
+ tbb_init = new tbb::task_scheduler_init(1);
+}
+
static std::string g_var_dir;
void set_var_dir(const std::string &dir)
@@ -139,6 +150,68 @@ const std::string& data_dir()
return g_data_dir;
}
+// borrowed from LLVM lib/Support/Windows/Path.inc
+int rename_file(const std::string &from, const std::string &to)
+{
+ int ec = 0;
+
+#ifdef _WIN32
+
+ // Convert to utf-16.
+ std::wstring wide_from = boost::nowide::widen(from);
+ std::wstring wide_to = boost::nowide::widen(to);
+
+ // Retry while we see recoverable errors.
+ // System scanners (eg. indexer) might open the source file when it is written
+ // and closed.
+ bool TryReplace = true;
+
+ // This loop may take more than 2000 x 1ms to finish.
+ for (int i = 0; i < 2000; ++ i) {
+ if (i > 0)
+ // Sleep 1ms
+ ::Sleep(1);
+ if (TryReplace) {
+ // Try ReplaceFile first, as it is able to associate a new data stream
+ // with the destination even if the destination file is currently open.
+ if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL))
+ return 0;
+ DWORD ReplaceError = ::GetLastError();
+ ec = -1; // ReplaceError
+ // If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or
+ // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW().
+ if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT ||
+ ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) {
+ TryReplace = false;
+ continue;
+ }
+ // If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry
+ // using ReplaceFileW().
+ if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED)
+ continue;
+ // We get ERROR_FILE_NOT_FOUND if the destination file is missing.
+ // MoveFileEx can handle this case.
+ if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION)
+ break;
+ }
+ if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING))
+ return 0;
+ DWORD MoveError = ::GetLastError();
+ ec = -1; // MoveError
+ if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION)
+ break;
+ }
+
+#else
+
+ boost::nowide::remove(to.c_str());
+ ec = boost::nowide::rename(from.c_str(), to.c_str());
+
+#endif
+
+ return ec;
+}
+
} // namespace Slic3r
#include <xsinit.h>
diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp
index 58858f5fc..7bd044b25 100644
--- a/xs/src/slic3r/AppController.cpp
+++ b/xs/src/slic3r/AppController.cpp
@@ -43,15 +43,6 @@ namespace GUI {
PresetBundle* get_preset_bundle();
}
-static const PrintObjectStep STEP_SLICE = posSlice;
-static const PrintObjectStep STEP_PERIMETERS = posPerimeters;
-static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill;
-static const PrintObjectStep STEP_INFILL = posInfill;
-static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial;
-static const PrintStep STEP_SKIRT = psSkirt;
-static const PrintStep STEP_BRIM = psBrim;
-static const PrintStep STEP_WIPE_TOWER = psWipeTower;
-
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::global_progress_indicator() {
ProgresIndicatorPtr ret;
@@ -71,193 +62,8 @@ void AppControllerBoilerplate::global_progress_indicator(
pri_data_->m.unlock();
}
-void PrintController::make_skirt()
-{
- assert(print_ != nullptr);
-
- // prerequisites
- for(auto obj : print_->objects) make_perimeters(obj);
- for(auto obj : print_->objects) infill(obj);
- for(auto obj : print_->objects) gen_support_material(obj);
-
- if(!print_->state.is_done(STEP_SKIRT)) {
- print_->state.set_started(STEP_SKIRT);
- print_->skirt.clear();
- if(print_->has_skirt()) print_->_make_skirt();
-
- print_->state.set_done(STEP_SKIRT);
- }
-}
-
-void PrintController::make_brim()
-{
- assert(print_ != nullptr);
-
- // prerequisites
- for(auto obj : print_->objects) make_perimeters(obj);
- for(auto obj : print_->objects) infill(obj);
- for(auto obj : print_->objects) gen_support_material(obj);
- make_skirt();
-
- if(!print_->state.is_done(STEP_BRIM)) {
- print_->state.set_started(STEP_BRIM);
-
- // since this method must be idempotent, we clear brim paths *before*
- // checking whether we need to generate them
- print_->brim.clear();
-
- if(print_->config.brim_width > 0) print_->_make_brim();
-
- print_->state.set_done(STEP_BRIM);
- }
-}
-
-void PrintController::make_wipe_tower()
-{
- assert(print_ != nullptr);
-
- // prerequisites
- for(auto obj : print_->objects) make_perimeters(obj);
- for(auto obj : print_->objects) infill(obj);
- for(auto obj : print_->objects) gen_support_material(obj);
- make_skirt();
- make_brim();
-
- if(!print_->state.is_done(STEP_WIPE_TOWER)) {
- print_->state.set_started(STEP_WIPE_TOWER);
-
- // since this method must be idempotent, we clear brim paths *before*
- // checking whether we need to generate them
- print_->brim.clear();
-
- if(print_->has_wipe_tower()) print_->_make_wipe_tower();
-
- print_->state.set_done(STEP_WIPE_TOWER);
- }
-}
-
-void PrintController::slice(PrintObject *pobj)
-{
- assert(pobj != nullptr && print_ != nullptr);
-
- if(pobj->state.is_done(STEP_SLICE)) return;
-
- pobj->state.set_started(STEP_SLICE);
-
- pobj->_slice();
-
- auto msg = pobj->_fix_slicing_errors();
- if(!msg.empty()) report_issue(IssueType::WARN, msg);
-
- // simplify slices if required
- if (print_->config.resolution)
- pobj->_simplify_slices(scale_(print_->config.resolution));
-
-
- if(pobj->layers.empty())
- report_issue(IssueType::ERR,
- _(L("No layers were detected. You might want to repair your "
- "STL file(s) or check their size or thickness and retry"))
- );
-
- pobj->state.set_done(STEP_SLICE);
-}
-
-void PrintController::make_perimeters(PrintObject *pobj)
-{
- assert(pobj != nullptr);
-
- slice(pobj);
-
- if (!pobj->state.is_done(STEP_PERIMETERS)) {
- pobj->_make_perimeters();
- }
-}
-
-void PrintController::infill(PrintObject *pobj)
-{
- assert(pobj != nullptr);
-
- make_perimeters(pobj);
-
- if (!pobj->state.is_done(STEP_PREPARE_INFILL)) {
- pobj->state.set_started(STEP_PREPARE_INFILL);
-
- pobj->_prepare_infill();
-
- pobj->state.set_done(STEP_PREPARE_INFILL);
- }
-
- pobj->_infill();
-}
-
-void PrintController::gen_support_material(PrintObject *pobj)
-{
- assert(pobj != nullptr);
-
- // prerequisites
- slice(pobj);
-
- if(!pobj->state.is_done(STEP_SUPPORTMATERIAL)) {
- pobj->state.set_started(STEP_SUPPORTMATERIAL);
-
- pobj->clear_support_layers();
-
- if((pobj->config.support_material || pobj->config.raft_layers > 0)
- && pobj->layers.size() > 1) {
- pobj->_generate_support_material();
- }
-
- pobj->state.set_done(STEP_SUPPORTMATERIAL);
- }
-}
-
-void PrintController::slice(AppControllerBoilerplate::ProgresIndicatorPtr pri)
-{
- auto st = pri->state();
-
- Slic3r::trace(3, "Starting the slicing process.");
-
- pri->update(st+20, _(L("Generating perimeters")));
- for(auto obj : print_->objects) make_perimeters(obj);
-
- pri->update(st+60, _(L("Infilling layers")));
- for(auto obj : print_->objects) infill(obj);
-
- pri->update(st+70, _(L("Generating support material")));
- for(auto obj : print_->objects) gen_support_material(obj);
-
- pri->message_fmt(_(L("Weight: %.1fg, Cost: %.1f")),
- print_->total_weight, print_->total_cost);
- pri->state(st+85);
-
-
- pri->update(st+88, _(L("Generating skirt")));
- make_skirt();
-
-
- pri->update(st+90, _(L("Generating brim")));
- make_brim();
-
- pri->update(st+95, _(L("Generating wipe tower")));
- make_wipe_tower();
-
- pri->update(st+100, _(L("Done")));
-
- // time to make some statistics..
-
- Slic3r::trace(3, _(L("Slicing process finished.")));
-}
-
-void PrintController::slice()
-{
- auto pri = global_progress_indicator();
- if(!pri) pri = create_progress_indicator(100, L("Slicing"));
- slice(pri);
-}
-
-void IProgressIndicator::message_fmt(
- const string &fmtstr, ...) {
+void ProgressIndicator::message_fmt(
+ const std::string &fmtstr, ...) {
std::stringstream ss;
va_list args;
va_start(args, fmtstr);
@@ -321,30 +127,34 @@ void AppController::arrange_model()
for(auto& v : bedpoints)
bed.append(Point::new_scale(v.x, v.y));
- if(pind) pind->update(0, _(L("Arranging objects...")));
+ if(pind) pind->update(0, L("Arranging objects..."));
try {
+ arr::BedShapeHint hint;
+ // TODO: from Sasha from GUI
+ hint.type = arr::BedShapeType::WHO_KNOWS;
+
arr::arrange(*model_,
min_obj_distance,
bed,
- arr::BOX,
+ hint,
false, // create many piles not just one pile
[pind, count](unsigned rem) {
if(pind)
- pind->update(count - rem, _(L("Arranging objects...")));
+ pind->update(count - rem, L("Arranging objects..."));
});
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
report_issue(IssueType::ERR,
- _(L("Could not arrange model objects! "
- "Some geometries may be invalid.")),
- _(L("Exception occurred")));
+ L("Could not arrange model objects! "
+ "Some geometries may be invalid."),
+ L("Exception occurred"));
}
// Restore previous max value
if(pind) {
pind->max(pmax);
- pind->update(0, _(L("Arranging done.")));
+ pind->update(0, L("Arranging done."));
}
});
diff --git a/xs/src/slic3r/AppController.hpp b/xs/src/slic3r/AppController.hpp
index e9252b50c..3ef47ffdc 100644
--- a/xs/src/slic3r/AppController.hpp
+++ b/xs/src/slic3r/AppController.hpp
@@ -7,7 +7,7 @@
#include <atomic>
#include <iostream>
-#include "IProgressIndicator.hpp"
+#include "ProgressIndicator.hpp"
namespace Slic3r {
@@ -15,7 +15,8 @@ class Model;
class Print;
class PrintObject;
class PrintConfig;
-
+class ProgressStatusBar;
+class DynamicPrintConfig;
/**
* @brief A boilerplate class for creating application logic. It should provide
@@ -33,7 +34,7 @@ class AppControllerBoilerplate {
public:
/// A Progress indicator object smart pointer
- using ProgresIndicatorPtr = std::shared_ptr<IProgressIndicator>;
+ using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>;
private:
class PriData; // Some structure to store progress indication data
@@ -46,7 +47,7 @@ public:
AppControllerBoilerplate();
~AppControllerBoilerplate();
- using Path = string;
+ using Path = std::string;
using PathList = std::vector<Path>;
/// Common runtime issue types
@@ -67,20 +68,20 @@ public:
* @return Returns a list of paths choosed by the user.
*/
PathList query_destination_paths(
- const string& title,
+ const std::string& title,
const std::string& extensions) const;
/**
* @brief Same as query_destination_paths but works for directories only.
*/
PathList query_destination_dirs(
- const string& title) const;
+ const std::string& title) const;
/**
* @brief Same as query_destination_paths but returns only one path.
*/
Path query_destination_path(
- const string& title,
+ const std::string& title,
const std::string& extensions,
const std::string& hint = "") const;
@@ -95,11 +96,11 @@ public:
* title.
*/
bool report_issue(IssueType issuetype,
- const string& description,
- const string& brief);
+ const std::string& description,
+ const std::string& brief);
bool report_issue(IssueType issuetype,
- const string& description);
+ const std::string& description);
/**
* @brief Return the global progress indicator for the current controller.
@@ -150,12 +151,12 @@ protected:
*/
ProgresIndicatorPtr create_progress_indicator(
unsigned statenum,
- const string& title,
- const string& firstmsg) const;
+ const std::string& title,
+ const std::string& firstmsg) const;
ProgresIndicatorPtr create_progress_indicator(
unsigned statenum,
- const string& title) const;
+ const std::string& title) const;
// This is a global progress indicator placeholder. In the Slic3r UI it can
// contain the progress indicator on the statusbar.
@@ -167,24 +168,6 @@ protected:
*/
class PrintController: public AppControllerBoilerplate {
Print *print_ = nullptr;
-protected:
-
- void make_skirt();
- void make_brim();
- void make_wipe_tower();
-
- void make_perimeters(PrintObject *pobj);
- void infill(PrintObject *pobj);
- void gen_support_material(PrintObject *pobj);
-
- /**
- * @brief Slice one pront object.
- * @param pobj The print object.
- */
- void slice(PrintObject *pobj);
-
- void slice(ProgresIndicatorPtr pri);
-
public:
// Must be public for perl to use it
@@ -199,11 +182,6 @@ public:
return PrintController::Ptr( new PrintController(print) );
}
- /**
- * @brief Slice the loaded print scene.
- */
- void slice();
-
const PrintConfig& config() const;
};
@@ -248,7 +226,7 @@ public:
* In perl we have a progress indicating status bar on the bottom of the
* window which is defined and created in perl. We can pass the ID-s of the
* gauge and the statusbar id and make a wrapper implementation of the
- * IProgressIndicator interface so we can use this GUI widget from C++.
+ * ProgressIndicator interface so we can use this GUI widget from C++.
*
* This function should be called from perl.
*
diff --git a/xs/src/slic3r/AppControllerWx.cpp b/xs/src/slic3r/AppControllerWx.cpp
index 4e116c7b9..36a465919 100644
--- a/xs/src/slic3r/AppControllerWx.cpp
+++ b/xs/src/slic3r/AppControllerWx.cpp
@@ -32,11 +32,11 @@ void AppControllerBoilerplate::process_events()
AppControllerBoilerplate::PathList
AppControllerBoilerplate::query_destination_paths(
- const string &title,
+ const std::string &title,
const std::string &extensions) const
{
- wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
+ wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
dlg.SetWildcard(extensions);
dlg.ShowModal();
@@ -52,11 +52,11 @@ AppControllerBoilerplate::query_destination_paths(
AppControllerBoilerplate::Path
AppControllerBoilerplate::query_destination_path(
- const string &title,
+ const std::string &title,
const std::string &extensions,
const std::string& hint) const
{
- wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
+ wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
dlg.SetWildcard(extensions);
dlg.SetFilename(hint);
@@ -71,8 +71,8 @@ AppControllerBoilerplate::query_destination_path(
}
bool AppControllerBoilerplate::report_issue(IssueType issuetype,
- const string &description,
- const string &brief)
+ const std::string &description,
+ const std::string &brief)
{
auto icon = wxICON_INFORMATION;
auto style = wxOK|wxCENTRE;
@@ -84,15 +84,15 @@ bool AppControllerBoilerplate::report_issue(IssueType issuetype,
case IssueType::FATAL: icon = wxICON_ERROR;
}
- auto ret = wxMessageBox(description, brief, icon | style);
+ auto ret = wxMessageBox(_(description), _(brief), icon | style);
return ret != wxCANCEL;
}
bool AppControllerBoilerplate::report_issue(
AppControllerBoilerplate::IssueType issuetype,
- const string &description)
+ const std::string &description)
{
- return report_issue(issuetype, description, string());
+ return report_issue(issuetype, description, std::string());
}
wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
@@ -104,10 +104,10 @@ namespace {
* the main thread as well.
*/
class GuiProgressIndicator:
- public IProgressIndicator, public wxEvtHandler {
+ public ProgressIndicator, public wxEvtHandler {
wxProgressDialog gauge_;
- using Base = IProgressIndicator;
+ using Base = ProgressIndicator;
wxString message_;
int range_; wxString title_;
bool is_asynch_ = false;
@@ -136,8 +136,8 @@ public:
/// Get the mode of parallel operation.
inline bool asynch() const { return is_asynch_; }
- inline GuiProgressIndicator(int range, const string& title,
- const string& firstmsg) :
+ inline GuiProgressIndicator(int range, const wxString& title,
+ const wxString& firstmsg) :
gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(),
wxPD_APP_MODAL | wxPD_AUTO_HIDE),
message_(firstmsg),
@@ -151,11 +151,6 @@ public:
this, id_);
}
- virtual void cancel() override {
- update(max(), "Abort");
- IProgressIndicator::cancel();
- }
-
virtual void state(float val) override {
state(static_cast<unsigned>(val));
}
@@ -170,26 +165,28 @@ public:
} else _state(st);
}
- virtual void message(const string & msg) override {
- message_ = msg;
+ virtual void message(const std::string & msg) override {
+ message_ = _(msg);
}
- virtual void messageFmt(const string& fmt, ...) {
+ virtual void messageFmt(const std::string& fmt, ...) {
va_list arglist;
va_start(arglist, fmt);
- message_ = wxString::Format(wxString(fmt), arglist);
+ message_ = wxString::Format(_(fmt), arglist);
va_end(arglist);
}
- virtual void title(const string & title) override {
- title_ = title;
+ virtual void title(const std::string & title) override {
+ title_ = _(title);
}
};
}
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::create_progress_indicator(
- unsigned statenum, const string& title, const string& firstmsg) const
+ unsigned statenum,
+ const std::string& title,
+ const std::string& firstmsg) const
{
auto pri =
std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg);
@@ -202,20 +199,20 @@ AppControllerBoilerplate::create_progress_indicator(
}
AppControllerBoilerplate::ProgresIndicatorPtr
-AppControllerBoilerplate::create_progress_indicator(unsigned statenum,
- const string &title) const
+AppControllerBoilerplate::create_progress_indicator(
+ unsigned statenum, const std::string &title) const
{
- return create_progress_indicator(statenum, title, string());
+ return create_progress_indicator(statenum, title, std::string());
}
namespace {
// A wrapper progress indicator class around the statusbar created in perl.
-class Wrapper: public IProgressIndicator, public wxEvtHandler {
+class Wrapper: public ProgressIndicator, public wxEvtHandler {
wxGauge *gauge_;
wxStatusBar *stbar_;
- using Base = IProgressIndicator;
- std::string message_;
+ using Base = ProgressIndicator;
+ wxString message_;
AppControllerBoilerplate& ctl_;
void showProgress(bool show = true) {
@@ -223,7 +220,7 @@ class Wrapper: public IProgressIndicator, public wxEvtHandler {
}
void _state(unsigned st) {
- if( st <= IProgressIndicator::max() ) {
+ if( st <= ProgressIndicator::max() ) {
Base::state(st);
if(!gauge_->IsShown()) showProgress(true);
@@ -266,7 +263,7 @@ public:
virtual void max(float val) override {
if(val > 1.0) {
gauge_->SetRange(static_cast<int>(val));
- IProgressIndicator::max(val);
+ ProgressIndicator::max(val);
}
}
@@ -280,18 +277,18 @@ public:
}
}
- virtual void message(const string & msg) override {
- message_ = msg;
+ virtual void message(const std::string & msg) override {
+ message_ = _(msg);
}
- virtual void message_fmt(const string& fmt, ...) override {
+ virtual void message_fmt(const std::string& fmt, ...) override {
va_list arglist;
va_start(arglist, fmt);
- message_ = wxString::Format(fmt, arglist);
+ message_ = wxString::Format(_(fmt), arglist);
va_end(arglist);
}
- virtual void title(const string & /*title*/) override {}
+ virtual void title(const std::string & /*title*/) override {}
};
}
diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
index 705347094..1901aa3a7 100644
--- a/xs/src/slic3r/GUI/3DScene.cpp
+++ b/xs/src/slic3r/GUI/3DScene.cpp
@@ -622,7 +622,7 @@ std::vector<int> GLVolumeCollection::load_object(
const ModelVolume *model_volume = model_object->volumes[volume_idx];
int extruder_id = -1;
- if (!model_volume->modifier)
+ if (model_volume->is_model_part())
{
extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0;
if (extruder_id == 0)
@@ -635,7 +635,16 @@ std::vector<int> GLVolumeCollection::load_object(
volumes_idx.push_back(int(this->volumes.size()));
float color[4];
memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
- color[3] = model_volume->modifier ? 0.5f : 1.f;
+ if (model_volume->is_support_blocker()) {
+ color[0] = 1.0f;
+ color[1] = 0.2f;
+ color[2] = 0.2f;
+ } else if (model_volume->is_support_enforcer()) {
+ color[0] = 0.2f;
+ color[1] = 0.2f;
+ color[2] = 1.0f;
+ }
+ color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back();
if (use_VBOs)
@@ -658,15 +667,15 @@ std::vector<int> GLVolumeCollection::load_object(
else if (drag_by == "instance")
v.drag_group_id = obj_idx * 1000 + instance_idx;
- if (!model_volume->modifier)
+ if (model_volume->is_model_part())
{
v.set_convex_hull(model_volume->get_convex_hull());
v.layer_height_texture = layer_height_texture;
if (extruder_id != -1)
v.extruder_id = extruder_id;
}
- v.is_modifier = model_volume->modifier;
- v.shader_outside_printer_detection_enabled = !model_volume->modifier;
+ v.is_modifier = ! model_volume->is_model_part();
+ v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
v.set_origin(Pointf3(instance->offset.x, instance->offset.y, 0.0));
v.set_angle_z(instance->rotation);
v.set_scale_factor(instance->scaling_factor);
@@ -1182,7 +1191,7 @@ static void thick_lines_to_indexed_vertex_array(
b1_prev = b1;
v_prev = v;
- if (bottom_z_different)
+ if (bottom_z_different && (closed || (!is_first && !is_last)))
{
// Found a change of the layer thickness -> Add a cap at the beginning of this segment.
volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]);
@@ -1190,10 +1199,10 @@ static void thick_lines_to_indexed_vertex_array(
if (! closed) {
// Terminate open paths with caps.
- if (is_first && !bottom_z_different)
+ if (is_first)
volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]);
// We don't use 'else' because both cases are true if we have only one line.
- if (is_last && !bottom_z_different)
+ if (is_last)
volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]);
}
diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp
index 2a33cd733..c2ae0bf0b 100644
--- a/xs/src/slic3r/GUI/AppConfig.cpp
+++ b/xs/src/slic3r/GUI/AppConfig.cpp
@@ -16,6 +16,8 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#include <boost/format.hpp>
+
namespace Slic3r {
@@ -60,6 +62,14 @@ void AppConfig::set_defaults()
if (get("remember_output_path").empty())
set("remember_output_path", "1");
+
+ // Remove legacy window positions/sizes
+ erase("", "main_frame_maximized");
+ erase("", "main_frame_pos");
+ erase("", "main_frame_size");
+ erase("", "object_settings_maximized");
+ erase("", "object_settings_pos");
+ erase("", "object_settings_size");
}
void AppConfig::load()
@@ -117,8 +127,14 @@ void AppConfig::load()
void AppConfig::save()
{
+ // The config is first written to a file with a PID suffix and then moved
+ // to avoid race conditions with multiple instances of Slic3r
+
+ const auto path = config_path();
+ std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
+
boost::nowide::ofstream c;
- c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc);
+ c.open(path_pid, std::ios::out | std::ios::trunc);
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
// Make sure the "no" category is written first.
for (const std::pair<std::string, std::string> &kvp : m_storage[""])
@@ -147,6 +163,9 @@ void AppConfig::save()
}
}
c.close();
+
+ rename_file(path_pid, path);
+
m_dirty = false;
}
diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp
index b742176ed..5af635a12 100644
--- a/xs/src/slic3r/GUI/AppConfig.hpp
+++ b/xs/src/slic3r/GUI/AppConfig.hpp
@@ -72,6 +72,14 @@ public:
bool has(const std::string &key) const
{ return this->has("", key); }
+ void erase(const std::string &section, const std::string &key)
+ {
+ auto it = m_storage.find(section);
+ if (it != m_storage.end()) {
+ it->second.erase(key);
+ }
+ }
+
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp
index 43faf941a..c024f7f13 100644
--- a/xs/src/slic3r/GUI/ConfigWizard.cpp
+++ b/xs/src/slic3r/GUI/ConfigWizard.cpp
@@ -411,11 +411,10 @@ PageFirmware::PageFirmware(ConfigWizard *parent) :
void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
{
- ConfigOptionEnum<GCodeFlavor> opt;
-
auto sel = gcode_picker->GetSelection();
- if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) {
- config.set_key_value("gcode_flavor", &opt);
+ if (sel >= 0 && sel < gcode_opt.enum_labels.size()) {
+ auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
+ config.set_key_value("gcode_flavor", opt);
}
}
@@ -873,10 +872,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) :
// If the screen is smaller, resize wizrad to match, which will enable scrollbars.
auto wizard_size = GetSize();
unsigned width, height;
- GUI::get_current_screen_size(width, height);
- wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN)));
- wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN)));
- SetMinSize(wizard_size);
+ if (GUI::get_current_screen_size(this, width, height)) {
+ wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN)));
+ wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN)));
+ SetMinSize(wizard_size);
+ }
Fit();
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });
diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp
index d0cd9f8cf..d5ac64d90 100644
--- a/xs/src/slic3r/GUI/FirmwareDialog.cpp
+++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp
@@ -367,7 +367,7 @@ void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
- return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT;
+ return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT;
}), ports.end());
if (ports.size() == 1) {
@@ -390,23 +390,22 @@ void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
void FirmwareDialog::priv::lookup_port_mmu()
{
+ static const auto msg_not_found =
+ "The Multi Material Control device was not found.\n"
+ "If the device is connected, please press the Reset button next to the USB connector ...";
+
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
- return port.id_vendor != USB_VID_PRUSA &&
+ return port.id_vendor != USB_VID_PRUSA ||
port.id_product != USB_PID_MMU_BOOT &&
port.id_product != USB_PID_MMU_APP;
}), ports.end());
if (ports.size() == 0) {
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
-
- queue_status(_(L(
- "The Multi Material Control device was not found.\n"
- "If the device is connected, please press the Reset button next to the USB connector ..."
- )));
-
+ queue_status(_(L(msg_not_found)));
wait_for_mmu_bootloader(30);
} else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
@@ -417,6 +416,13 @@ void FirmwareDialog::priv::lookup_port_mmu()
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
mmu_reboot(ports[0]);
wait_for_mmu_bootloader(10);
+
+ if (! port) {
+ // The device in bootloader mode was not found, inform the user and wait some more...
+ BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ...";
+ queue_status(_(L(msg_not_found)));
+ wait_for_mmu_bootloader(30);
+ }
} else {
port = ports[0];
}
@@ -702,7 +708,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
panel->SetSizer(vsizer);
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
- p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY);
+ p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr,
+ "Hex files (*.hex)|*.hex|All files|*.*");
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY);
diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
index 1897416d9..ff4ebd280 100644
--- a/xs/src/slic3r/GUI/GUI.cpp
+++ b/xs/src/slic3r/GUI/GUI.cpp
@@ -7,6 +7,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
#if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h>
@@ -828,7 +829,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
double brim_width = config->opt_float("brim_width");
if (boost::any_cast<bool>(value) == true)
{
- new_val = m_brim_width == 0.0 ? 10 :
+ new_val = m_brim_width == 0.0 ? 5 :
m_brim_width < 0.0 ? m_brim_width * (-1) :
m_brim_width;
}
@@ -976,14 +977,66 @@ int get_export_option(wxFileDialog* dlg)
}
-void get_current_screen_size(unsigned &width, unsigned &height)
+bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height)
{
- wxDisplay display(wxDisplay::GetFromWindow(g_wxMainFrame));
+ const auto idx = wxDisplay::GetFromWindow(window);
+ if (idx == wxNOT_FOUND) {
+ return false;
+ }
+
+ wxDisplay display(idx);
const auto disp_size = display.GetClientArea();
width = disp_size.GetWidth();
height = disp_size.GetHeight();
+
+ return true;
+}
+
+void save_window_size(wxTopLevelWindow *window, const std::string &name)
+{
+ const wxSize size = window->GetSize();
+ const wxPoint pos = window->GetPosition();
+ const auto maximized = window->IsMaximized() ? "1" : "0";
+
+ g_AppConfig->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str());
+ g_AppConfig->set((boost::format("window_%1%_maximized") % name).str(), maximized);
+}
+
+void restore_window_size(wxTopLevelWindow *window, const std::string &name)
+{
+ // XXX: This still doesn't behave nicely in some situations (mostly on Linux).
+ // The problem is that it's hard to obtain window position with respect to screen geometry reliably
+ // from wxWidgets. Sometimes wxWidgets claim a window is located on a different screen than on which
+ // it's actually visible. I suspect this has something to do with window initialization (maybe we
+ // restore window geometry too early), but haven't yet found a workaround.
+
+ const auto display_idx = wxDisplay::GetFromWindow(window);
+ if (display_idx == wxNOT_FOUND) { return; }
+
+ const auto display = wxDisplay(display_idx).GetClientArea();
+ std::vector<std::string> pair;
+
+ try {
+ const auto key_size = (boost::format("window_%1%_size") % name).str();
+ if (g_AppConfig->has(key_size)) {
+ if (unescape_strings_cstyle(g_AppConfig->get(key_size), pair) && pair.size() == 2) {
+ auto width = boost::lexical_cast<int>(pair[0]);
+ auto height = boost::lexical_cast<int>(pair[1]);
+
+ window->SetSize(width, height);
+ }
+ }
+ } catch(const boost::bad_lexical_cast &) {}
+
+ // Maximizing should be the last thing to do.
+ // This ensure the size and position are sane when the user un-maximizes the window.
+ const auto key_maximized = (boost::format("window_%1%_maximized") % name).str();
+ if (g_AppConfig->get(key_maximized) == "1") {
+ window->Maximize(true);
+ }
}
+
void about()
{
AboutDialog dlg;
diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp
index 165288819..68dbdfe84 100644
--- a/xs/src/slic3r/GUI/GUI.hpp
+++ b/xs/src/slic3r/GUI/GUI.hpp
@@ -24,6 +24,7 @@ class wxBoxSizer;
class wxFlexGridSizer;
class wxButton;
class wxFileDialog;
+class wxTopLevelWindow;
namespace Slic3r {
@@ -182,7 +183,12 @@ void add_export_option(wxFileDialog* dlg, const std::string& format);
int get_export_option(wxFileDialog* dlg);
// Returns the dimensions of the screen on which the main frame is displayed
-void get_current_screen_size(unsigned &width, unsigned &height);
+bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height);
+
+// Save window size and maximized status into AppConfig
+void save_window_size(wxTopLevelWindow *window, const std::string &name);
+// Restore the above
+void restore_window_size(wxTopLevelWindow *window, const std::string &name);
// Display an About dialog
extern void about();
diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
index 5c1463f8a..420fe267b 100644
--- a/xs/src/slic3r/GUI/Preset.cpp
+++ b/xs/src/slic3r/GUI/Preset.cpp
@@ -292,7 +292,7 @@ const std::vector<std::string>& Preset::print_options()
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height",
- "min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers",
+ "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
"support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",
@@ -420,7 +420,14 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
try {
Preset preset(m_type, name, false);
preset.file = dir_entry.path().string();
- preset.load(keys);
+ DynamicPrintConfig &config = preset.load(keys);
+ // Report configuration fields, which are misplaced into a wrong group.
+ std::string incorrect_keys;
+ if (config.remove_keys_not_in(this->default_preset().config, incorrect_keys) > 0)
+ BOOST_LOG_TRIVIAL(error) << "Error in \"" << dir_entry.path().string() << "\": The preset contains the following incorrect keys: " <<
+ incorrect_keys << ", which were ignored";
+ // Normalize once again to set the length of the filament specific vectors to 1.
+ Preset::normalize(config);
m_presets.emplace_back(preset);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp
index 94baa0e0e..e7ae4ebd9 100644
--- a/xs/src/slic3r/GUI/PresetBundle.cpp
+++ b/xs/src/slic3r/GUI/PresetBundle.cpp
@@ -919,24 +919,12 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
DynamicPrintConfig config(default_config);
for (auto &kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data());
- Preset::normalize(config);
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys;
- size_t n_incorrect_keys = 0;
- for (const std::string &key : config.keys())
- if (! default_config.has(key)) {
- if (incorrect_keys.empty())
- incorrect_keys = key;
- else {
- incorrect_keys += ", ";
- incorrect_keys += key;
- }
- config.erase(key);
- ++ n_incorrect_keys;
- }
- if (! incorrect_keys.empty())
+ if (config.remove_keys_not_in(default_config, incorrect_keys) > 0)
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
- section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
+ section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
+ Preset::normalize(config);
if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) {
// Filter out printer presets, which are not mentioned in the vendor profile.
// These presets are considered not installed.
diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
index e1ce5ecb5..d37bc5289 100644
--- a/xs/src/slic3r/GUI/Tab.cpp
+++ b/xs/src/slic3r/GUI/Tab.cpp
@@ -870,6 +870,7 @@ void TabPrint::build()
page = add_options_page(_(L("Support material")), "building.png");
optgroup = page->new_optgroup(_(L("Support material")));
optgroup->append_single_option_line("support_material");
+ optgroup->append_single_option_line("support_material_auto");
optgroup->append_single_option_line("support_material_threshold");
optgroup->append_single_option_line("support_material_enforce_layers");
@@ -1247,13 +1248,15 @@ void TabPrint::update()
bool have_raft = m_config->opt_int("raft_layers") > 0;
bool have_support_material = m_config->opt_bool("support_material") || have_raft;
+ bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto");
bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0;
bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0;
- for (auto el : {"support_material_threshold", "support_material_pattern", "support_material_with_sheath",
+ for (auto el : {"support_material_pattern", "support_material_with_sheath",
"support_material_spacing", "support_material_angle", "support_material_interface_layers",
"dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance",
"support_material_xy_spacing" })
get_field(el)->toggle(have_support_material);
+ get_field("support_material_threshold")->toggle(have_support_material_auto);
for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder",
"support_material_interface_speed", "support_material_interface_contact_loops" })
diff --git a/xs/src/slic3r/IProgressIndicator.hpp b/xs/src/slic3r/ProgressIndicator.hpp
index 704931574..1b064077e 100644
--- a/xs/src/slic3r/IProgressIndicator.hpp
+++ b/xs/src/slic3r/ProgressIndicator.hpp
@@ -3,16 +3,15 @@
#include <string>
#include <functional>
-#include "Strings.hpp"
namespace Slic3r {
/**
* @brief Generic progress indication interface.
*/
-class IProgressIndicator {
+class ProgressIndicator {
public:
- using CancelFn = std::function<void(void)>; // Cancel functio signature.
+ using CancelFn = std::function<void(void)>; // Cancel function signature.
private:
float state_ = .0f, max_ = 1.f, step_;
@@ -20,7 +19,7 @@ private:
public:
- inline virtual ~IProgressIndicator() {}
+ inline virtual ~ProgressIndicator() {}
/// Get the maximum of the progress range.
float max() const { return max_; }
@@ -28,14 +27,14 @@ public:
/// Get the current progress state
float state() const { return state_; }
- /// Set the maximum of hte progress range
+ /// Set the maximum of the progress range
virtual void max(float maxval) { max_ = maxval; }
/// Set the current state of the progress.
virtual void state(float val) { state_ = val; }
/**
- * @brief Number of states int the progress. Can be used insted of giving a
+ * @brief Number of states int the progress. Can be used instead of giving a
* maximum value.
*/
virtual void states(unsigned statenum) {
@@ -43,25 +42,19 @@ public:
}
/// Message shown on the next status update.
- virtual void message(const string&) = 0;
+ virtual void message(const std::string&) = 0;
- /// Title of the operaton.
- virtual void title(const string&) = 0;
+ /// Title of the operation.
+ virtual void title(const std::string&) = 0;
- /// Formatted message for the next status update. Works just like sprinf.
- virtual void message_fmt(const string& fmt, ...);
+ /// Formatted message for the next status update. Works just like sprintf.
+ virtual void message_fmt(const std::string& fmt, ...);
/// Set up a cancel callback for the operation if feasible.
- inline void on_cancel(CancelFn func) { cancelfunc_ = func; }
+ virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; }
- /**
- * Explicitly shut down the progress indicator and call the associated
- * callback.
- */
- virtual void cancel() { cancelfunc_(); }
-
- /// Convinience function to call message and status update in one function.
- void update(float st, const string& msg) {
+ /// Convenience function to call message and status update in one function.
+ void update(float st, const std::string& msg) {
message(msg); state(st);
}
};
diff --git a/xs/src/slic3r/Strings.hpp b/xs/src/slic3r/Strings.hpp
deleted file mode 100644
index b267fe064..000000000
--- a/xs/src/slic3r/Strings.hpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef STRINGS_HPP
-#define STRINGS_HPP
-
-#include "GUI/GUI.hpp"
-
-namespace Slic3r {
-using string = wxString;
-}
-
-#endif // STRINGS_HPP
diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
index 82a42eb71..f25327161 100644
--- a/xs/src/slic3r/Utils/Duet.cpp
+++ b/xs/src/slic3r/Utils/Duet.cpp
@@ -197,7 +197,7 @@ std::string Duet::get_upload_url(const std::string &filename) const
{
return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
% get_base_url()
- % filename
+ % Http::url_encode(filename)
% timestamp_str()).str();
}
@@ -248,9 +248,10 @@ wxString Duet::format_error(const std::string &body, const std::string &error, u
bool Duet::start_print(wxString &msg, const std::string &filename) const
{
bool res = false;
+
auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"")
% get_base_url()
- % filename).str();
+ % Http::url_encode(filename)).str();
auto http = Http::get(std::move(url));
http.on_error([&](std::string body, std::string error, unsigned status) {
@@ -275,5 +276,4 @@ int Duet::get_err_code_from_body(const std::string &body) const
return root.get<int>("err", 0);
}
-
}
diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp
index a92e399a0..9b67ceea8 100644
--- a/xs/src/slic3r/Utils/Http.cpp
+++ b/xs/src/slic3r/Utils/Http.cpp
@@ -421,6 +421,21 @@ bool Http::ca_file_supported()
return res;
}
+std::string Http::url_encode(const std::string &str)
+{
+ ::CURL *curl = ::curl_easy_init();
+ if (curl == nullptr) {
+ return str;
+ }
+ char *ce = ::curl_easy_escape(curl, str.c_str(), str.length());
+ std::string encoded = std::string(ce);
+
+ ::curl_free(ce);
+ ::curl_easy_cleanup(curl);
+
+ return encoded;
+}
+
std::ostream& operator<<(std::ostream &os, const Http::Progress &progress)
{
os << "Http::Progress("
diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp
index f1302b0ed..44580b7ea 100644
--- a/xs/src/slic3r/Utils/Http.hpp
+++ b/xs/src/slic3r/Utils/Http.hpp
@@ -98,6 +98,9 @@ public:
// Tells whether current backend supports seting up a CA file using ca_file()
static bool ca_file_supported();
+
+ // converts the given string to an url_encoded_string
+ static std::string url_encode(const std::string &str);
private:
Http(const std::string &url);
diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp
index 183119b44..601719b50 100644
--- a/xs/src/slic3r/Utils/Serial.cpp
+++ b/xs/src/slic3r/Utils/Serial.cpp
@@ -231,7 +231,12 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
spi.port = path;
#ifdef __linux__
auto friendly_name = sysfs_tty_prop(name, "product");
- spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path;
+ if (friendly_name) {
+ spi.is_printer = looks_like_printer(*friendly_name);
+ spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str();
+ } else {
+ spi.friendly_name = path;
+ }
auto vid = sysfs_tty_prop_hex(name, "idVendor");
auto pid = sysfs_tty_prop_hex(name, "idProduct");
if (vid && pid) {
diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t
index fd57cf805..4013a1f83 100644
--- a/xs/t/01_trianglemesh.t
+++ b/xs/t/01_trianglemesh.t
@@ -79,7 +79,9 @@ my $cube = {
my $m = Slic3r::TriangleMesh->new;
$m->ReadFromPerl($cube->{vertices}, $cube->{facets});
$m->repair;
- my @z = (0,2,4,8,6,8,10,12,14,16,18,20);
+ # The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be
+ # open intervals at the bottom end, closed at the top end.
+ my @z = (0.0001,2,4,8,6,8,10,12,14,16,18,20);
my $result = $m->slice(\@z);
my $SCALING_FACTOR = 0.000001;
for my $i (0..$#z) {
@@ -105,7 +107,9 @@ my $cube = {
# this second test also checks that performing a second slice on a mesh after
# a transformation works properly (shared_vertices is correctly invalidated);
# at Z = -10 we have a bottom horizontal surface
- my $slices = $m->slice([ -5, -10 ]);
+ # (The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be
+ # 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).
+ my $slices = $m->slice([ -5, -10+0.00001 ]);
is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area';
}
}
diff --git a/xs/xsp/AppController.xsp b/xs/xsp/AppController.xsp
index 1b653081d..a578fe0b1 100644
--- a/xs/xsp/AppController.xsp
+++ b/xs/xsp/AppController.xsp
@@ -8,10 +8,7 @@
%}
%name{Slic3r::PrintController} class PrintController {
-
PrintController(Print *print);
-
- void slice();
};
%name{Slic3r::AppController} class AppController {
diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp
index c6eead1ad..8d2efb858 100644
--- a/xs/xsp/GUI.xsp
+++ b/xs/xsp/GUI.xsp
@@ -111,3 +111,9 @@ void register_on_request_update_callback(SV* callback)
void deregister_on_request_update_callback()
%code%{ Slic3r::GUI::g_on_request_update_callback.deregister_callback(); %};
+void save_window_size(SV *window, std::string name)
+ %code%{ Slic3r::GUI::save_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %};
+
+void restore_window_size(SV *window, std::string name)
+ %code%{ Slic3r::GUI::restore_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %};
+
diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp
index 4f09fb521..efd6c9ae6 100644
--- a/xs/xsp/Layer.xsp
+++ b/xs/xsp/Layer.xsp
@@ -17,8 +17,6 @@
%code%{ RETVAL = &THIS->thin_fills; %};
Ref<SurfaceCollection> fill_surfaces()
%code%{ RETVAL = &THIS->fill_surfaces; %};
- Ref<SurfaceCollection> perimeter_surfaces()
- %code%{ RETVAL = &THIS->perimeter_surfaces; %};
Polygons bridged()
%code%{ RETVAL = THIS->bridged; %};
Ref<PolylineCollection> unsupported_bridge_edges()
diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp
index 182963257..0b59b3126 100644
--- a/xs/xsp/Model.xsp
+++ b/xs/xsp/Model.xsp
@@ -338,11 +338,23 @@ ModelMaterial::attributes()
%code%{ RETVAL = &THIS->config; %};
Ref<TriangleMesh> mesh()
%code%{ RETVAL = &THIS->mesh; %};
+ Ref<TriangleMesh> convex_hull()
+ %code%{ RETVAL = &THIS->get_convex_hull(); %};
bool modifier()
- %code%{ RETVAL = THIS->modifier; %};
+ %code%{ RETVAL = THIS->is_modifier(); %};
void set_modifier(bool modifier)
- %code%{ THIS->modifier = modifier; %};
+ %code%{ THIS->set_type(modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); %};
+ bool model_part()
+ %code%{ RETVAL = THIS->is_model_part(); %};
+ bool support_enforcer()
+ %code%{ RETVAL = THIS->is_support_enforcer(); %};
+ void set_support_enforcer()
+ %code%{ THIS->set_type(ModelVolume::SUPPORT_ENFORCER); %};
+ bool support_blocker()
+ %code%{ RETVAL = THIS->is_support_blocker(); %};
+ void set_support_blocker()
+ %code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %};
size_t split(unsigned int max_extruders);
diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
index 717064916..1dee8a4c4 100644
--- a/xs/xsp/Print.xsp
+++ b/xs/xsp/Print.xsp
@@ -277,6 +277,36 @@ Print::total_cost(...)
}
RETVAL = THIS->total_cost;
OUTPUT:
+ RETVAL
+
+double
+Print::total_wipe_tower_cost(...)
+ CODE:
+ if (items > 1) {
+ THIS->total_wipe_tower_cost = (double)SvNV(ST(1));
+ }
+ RETVAL = THIS->total_wipe_tower_cost;
+ OUTPUT:
+ RETVAL
+
+double
+Print::total_wipe_tower_filament(...)
+ CODE:
+ if (items > 1) {
+ THIS->total_wipe_tower_filament = (double)SvNV(ST(1));
+ }
+ RETVAL = THIS->total_wipe_tower_filament;
+ OUTPUT:
+ RETVAL
+
+int
+Print::m_wipe_tower_number_of_toolchanges(...)
+ CODE:
+ if (items > 1) {
+ THIS->m_wipe_tower_number_of_toolchanges = (double)SvNV(ST(1));
+ }
+ RETVAL = THIS->m_wipe_tower_number_of_toolchanges;
+ OUTPUT:
RETVAL
%}
};
diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp
index e900532aa..04969a7f9 100644
--- a/xs/xsp/XS.xsp
+++ b/xs/xsp/XS.xsp
@@ -49,6 +49,11 @@ trace(level, message)
Slic3r::trace(level, message);
void
+disable_multi_threading()
+ CODE:
+ Slic3r::disable_multi_threading();
+
+void
set_var_dir(dir)
char *dir;
CODE: