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 4032886f3..fd02a030f 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) = @_; diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index ea4ce7132..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( diff --git a/resources/icons/support_blocker.png b/resources/icons/support_blocker.png new file mode 100644 index 000000000..8a66c73f4 Binary files /dev/null and b/resources/icons/support_blocker.png differ diff --git a/resources/icons/support_enforcer.png b/resources/icons/support_enforcer.png new file mode 100644 index 000000000..c0bef8d5e Binary files /dev/null and b/resources/icons/support_enforcer.png differ diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp index 733ff2ad7..801a27e3a 100644 --- a/xs/src/libslic3r/EdgeGrid.cpp +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -1225,8 +1225,10 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo return true; } -Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const +Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) const { + assert(std::abs(2 * offset) < m_resolution); + typedef std::unordered_multimap EndPointMapType; // 0) Prepare a binary grid. size_t cell_rows = m_rows + 2; @@ -1237,7 +1239,7 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1); // Fill in empty cells, which have a left / right neighbor filled. // Fill in empty cells, which have the top / bottom neighbor filled. - { + if (fill_holes) { std::vector cell_inside2(cell_inside); for (int r = 1; r + 1 < int(cell_rows); ++ r) { for (int c = 1; c + 1 < int(cell_cols); ++ c) { diff --git a/xs/src/libslic3r/EdgeGrid.hpp b/xs/src/libslic3r/EdgeGrid.hpp index 3eb741865..ab1aa4ed0 100644 --- a/xs/src/libslic3r/EdgeGrid.hpp +++ b/xs/src/libslic3r/EdgeGrid.hpp @@ -58,7 +58,7 @@ public: const size_t cols() const { return m_cols; } // For supports: Contours enclosing the rasterized edges. - Polygons contours_simplified(coord_t offset) const; + Polygons contours_simplified(coord_t offset, bool fill_holes) const; protected: struct Cell { 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 ¢er) } template -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(const Point &item) const; diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 15363e8ed..504d264fe 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; } }; // Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging. @@ -218,18 +225,18 @@ public: bool make_clockwise(); bool make_counter_clockwise(); void reverse(); - Point first_point() const { return this->paths.front().polyline.points.front(); } - Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } + Point first_point() const override { return this->paths.front().polyline.points.front(); } + Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } Polygon polygon() const; - virtual double length() const; + double length() const override; bool split_at_vertex(const Point &point); void split_at(const Point &point, bool prefer_non_overhang); void clip_end(double distance, ExtrusionPaths* paths) const; // Test, whether the point is extruded by a bridging flow. // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. bool has_overhang_point(const Point &point) const; - virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } - ExtrusionLoopRole loop_role() const { return m_loop_role; } + ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } + ExtrusionLoopRole loop_role() const { return m_loop_role; } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; @@ -244,7 +251,8 @@ public: // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; Polyline as_polyline() const { return this->polygon().split_at_first_point(); } - virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } + void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); } + double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } private: ExtrusionLoopRole m_loop_role; diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 382455fe3..81582a94d 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 @@ -79,14 +79,20 @@ public: void flatten(ExtrusionEntityCollection* retval) const; ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; - virtual double total_volume() const {double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } + double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } // Following methods shall never be called on an ExtrusionEntityCollection. Polyline as_polyline() const { CONFESS("Calling as_polyline() on a ExtrusionEntityCollection"); return Polyline(); }; - virtual double length() const { + + void collect_polylines(Polylines &dst) const override { + for (ExtrusionEntity* extrusion_entity : this->entities) + extrusion_entity->collect_polylines(dst); + } + + double length() const override { CONFESS("Calling length() on a ExtrusionEntityCollection"); return 0.; } 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 5de1d26c5..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[] = @@ -1499,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); } @@ -2015,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 << " " << volume->config.serialize(key) << "\n"; if (!volume->name.empty()) stream << " " << xml_escape(volume->name) << "\n"; - if (volume->modifier) + if (volume->is_modifier()) stream << " 1\n"; + stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index f3b460443..620583128 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -21,45 +21,37 @@ class LayerRegion friend class Layer; public: - Layer* layer() { return this->_layer; } - const Layer* layer() const { return this->_layer; } - PrintRegion* region() { return this->_region; } - const PrintRegion* region() const { return this->_region; } + Layer* layer() { return this->_layer; } + const Layer* layer() const { return this->_layer; } + PrintRegion* region() { return this->_region; } + const PrintRegion* region() const { return this->_region; } - // collection of surfaces generated by slicing the original geometry - // divided by type top/bottom/internal - SurfaceCollection slices; - - // collection of extrusion paths/loops filling gaps - // These fills are generated by the perimeter generator. - // They are not printed on their own, but they are copied to this->fills during infill generation. - ExtrusionEntityCollection thin_fills; + // Collection of surfaces generated by slicing the original geometry, divided by type top/bottom/internal. + SurfaceCollection slices; // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") // and for re-starting of infills. - ExPolygons fill_expolygons; + ExPolygons fill_expolygons; // collection of surfaces for infill generation - SurfaceCollection fill_surfaces; + SurfaceCollection fill_surfaces; + // Collection of extrusion paths/loops filling gaps. + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. + ExtrusionEntityCollection thin_fills; - // Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces). - // While not necessary, the memory consumption is meager and it speeds up calculation. - // The perimeter_surfaces keep the IDs of the slices (top/bottom/) - SurfaceCollection perimeter_surfaces; - - // collection of expolygons representing the bridged areas (thus not - // needing support material) - Polygons bridged; + // Collection of expolygons representing the bridged areas (thus not needing support material). + //FIXME Not used as of now. + Polygons bridged; // collection of polylines representing the unsupported bridge edges - PolylineCollection unsupported_bridge_edges; + PolylineCollection unsupported_bridge_edges; - // ordered collection of extrusion paths/loops to build all perimeters - // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection perimeters; - - // ordered collection of extrusion paths to fill surfaces - // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection fills; + // Ordered collection of extrusion paths/loops to build all perimeters. + // This collection contains only ExtrusionEntityCollection objects. + ExtrusionEntityCollection perimeters; + // Ordered collection of extrusion paths to fill surfaces. + // This collection contains only ExtrusionEntityCollection objects. + ExtrusionEntityCollection fills; Flow flow(FlowRole role, bool bridge = false, double width = -1) const; void slices_to_fill_surfaces_clipped(); diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index 68e17407e..12526f2ec 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(); @@ -340,8 +338,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } -void -LayerRegion::prepare_fill_surfaces() +void LayerRegion::prepare_fill_surfaces() { #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); @@ -382,8 +379,7 @@ LayerRegion::prepare_fill_surfaces() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } -double -LayerRegion::infill_area_threshold() const +double LayerRegion::infill_area_threshold() const { double ss = this->flow(frSolidInfill).scaled_spacing(); return ss*ss; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index e5f4888a6..af764eb89 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -609,7 +609,7 @@ 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; @@ -641,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; } @@ -652,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)); } @@ -664,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; } @@ -675,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 @@ -779,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; } @@ -787,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; } @@ -803,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); @@ -855,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); @@ -869,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) { @@ -973,6 +973,37 @@ const TriangleMesh& ModelVolume::get_convex_hull() const return m_convex_hull; } +ModelVolume::Type ModelVolume::type_from_string(const std::string &s) +{ + // Legacy support + if (s == "0") + return MODEL_PART; + if (s == "1") + return PARAMETER_MODIFIER; + // New type (supporting the support enforcers & blockers) + if (s == "ModelPart") + return MODEL_PART; + if (s == "ParameterModifier") + return PARAMETER_MODIFIER; + if (s == "SupportEnforcer") + return SUPPORT_ENFORCER; + if (s == "SupportBlocker") + return SUPPORT_BLOCKER; +} + +std::string ModelVolume::type_to_string(const Type t) +{ + switch (t) { + case MODEL_PART: return "ModelPart"; + case PARAMETER_MODIFIER: return "ParameterModifier"; + case SUPPORT_ENFORCER: return "SupportEnforcer"; + case SUPPORT_BLOCKER: return "SupportBlocker"; + default: + assert(false); + return "ModelPart"; + } +} + // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index dadd515de..34a7c7cc1 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. @@ -184,24 +196,30 @@ public: void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; + // Helpers for loading / storing into AMF / 3MF files. + static Type type_from_string(const std::string &s); + static std::string type_to_string(const Type t); + private: // Parent object owning this ModelVolume. - ModelObject* object; - t_model_material_id _material_id; + ModelObject* object; + // Is it an object to be printed, or a modifier volume? + Type m_type; + t_model_material_id _material_id; - ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object) { if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } - ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {} + ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {} ModelVolume(ModelObject *object, const ModelVolume &other) : - name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object) + name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object) { this->material_id(other.material_id()); } ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : - name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object) + name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object) { this->material_id(other.material_id()); if (mesh.stl.stats.number_of_facets > 1) diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index 0970e9a67..f4f82a353 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; 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 3432506c6..05bd8c7fb 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -193,23 +193,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/Print.cpp b/xs/src/libslic3r/Print.cpp index eb2112ef0..c2fac891d 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -362,9 +362,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) @@ -379,6 +382,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. @@ -853,7 +857,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("extruder", true)->value = int(volume_id + 1); } } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 95b8abc5b..3ef720db3 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; @@ -211,6 +214,10 @@ public: bool is_printable() const { return !this->_shifted_copies.empty(); } + // Helpers to slice support enforcer / blocker meshes by the support generator. + std::vector slice_support_enforcers() const; + std::vector slice_support_blockers() const; + private: Print* _print; ModelObject* _model_object; @@ -222,6 +229,7 @@ private: ~PrintObject() {} std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); + std::vector _slice_volumes(const std::vector &z, const std::vector &volumes) const; }; typedef std::vector PrintObjectPtrs; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index f9f0b2056..32c3f5700 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1696,6 +1696,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"); @@ -1734,7 +1742,7 @@ 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()); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 932ed6054..89ba81124 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -323,6 +323,7 @@ public: ConfigOptionFloatOrPercent extrusion_width; ConfigOptionFloatOrPercent first_layer_height; ConfigOptionBool infill_only_where_needed; + // Force the generation of solid shells between adjacent materials/volumes. ConfigOptionBool interface_shells; ConfigOptionFloat layer_height; ConfigOptionInt raft_layers; @@ -330,6 +331,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; @@ -339,12 +343,15 @@ public: ConfigOptionBool support_material_interface_contact_loops; ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; + // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. ConfigOptionFloat support_material_interface_spacing; ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionEnum support_material_pattern; + // Spacing between support material lines (the hatching distance). ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; ConfigOptionBool support_material_synchronize_layers; + // Overhang angle threshold. ConfigOptionInt support_material_threshold; ConfigOptionBool support_material_with_sheath; ConfigOptionFloatOrPercent support_material_xy_spacing; @@ -367,6 +374,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); @@ -414,10 +422,12 @@ public: ConfigOptionInt infill_every_layers; ConfigOptionFloatOrPercent infill_overlap; ConfigOptionFloat infill_speed; + // Detect bridging perimeters ConfigOptionBool overhangs; ConfigOptionInt perimeter_extruder; ConfigOptionFloatOrPercent perimeter_extrusion_width; ConfigOptionFloat perimeter_speed; + // Total number of perimeters. ConfigOptionInt perimeters; ConfigOptionFloatOrPercent small_perimeter_speed; ConfigOptionFloat solid_infill_below_area; @@ -425,6 +435,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 7150ead59..ea6b39280 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -172,6 +172,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector PrintObject::_slice_region(size_t region_id, const std::vector &z, bool modifier) { - std::vector layers; + std::vector volumes; if (region_id < this->region_volumes.size()) { - std::vector &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 PrintObject::slice_support_enforcers() const +{ + std::vector volumes; + for (const ModelVolume *volume : this->model_object()->volumes) + if (volume->is_support_enforcer()) + volumes.emplace_back(volume); + std::vector 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 PrintObject::slice_support_blockers() const +{ + std::vector volumes; + for (const ModelVolume *volume : this->model_object()->volumes) + if (volume->is_support_blocker()) + volumes.emplace_back(volume); + std::vector 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 PrintObject::_slice_volumes(const std::vector &z, const std::vector &volumes) const +{ + std::vector 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 e9295d1e3..d3fbcc7cb 100644 --- a/xs/src/libslic3r/Slicing.cpp +++ b/xs/src/libslic3r/Slicing.cpp @@ -224,9 +224,9 @@ std::vector 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 0cecf0014..9019583b9 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 ®ion = *(*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 ®ion = *(*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,8 +450,8 @@ Polygons collect_slices_outer(const Layer &layer) { Polygons out; out.reserve(out.size() + layer.slices.expolygons.size()); - for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it) - out.push_back(it->contour); + for (const ExPolygon &expoly : layer.slices.expolygons) + out.emplace_back(expoly.contour); return out; } @@ -461,8 +459,11 @@ class SupportGridPattern { public: SupportGridPattern( + // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) const Polygons &support_polygons, - 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), @@ -485,7 +486,8 @@ public: m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); m_grid.calculate_sdf(); - // Extract a bounding contour from the grid, trim by the object. + // Sample a single point per input support polygon, keep it as a reference to maintain corresponding + // polygons if ever these polygons get split into parts by the trimming polygons. m_island_samples = island_samples(*m_support_polygons); } @@ -493,22 +495,22 @@ public: // and trim the extracted polygons by trimming_polygons. // Trimming by the trimming_polygons may split the extracted polygons into pieces. // Remove all the pieces, which do not contain any of the island_samples. - Polygons extract_support(const coord_t offset_in_grid) + Polygons extract_support(const coord_t offset_in_grid, bool fill_holes) { // Generate islands, so each island may be tested for overlap with m_island_samples. + assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); ExPolygons islands = diff_ex( - m_grid.contours_simplified(offset_in_grid), + m_grid.contours_simplified(offset_in_grid, fill_holes), *m_trimming_polygons, false); // Extract polygons, which contain some of the m_island_samples. Polygons out; - std::vector> 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> samples_inside; for (auto it = it_lower; it != it_upper; ++ it) if (bbox.contains(*it)) samples_inside.push_back(std::make_pair(*it, false)); @@ -569,8 +571,10 @@ public: private: SupportGridPattern& operator=(const SupportGridPattern &rhs); +#if 0 // Get some internal point of an expolygon, to be used as a representative // sample to test, whether this island is inside another island. + //FIXME this was quick, but not sufficiently robust. static Point island_sample(const ExPolygon &expoly) { // Find the lowest point lexicographically. @@ -591,7 +595,10 @@ private: double coef = 20. / sqrt(l2); return Point(p2.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,9 +636,164 @@ private: coordf_t m_support_spacing; Slic3r::EdgeGrid::Grid m_grid; + // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding + // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons. Points m_island_samples; }; +namespace SupportMaterialInternal { + static inline bool has_bridging_perimeters(const ExtrusionLoop &loop) + { + for (const ExtrusionPath &ep : loop.paths) + if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) + return ep.size() >= (ep.is_closed() ? 3 : 2); + return false; + } + static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters) + { + for (const ExtrusionEntity *ee : perimeters.entities) { + if (ee->is_collection()) { + for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { + assert(! ee2->is_collection()); + if (ee2->is_loop()) + if (has_bridging_perimeters(*static_cast(ee2))) + return true; + } + } else if (ee->is_loop() && has_bridging_perimeters(*static_cast(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(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(ee)->entities) { + assert(! ee2->is_collection()); + if (ee2->is_loop()) + collect_bridging_perimeter_areas(*static_cast(ee2), expansion_scaled, out); + } + } else if (ee->is_loop()) + collect_bridging_perimeter_areas(*static_cast(ee), expansion_scaled, out); + } + } + + static void remove_bridges_from_contacts( + const PrintConfig &print_config, + const Layer &lower_layer, + const Polygons &lower_layer_polygons, + LayerRegion *layerm, + float fw, + Polygons &contact_polygons) + { + // compute the area of bridging perimeters + Polygons bridges; + { + // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. + Polygons lower_grown_slices = offset(lower_layer_polygons, + //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1))), + SUPPORT_SURFACES_OFFSET_PARAMETERS); + // Collect perimeters of this layer. + //FIXME split_at_first_point() could split a bridge mid-way + #if 0 + Polylines overhang_perimeters = layerm->perimeters.as_polylines(); + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polyline &polyline : overhang_perimeters) + polyline.points[0].x += 1; + // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); + #else + Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices); + #endif + + // only consider straight overhangs + // only consider overhangs having endpoints inside layer's slices + // convert bridging polylines into polygons by inflating them with their thickness + // since we're dealing with bridges, we can't assume width is larger than spacing, + // so we take the largest value and also apply safety offset to be ensure no gaps + // are left in between + Flow bridge_flow = layerm->flow(frPerimeter, true); + float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); + for (Polyline &polyline : overhang_perimeters) + if (polyline.is_straight()) { + // This is a bridge + polyline.extend_start(fw); + polyline.extend_end(fw); + // Is the straight perimeter segment supported at both sides? + if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point())) + // Offset a polyline into a thick line. + polygons_append(bridges, offset(polyline, 0.5f * w + 10.f)); + } + bridges = union_(bridges); + } + // remove the entire bridges and only support the unsupported edges + //FIXME the brided regions are already collected as layerm->bridged. Use it? + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) + polygons_append(bridges, surface.expolygon); + //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? + contact_polygons = diff(contact_polygons, bridges, true); + // Add the bridge anchors into the region. + //FIXME add supports at regular intervals to support long bridges! + polygons_append(contact_polygons, + intersection( + // Offset unsupported edges into polygons. + offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), + bridges)); + } +} + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. @@ -643,9 +805,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ ++ iRun; #endif /* SLIC3R_DEBUG */ + // Slice support enforcers / support blockers. + std::vector enforcers = object.slice_support_enforcers(); + std::vector blockers = object.slice_support_blockers(); + // Output layers, sorted by top Z. MyLayersPtr contact_out; + const bool support_auto = m_object_config->support_material_auto.value; // If user specified a custom angle threshold, convert it to radians. // Zero means automatic overhang detection. const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ? @@ -680,10 +847,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. size_t num_layers = this->has_support() ? object.layer_count() : 1; - contact_out.assign(num_layers, nullptr); + // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, + // and the other for the overhangs extruded with a normal flow. + contact_out.assign(num_layers * 2, nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &buildplate_covered, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range& range) { + [this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out] + (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { const Layer &layer = *object.layers[layer_id]; @@ -694,6 +864,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ Polygons contact_polygons; Polygons slices_margin_cached; float slices_margin_cached_offset = -1.; + Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers[layer_id-1]->slices.expolygons); + // Offset of the lower layer, to trim the support polygons with to calculate dense supports. + float no_interface_offset = 0.f; if (layer_id == 0) { // This is the first object layer, so the object is being printed on a raft and // we're here just to get the object footprint for the raft. @@ -708,6 +881,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); + no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); float lower_layer_offset = (layer_id < this->m_object_config->support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. @@ -720,7 +894,6 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Overhang polygons for this layer and region. Polygons diff_polygons; Polygons layerm_polygons = to_polygons(layerm->slices); - Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons); if (lower_layer_offset == 0.f) { // Support everything. diff_polygons = diff(layerm_polygons, lower_layer_polygons); @@ -730,28 +903,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 (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); + if (! enforcers.empty()) { + // Apply the "support enforcers". + //FIXME add the "enforcers" to the sparse support regions only. + const ExPolygons &enforcer = enforcers[layer_id - 1]; + if (! enforcer.empty()) { + // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. + Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(enforcer)), + offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (! new_contacts.empty()) { + if (diff_polygons.empty()) + diff_polygons = std::move(new_contacts); + else + diff_polygons = union_(diff_polygons, new_contacts); + } + } + } + } + // Apply the "support blockers". + if (! diff_polygons.empty() && ! blockers.empty() && ! blockers[layer_id].empty()) { + // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. + diff_polygons = diff(diff_polygons, to_polygons(blockers[layer_id])); } if (diff_polygons.empty()) continue; - #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, @@ -762,73 +968,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) { - const ExtrusionEntityCollection *island = dynamic_cast(extrusion_entity); - assert(island != NULL); - for (size_t i = 0; i < island->entities.size(); ++ i) { - ExtrusionEntity *entity = island->entities[i]; - ExtrusionLoop *loop = dynamic_cast(entity); - overhang_perimeters.push_back(loop ? - loop->as_polyline() : - dynamic_cast(entity)->polyline); - } - } - - // 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; @@ -842,7 +984,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ union_ex(diff_polygons, false)); #endif /* SLIC3R_DEBUG */ - if (this->has_contact_loops()) + //FIXME the overhang_polygons are used to construct the support towers as well. + //if (this->has_contact_loops()) + // Store the exact contour of the overhang for the contact loops. polygons_append(overhang_polygons, diff_polygons); // Let's define the required contact area by using a max gap of half the upper @@ -851,12 +995,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // on the other side of the object (if it's very thin). { //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. + //FIXME one should trim with the layer span colliding with the support layer, this layer + // may be lower than lower_layer, so the support area needed may need to be actually bigger! + // For the same reason, the non-bridging support area may be smaller than the bridging support area! float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy))); if (slices_margin_cached_offset != slices_margin_offset) { slices_margin_cached_offset = slices_margin_offset; slices_margin_cached = (slices_margin_offset == 0.f) ? - to_polygons(lower_layer.slices.expolygons) : - offset(lower_layer.slices.expolygons, slices_margin_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS); + lower_layer_polygons : + offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! buildplate_covered.empty()) { // Trim the inflated contact surfaces by the top surfaces as well. polygons_append(slices_margin_cached, buildplate_covered[layer_id]); @@ -879,58 +1026,72 @@ 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; + } + } } } } @@ -940,27 +1101,112 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_polygons, // Trimming polygons, to trim the stretched support islands. slices_margin_cached, - // How much to offset the extracted contour outside of the grid. + // Grid resolution. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); - // 1) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); - // 2) Contact polygons will be projected down. To keep the interface and base layers to grow, return a contour a tiny bit smaller than the grid cells. - new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3)); + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true)); + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + if (layer_id == 0) { + // if (no_interface_offset == 0.f) { + new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true); + } else { + Polygons dense_interface_polygons = diff(overhang_polygons, + offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); +// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (! dense_interface_polygons.empty()) { + //FIXME do it for non-soluble support interfaces only. + //FIXME do it for the bridges only? + SupportGridPattern support_grid_pattern( + // Support islands, to be stretched into a grid. + dense_interface_polygons, + // Trimming polygons, to trim the stretched support islands. + slices_margin_cached, + // Grid resolution. + m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), + Geometry::deg2rad(m_object_config->support_material_angle.value)); + new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); + } + } // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. // Store the overhang polygons. // The overhang polygons are used in the path generator for planning of the contact loops. - // if (this->has_contact_loops()) + // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons)); - contact_out[layer_id] = &new_layer; + contact_out[layer_id * 2] = &new_layer; + if (bridging_layer != nullptr) { + bridging_layer->polygons = new_layer.polygons; + bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons); + bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons); + contact_out[layer_id * 2 + 1] = bridging_layer; + } } } }); + // Compress contact_out, remove the nullptr items. remove_nulls(contact_out); + // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. + std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; }); + + // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), + // the top contact layer is merged into the bottom contact layer. + { + int i = 0; + int k = 0; + { + // Find the span of layers, which are to be printed at the first layer height. + int j = 0; + for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); + if (j > 0) { + // Merge the contact_out layers (0) to (j - 1) into the contact_out[0]. + MyLayer &dst = *contact_out.front(); + for (int u = 1; u < j; ++ u) { + MyLayer &src = *contact_out[u]; + // The union_() does not support move semantic yet, but maybe one day it will. + dst.polygons = union_(dst.polygons, std::move(src.polygons)); + *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); + *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); + // Source polygon is no more needed, it will not be refrenced. Release its data. + src.reset(); + } + // Snap the first layer to the 1st layer height. + dst.print_z = m_slicing_params.first_print_layer_height; + dst.height = m_slicing_params.first_print_layer_height; + dst.bottom_z = 0; + ++ k; + } + i = j; + } + for (; i < int(contact_out.size()); ++ k) { + // Find the span of layers closer than m_support_layer_height_min. + int j = i + 1; + coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; + for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; + if (i + 1 < j) { + // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. + MyLayer &dst = *contact_out[i]; + for (int u = i + 1; u < j; ++ u) { + MyLayer &src = *contact_out[u]; + // The union_() does not support move semantic yet, but maybe one day it will. + dst.polygons = union_(dst.polygons, std::move(src.polygons)); + *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); + *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); + // Source polygon is no more needed, it will not be refrenced. Release its data. + src.reset(); + } + } + if (k < i) + contact_out[k] = contact_out[i]; + i = j; + } + if (k < contact_out.size()) + contact_out.erase(contact_out.begin() + k, contact_out.end()); + } + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; return contact_out; @@ -996,7 +1242,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; const Layer &layer = *object.get_layer(layer_id); // Collect projections of all contact areas above or at the same level as this top surface. - for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) { + for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { Polygons polygons_new; // Contact surfaces are expanded away from the object, trimmed by the object. // Use a slight positive offset to overlap the touching regions. @@ -1004,7 +1250,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON)); #else - // Consume the contact_polygons. The contact polygons are already expanded into a grid form. + // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller + // than the grid cells. polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons)); #endif // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. @@ -1016,9 +1263,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta continue; Polygons projection_raw = union_(projection); - // Top surfaces of this layer, to be used to stop the surface volume from growing down. tbb::task_group task_group; if (! m_object_config->support_material_buildplate_only) + // Find the bottom contact layers above the top surfaces of this layer. task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] { Polygons top = collect_region_slices_by_type(layer, stTop); #ifdef SLIC3R_DEBUG @@ -1046,28 +1293,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // Grow top surfaces so that interface and support generation are generated // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here + //FIXME calculate layer height based on the actual thickness of the layer: + // If the layer is extruded with no bridging flow, support just the normal extrusions. layer_new.height = m_slicing_params.soluble_interface ? // Align the interface layer with the object's layer height. object.layers[layer_id + 1]->height : // Place a bridge flow interface layer over the top surface. + //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?) + // According to Jindrich the bottom surfaces work well. + //FIXME test the bridging flow instead? m_support_material_interface_flow.nozzle_diameter; layer_new.print_z = m_slicing_params.soluble_interface ? object.layers[layer_id + 1]->print_z : layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; layer_new.bottom_z = layer.print_z; layer_new.idx_object_layer_below = layer_id; layer_new.bridging = ! m_slicing_params.soluble_interface; - //FIXME how much to inflate the top surface? + //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. + //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! m_slicing_params.soluble_interface) { // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. for (size_t top_idx = size_t(std::max(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); @@ -1091,10 +1344,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta union_ex(layer_new.polygons, false)); #endif /* SLIC3R_DEBUG */ // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. + //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? touching = offset(touching, float(SCALED_EPSILON)); for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { const Layer &layer_above = *object.layers[layer_id_above]; - if (layer_above.print_z > layer_new.print_z + EPSILON) + if (layer_above.print_z > layer_new.print_z - EPSILON) break; if (! layer_support_areas[layer_id_above].empty()) { #ifdef SLIC3R_DEBUG @@ -1147,7 +1401,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta projection, // Trimming polygons, to trim the stretched support islands. trimming, - // How much to offset the extracted contour outside of the grid. + // Grid spacing. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); tbb::task_group task_group_inner; @@ -1158,7 +1412,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta , &layer #endif /* SLIC3R_DEBUG */ ] { - layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25); + layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z), @@ -1172,7 +1426,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta , &layer #endif /* SLIC3R_DEBUG */ ] { - projection_new = support_grid_pattern.extract_support(-5); + projection_new = support_grid_pattern.extract_support(-5, true); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), @@ -1185,7 +1439,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta task_group.wait(); } std::reverse(bottom_contacts.begin(), bottom_contacts.end()); - trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); +// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy); + trim_support_layers_by_object(object, bottom_contacts, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + } // ! top_contacts.empty() return bottom_contacts; @@ -1502,9 +1760,6 @@ void PrintObjectSupportMaterial::generate_base_layers( assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z); // Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new. - idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, - [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); - // New polygons for layer_intermediate. Polygons polygons_new; @@ -1523,12 +1778,10 @@ void PrintObjectSupportMaterial::generate_base_layers( // 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here. // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. - int idx_top_contact_overlapping = idx_top_contact_above; - while (idx_top_contact_overlapping >= 0 && - top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) - -- idx_top_contact_overlapping; + idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, + [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); // Collect all the top_contact layer intersecting with this layer. - for (; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { + for ( int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; @@ -1608,7 +1861,10 @@ void PrintObjectSupportMaterial::generate_base_layers( ++ iRun; #endif /* SLIC3R_DEBUG */ - trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_gap_xy); +// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy); + this->trim_support_layers_by_object(object, intermediate_layers, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); } void PrintObjectSupportMaterial::trim_support_layers_by_object( @@ -1653,19 +1909,23 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( const Layer &object_layer = *object.layers[i]; if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) break; - polygons_append(polygons_trimming, (Polygons)object_layer.slices); + polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } if (! 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; @@ -1675,9 +1935,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( // perimeter's width. $support contains the full shape of support // material, thus including the width of its foremost extrusion. // We leave a gap equal to a full extrusion width. - support_layer.polygons = diff( - support_layer.polygons, - offset(polygons_trimming, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + support_layer.polygons = diff(support_layer.polygons, polygons_trimming); } }); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; @@ -1800,11 +2058,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_int coordf_t top_z = intermediate_layers[std::min(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(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z; // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_top_contact_projected; for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) { const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; + //FIXME maybe this adds one interface layer in excess? if (top_contact_layer.bottom_z - EPSILON > top_z) break; polygons_append(polygons_top_contact_projected, top_contact_layer.polygons); @@ -1861,8 +2120,8 @@ static inline void fill_expolygons_generate_paths( fill_params.density = density; fill_params.complete = true; fill_params.dont_adjust = true; - for (ExPolygons::const_iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { - Surface surface(stInternal, *it_expolygon); + for (const ExPolygon &expoly : expolygons) { + Surface surface(stInternal, expoly); extrusion_entities_append_paths( dst, filler->fill_surface(&surface, fill_params), @@ -1883,8 +2142,8 @@ static inline void fill_expolygons_generate_paths( fill_params.density = density; fill_params.complete = true; fill_params.dont_adjust = true; - for (ExPolygons::iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { - Surface surface(stInternal, std::move(*it_expolygon)); + for (ExPolygon &expoly : expolygons) { + Surface surface(stInternal, std::move(expoly)); extrusion_entities_append_paths( dst, filler->fill_surface(&surface, fill_params), @@ -2359,7 +2618,7 @@ void modulate_extrusion_by_overlapping_layers( (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); } private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&); + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {} const std::vector &m_path_fragments; }; const coord_t search_radius = 7; @@ -2711,6 +2970,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( continue; //FIXME When paralellizing, each thread shall have its own copy of the fillers. bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) Flow interface_flow( float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)), float(layer_ex.layer->height), 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/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 9cf55513c..1901aa3a7 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -622,7 +622,7 @@ std::vector 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 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 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); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index af2cd8ff6..cede4c707 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -292,7 +292,7 @@ const std::vector& Preset::print_options() "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", - "min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers", + "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6505e1092..9d265cfc4 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -847,6 +847,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"); @@ -1183,13 +1184,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/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 fill_surfaces() %code%{ RETVAL = &THIS->fill_surfaces; %}; - Ref perimeter_surfaces() - %code%{ RETVAL = &THIS->perimeter_surfaces; %}; Polygons bridged() %code%{ RETVAL = THIS->bridged; %}; Ref unsupported_bridge_edges() diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 182963257..0f9b5cd15 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -340,9 +340,19 @@ ModelMaterial::attributes() %code%{ RETVAL = &THIS->mesh; %}; bool modifier() - %code%{ RETVAL = THIS->modifier; %}; + %code%{ RETVAL = THIS->is_modifier(); %}; void set_modifier(bool modifier) - %code%{ THIS->modifier = modifier; %}; + %code%{ THIS->set_type(modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); %}; + bool model_part() + %code%{ RETVAL = THIS->is_model_part(); %}; + bool support_enforcer() + %code%{ RETVAL = THIS->is_support_enforcer(); %}; + void set_support_enforcer() + %code%{ THIS->set_type(ModelVolume::SUPPORT_ENFORCER); %}; + bool support_blocker() + %code%{ RETVAL = THIS->is_support_blocker(); %}; + void set_support_blocker() + %code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %}; size_t split(unsigned int max_extruders);