From 2f4fa41ce498cf04a2a62c3c14b86ef658d1d646 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 28 May 2015 18:44:19 +0200 Subject: [PATCH 01/83] Fixed regression introduced with recent fixes to UTF-8 preset names handling on Windows that prevented their load on OS X (TODO: test on Linux) --- lib/Slic3r.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index c3c21e782b..9fc1df029c 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -268,7 +268,11 @@ sub encode_path { sub decode_path { my ($path) = @_; - utf8::upgrade($path) if $^O eq 'MSWin32'; + if ($^O eq 'MSWin32') { + utf8::upgrade($path); + } else { + utf8::decode($path); + } return $path; } From 6e280ab8cbe0152e0211e858396b37fb0cbf2331 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 28 May 2015 18:56:35 +0200 Subject: [PATCH 02/83] Prompt user when using 100% infill with a pattern that does not support it --- lib/Slic3r/GUI/Tab.pm | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 230314004e..21e1babed0 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -431,6 +431,7 @@ sub set_value { package Slic3r::GUI::Tab::Print; use base 'Slic3r::GUI::Tab'; +use List::Util qw(first); use Wx qw(:icon :dialog :id); sub name { 'print' } @@ -738,6 +739,22 @@ sub _update { } } + if ($config->fill_density == 100 + && !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{external_fill_pattern}{values}}) { + my $dialog = Wx::MessageDialog->new($self, + "The " . $config->fill_pattern . " infill pattern is not supposed to work at 100% density.\n" + . "\nShall I switch to rectilinear fill pattern?", + 'Infill', wxICON_WARNING | wxYES | wxNO); + + my $new_conf = Slic3r::Config->new; + if ($dialog->ShowModal() == wxID_YES) { + $new_conf->set("fill_pattern", 1); + } else { + $new_conf->set("fill_density", 40); + } + $self->load_config($new_conf); + } + my $have_perimeters = $config->perimeters > 0; $self->get_field($_)->toggle($have_perimeters) for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first @@ -1295,7 +1312,7 @@ sub load_config_file { } package Slic3r::GUI::Tab::Page; -use Wx qw(:misc :panel :sizer); +use Wx qw(wxTheApp :misc :panel :sizer); use base 'Wx::ScrolledWindow'; sub new { @@ -1323,8 +1340,11 @@ sub new_optgroup { config => $self->GetParent->{config}, label_width => $params{label_width} // 200, on_change => sub { - $self->GetParent->update_dirty; - $self->GetParent->_on_value_change(@_); + my ($opt_key, $value) = @_; + wxTheApp->CallAfter(sub { + $self->GetParent->update_dirty; + $self->GetParent->_on_value_change($opt_key, $value); + }); }, ); From 7f70da97b46eaf61a88e985f4ff1b4954e3c5d69 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 31 May 2015 22:04:32 +0200 Subject: [PATCH 03/83] New experimental autospeed feature. #2810 --- lib/Slic3r/GCode.pm | 9 ++- lib/Slic3r/GUI/Tab.pm | 6 ++ lib/Slic3r/Print/GCode.pm | 55 ++++++++++++++++++- utils/post-processing/flowrate.pl | 20 ++++++- xs/src/libslic3r/ExtrusionEntity.cpp | 15 +++++ xs/src/libslic3r/ExtrusionEntity.hpp | 5 ++ .../libslic3r/ExtrusionEntityCollection.cpp | 32 +++++++++++ .../libslic3r/ExtrusionEntityCollection.hpp | 2 + xs/src/libslic3r/Print.hpp | 2 +- xs/src/libslic3r/PrintConfig.cpp | 16 ++++++ xs/src/libslic3r/PrintConfig.hpp | 6 ++ xs/xsp/ExtrusionEntityCollection.xsp | 6 ++ 12 files changed, 165 insertions(+), 9 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 5398be0a86..86f532589a 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -27,6 +27,7 @@ has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => p has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } ); +has 'volumetric_speed' => (is => 'rw', default => sub {0}); sub apply_print_config { my ($self, $print_config) = @_; @@ -298,11 +299,13 @@ sub _extrude_path { die "Invalid speed"; } } - my $F = $speed * 60; # convert mm/sec to mm/min - if ($self->first_layer) { - $F = $self->config->get_abs_value_over('first_layer_speed', $F/60) * 60; + $speed = $self->config->get_abs_value_over('first_layer_speed', $speed); } + if ($self->volumetric_speed != 0) { + $speed ||= $self->volumetric_speed / $path->mm3_per_mm; + } + my $F = $speed * 60; # convert mm/sec to mm/min # extrude arc or line $gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge && $self->enable_cooling_markers; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 21e1babed0..5940942cdb 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -450,6 +450,7 @@ sub build { infill_every_layers infill_only_where_needed solid_infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters infill_first + max_print_speed max_volumetric_speed perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed support_material_speed support_material_interface_speed bridge_speed gap_fill_speed @@ -607,6 +608,11 @@ sub build { $optgroup->append_single_option_line('first_layer_acceleration'); $optgroup->append_single_option_line('default_acceleration'); } + { + my $optgroup = $page->new_optgroup('Autospeed (advanced)'); + $optgroup->append_single_option_line('max_print_speed'); + $optgroup->append_single_option_line('max_volumetric_speed'); + } } { diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 7813ba1ad4..ea7dafcc13 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -15,7 +15,7 @@ has '_brim_done' => (is => 'rw'); has '_second_layer_things_done' => (is => 'rw'); has '_last_obj_copy' => (is => 'rw'); -use List::Util qw(first sum); +use List::Util qw(first sum min max); use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull); use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset); @@ -40,9 +40,60 @@ sub BUILD { layer_count => $layer_count, enable_cooling_markers => 1, ); + $self->_gcodegen($gcodegen); $gcodegen->apply_print_config($self->config); $gcodegen->set_extruders($self->print->extruders); - $self->_gcodegen($gcodegen); + + # initialize autospeed + { + # get the minimum cross-section used in the print + my @mm3_per_mm = (); + foreach my $object (@{$self->print->objects}) { + foreach my $region_id (0..$#{$self->print->regions}) { + my $region = $self->print->get_region($region_id); + foreach my $layer (@{$object->layers}) { + my $layerm = $layer->get_region($region_id); + if ($region->config->get_abs_value('perimeter_speed') == 0 + || $region->config->get_abs_value('small_perimeter_speed') == 0 + || $region->config->get_abs_value('external_perimeter_speed') == 0 + || $region->config->get_abs_value('bridge_speed') == 0) { + push @mm3_per_mm, $layerm->perimeters->min_mm3_per_mm; + } + if ($region->config->get_abs_value('infill_speed') == 0 + || $region->config->get_abs_value('solid_infill_speed') == 0 + || $region->config->get_abs_value('top_solid_infill_speed') == 0 + || $region->config->get_abs_value('bridge_speed') == 0) { + push @mm3_per_mm, $layerm->fills->min_mm3_per_mm; + } + } + } + if ($object->config->get_abs_value('support_material_speed') == 0 + || $object->config->get_abs_value('support_material_interface_speed') == 0) { + foreach my $layer (@{$object->support_layers}) { + push @mm3_per_mm, $layer->support_fills->min_mm3_per_mm; + push @mm3_per_mm, $layer->support_interface_fills->min_mm3_per_mm; + } + } + } + my $min_mm3_per_mm = min(@mm3_per_mm); + if ($min_mm3_per_mm > 0) { + # In order to honor max_print_speed we need to find a target volumetric + # speed that we can use throughout the print. So we define this target + # volumetric speed as the volumetric speed produced by printing the + # smallest cross-section at the maximum speed: any larger cross-section + # will need slower feedrates. + my $volumetric_speed = $min_mm3_per_mm * $self->config->max_print_speed; + + # limit such volumetric speed with max_volumetric_speed if set + if ($self->config->max_volumetric_speed > 0) { + $volumetric_speed = min( + $volumetric_speed, + $self->config->max_volumetric_speed, + ); + } + $gcodegen->volumetric_speed($volumetric_speed); + } + } } $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new( diff --git a/utils/post-processing/flowrate.pl b/utils/post-processing/flowrate.pl index 573597c4cc..7aeef24dcf 100755 --- a/utils/post-processing/flowrate.pl +++ b/utils/post-processing/flowrate.pl @@ -6,17 +6,28 @@ use strict; use warnings; +use constant PI => 3.141592653589793238; +my @filament_diameter = split /,/, $ENV{SLIC3R_FILAMENT_DIAMETER}; + my $E = 0; -my ($X, $Y); +my $T = 0; +my ($X, $Y, $F); while (<>) { + if (/^G1.*? F([0-9.]+)/) { + $F = $1; + } if (/^G1 X([0-9.]+) Y([0-9.]+).*? E([0-9.]+)/) { my ($x, $y, $e) = ($1, $2, $3); my $e_length = $e - $E; if ($e_length > 0 && defined $X && defined $Y) { my $dist = sqrt( (($x-$X)**2) + (($y-$Y)**2) ); if ($dist > 0) { - my $flowrate = sprintf '%.2f', $e_length / $dist; - s/(\R+)/ ; XY dist = $dist ; E dist = $e_length ; E\/XY = $flowrate mm\/mm$1/; + my $mm_per_mm = $e_length / $dist; # dE/dXY + my $mm3_per_mm = ($filament_diameter[$T] ** 2) * PI/4 * $mm_per_mm; + my $vol_speed = $F/60 * $mm3_per_mm; + my $comment = sprintf ' ; dXY = %.3fmm ; dE = %.5fmm ; dE/XY = %.5fmm/mm; volspeed = %.5fmm^3/sec', + $dist, $e_length, $mm_per_mm, $vol_speed; + s/(\R+)/$comment$1/; } } $E = $e; @@ -33,6 +44,9 @@ while (<>) { if (/^G92 E0/) { $E = 0; } + if (/^T(\d+)/) { + $T = $1; + } print; } diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index f4ef37c13a..f6c3b80a8a 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -3,6 +3,7 @@ #include "ExPolygonCollection.hpp" #include "ClipperUtils.hpp" #include "Extruder.hpp" +#include #include namespace Slic3r { @@ -375,6 +376,20 @@ ExtrusionLoop::grow() const return pp; } +double +ExtrusionLoop::min_mm3_per_mm() const +{ + double min_mm3_per_mm = 0; + for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { + if (min_mm3_per_mm == 0) { + min_mm3_per_mm = path->mm3_per_mm; + } else { + min_mm3_per_mm = fmin(min_mm3_per_mm, path->mm3_per_mm); + } + } + return min_mm3_per_mm; +} + #ifdef SLIC3RXS REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); #endif diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index b5262eae05..74541832f0 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -50,6 +50,7 @@ class ExtrusionEntity virtual Point first_point() const = 0; virtual Point last_point() const = 0; virtual Polygons grow() const = 0; + virtual double min_mm3_per_mm() const = 0; }; typedef std::vector ExtrusionEntitiesPtr; @@ -81,6 +82,9 @@ class ExtrusionPath : public ExtrusionEntity double xofs, double yofs, std::string extrusion_axis, std::string gcode_line_suffix) const; Polygons grow() const; + double min_mm3_per_mm() const { + return this->mm3_per_mm; + }; private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; @@ -117,6 +121,7 @@ class ExtrusionLoop : public ExtrusionEntity bool is_infill() const; bool is_solid_infill() const; Polygons grow() const; + double min_mm3_per_mm() const; }; } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index 075429d9de..79736cbcb7 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -1,5 +1,6 @@ #include "ExtrusionEntityCollection.hpp" #include +#include #include namespace Slic3r { @@ -137,6 +138,37 @@ ExtrusionEntityCollection::items_count() const return count; } +/* Returns a single vector of pointers to all non-collection items contained in this one */ +void +ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const +{ + for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { + if ((*it)->is_collection()) { + ExtrusionEntityCollection* collection = dynamic_cast(*it); + ExtrusionEntityCollection contents; + collection->flatten(&contents); + retval->entities.insert(retval->entities.end(), contents.entities.begin(), contents.entities.end()); + } else { + retval->entities.push_back((*it)->clone()); + } + } +} + +double +ExtrusionEntityCollection::min_mm3_per_mm() const +{ + double min_mm3_per_mm = 0; + for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { + double mm3_per_mm = (*it)->min_mm3_per_mm(); + if (min_mm3_per_mm == 0) { + min_mm3_per_mm = mm3_per_mm; + } else { + min_mm3_per_mm = fmin(min_mm3_per_mm, mm3_per_mm); + } + } + return min_mm3_per_mm; +} + #ifdef SLIC3RXS // there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 18e15e3188..24016d6383 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -30,6 +30,8 @@ class ExtrusionEntityCollection : public ExtrusionEntity Point last_point() const; Polygons grow() const; size_t items_count() const; + void flatten(ExtrusionEntityCollection* retval) const; + double min_mm3_per_mm() const; }; } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 8252fe2d2f..9c8c1738f0 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -61,7 +61,7 @@ class PrintRegion private: Print* _print; - + PrintRegion(Print* print); ~PrintRegion(); }; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 7d454eae8c..f14fac1a05 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -501,6 +501,22 @@ PrintConfigDef::build_def() { Options["min_print_speed"].min = 0; Options["min_print_speed"].max = 1000; + Options["max_print_speed"].type = coFloat; + Options["max_print_speed"].label = "Max print speed"; + Options["max_print_speed"].tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the highest print speed you want to allow."; + Options["max_print_speed"].sidetext = "mm/s"; + Options["max_print_speed"].cli = "max-print-speed=f"; + Options["max_print_speed"].min = 1; + Options["max_print_speed"].max = 1000; + + Options["max_volumetric_speed"].type = coFloat; + Options["max_volumetric_speed"].label = "Max volumetric speed"; + Options["max_volumetric_speed"].tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the maximum volumetric speed your extruder supports."; + Options["max_volumetric_speed"].sidetext = "mm³/s"; + Options["max_volumetric_speed"].cli = "max-volumetric-speed=f"; + Options["max_volumetric_speed"].min = 0; + Options["max_volumetric_speed"].max = 1000; + Options["min_skirt_length"].type = coFloat; Options["min_skirt_length"].label = "Minimum extrusion length"; Options["min_skirt_length"].tooltip = "Generate no less than the number of skirt loops required to consume the specified amount of filament on the bottom layer. For multi-extruder machines, this minimum applies to each extruder."; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 287f33cf7c..4a3c4a2696 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -306,6 +306,8 @@ class GCodeConfig : public virtual StaticPrintConfig ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; ConfigOptionString layer_gcode; + ConfigOptionFloat max_print_speed; + ConfigOptionFloat max_volumetric_speed; ConfigOptionFloat pressure_advance; ConfigOptionFloats retract_length; ConfigOptionFloats retract_length_toolchange; @@ -331,6 +333,8 @@ class GCodeConfig : public virtual StaticPrintConfig this->gcode_comments.value = false; this->gcode_flavor.value = gcfRepRap; this->layer_gcode.value = ""; + this->max_print_speed.value = 80; + this->max_volumetric_speed.value = 0; this->pressure_advance.value = 0; this->retract_length.values.resize(1); this->retract_length.values[0] = 2; @@ -361,6 +365,8 @@ class GCodeConfig : public virtual StaticPrintConfig if (opt_key == "gcode_comments") return &this->gcode_comments; if (opt_key == "gcode_flavor") return &this->gcode_flavor; if (opt_key == "layer_gcode") return &this->layer_gcode; + if (opt_key == "max_print_speed") return &this->max_print_speed; + if (opt_key == "max_volumetric_speed") return &this->max_volumetric_speed; if (opt_key == "pressure_advance") return &this->pressure_advance; if (opt_key == "retract_length") return &this->retract_length; if (opt_key == "retract_length_toolchange") return &this->retract_length_toolchange; diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp index 08577c8926..b7e439479b 100644 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ b/xs/xsp/ExtrusionEntityCollection.xsp @@ -28,6 +28,12 @@ %code{% RETVAL = THIS->entities.size(); %}; int items_count() %code{% RETVAL = THIS->items_count(); %}; + ExtrusionEntityCollection* flatten() + %code{% + RETVAL = new ExtrusionEntityCollection(); + THIS->flatten(RETVAL); + %}; + double min_mm3_per_mm(); bool empty() %code{% RETVAL = THIS->entities.empty(); %}; std::vector orig_indices() From 1875825f45b088bd5379aa5567cf6acffd0312a3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 31 May 2015 22:09:58 +0200 Subject: [PATCH 04/83] Minor fix to previous commit --- lib/Slic3r/Print/GCode.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index ea7dafcc13..ce60996c4b 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -75,7 +75,7 @@ sub BUILD { } } } - my $min_mm3_per_mm = min(@mm3_per_mm); + my $min_mm3_per_mm = min(@mm3_per_mm) // 0; if ($min_mm3_per_mm > 0) { # In order to honor max_print_speed we need to find a target volumetric # speed that we can use throughout the print. So we define this target From 29b0e807d4b4b708b8d56d82dd4fd9aadfd2a24a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 31 May 2015 22:14:24 +0200 Subject: [PATCH 05/83] New [current_extruder] variable in custom G-code. #2866 --- lib/Slic3r/GCode.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 86f532589a..1b7194bf90 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -455,6 +455,7 @@ sub point_to_gcode { sub set_extruder { my ($self, $extruder_id) = @_; + $self->placeholder_parser->set('current_extruder', $extruder_id); return "" if !$self->writer->need_toolchange($extruder_id); # if we are running a single-extruder setup, just set the extruder and return nothing From 17eb50da6dd6754b738be9793fcfb0e7a43e86e5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 31 May 2015 22:33:46 +0200 Subject: [PATCH 06/83] Use Ctrl+, for Preferences (standard on OS X, don't know about others). #2860 --- lib/Slic3r/GUI/MainFrame.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 2c1a3bca53..4e7d362173 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -196,7 +196,7 @@ sub _init_menubar { $self->repair_stl; }, undef, 'wrench.png'); $fileMenu->AppendSeparator(); - $self->_append_menu_item($fileMenu, "Preferences…", 'Application preferences', sub { + $self->_append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub { Slic3r::GUI::Preferences->new($self)->ShowModal; }, wxID_PREFERENCES); $fileMenu->AppendSeparator(); From e26022a2f2591e36955f1a7ec0766810cafa1d24 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 31 May 2015 22:35:52 +0200 Subject: [PATCH 07/83] Comment about Ctrl+, --- lib/Slic3r/GUI/MainFrame.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 4e7d362173..58372fd99b 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -196,6 +196,7 @@ sub _init_menubar { $self->repair_stl; }, undef, 'wrench.png'); $fileMenu->AppendSeparator(); + # Cmd+, is standard on OS X - what about other operating systems? $self->_append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub { Slic3r::GUI::Preferences->new($self)->ShowModal; }, wxID_PREFERENCES); From 23119e36735a62bef7319a982da441c9fcdc6ed1 Mon Sep 17 00:00:00 2001 From: Daren Schwenke Date: Mon, 1 Jun 2015 02:48:11 -0400 Subject: [PATCH 08/83] Fixes #2858 --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8589887a62..c4c3ea5448 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -471,7 +471,7 @@ sub update_presets { sub filament_presets { my $self = shift; - return map $_->GetSelection, @{ $self->{preset_choosers}{filament} }; + return map scalar($_->GetSelection), @{ $self->{preset_choosers}{filament} }; } sub add { From fbea32a81c1292e12717e3f406f7bcac4a53ec1d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 11:50:08 +0200 Subject: [PATCH 09/83] Prevent writing empty materials to AMF files. Also add a note about material-id = 0 being reserved by AMF spec. #2871 --- lib/Slic3r/Format/AMF.pm | 2 ++ xs/src/libslic3r/Model.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm index ec6b31fe4e..218b1c29dd 100644 --- a/lib/Slic3r/Format/AMF.pm +++ b/lib/Slic3r/Format/AMF.pm @@ -36,7 +36,9 @@ sub write_file { printf $fh qq{\n}; printf $fh qq{ Slic3r %s\n}, $Slic3r::VERSION; for my $material_id (sort @{ $model->material_names }) { + next if $material_id eq ''; my $material = $model->get_material($material_id); + # note that material-id must never be 0 since it's reserved by the AMF spec printf $fh qq{ \n}, $material_id; for (keys %{$material->attributes}) { printf $fh qq{ %s\n}, $_, $material->attributes->{$_}; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 8d98b4d6b7..29a2f33103 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -690,7 +690,8 @@ ModelVolume::assign_unique_material() { Model* model = this->get_object()->get_model(); - this->_material_id = 1 + model->materials.size(); + // as material-id "0" is reserved by the AMF spec we start from 1 + this->_material_id = 1 + model->materials.size(); // watchout for implicit cast return model->add_material(this->_material_id); } From d549393a84ff369eb50f1d25e4346e71e655c9cb Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 14:32:31 +0200 Subject: [PATCH 10/83] Comment about GetSelection() being context-aware. #2873 --- lib/Slic3r/GUI/Plater.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index c4c3ea5448..e01fd060f3 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -471,6 +471,7 @@ sub update_presets { sub filament_presets { my $self = shift; + # force scalar context for GetSelection() as it's context-aware return map scalar($_->GetSelection), @{ $self->{preset_choosers}{filament} }; } From 97c701cdacb6f9225eb9a25a24ac04353a310d23 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 14:57:07 +0200 Subject: [PATCH 11/83] Fix concurrency issue in Wx when changing the number of extruders. #2863 --- lib/Slic3r/GUI/Tab.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 5940942cdb..5df72826ec 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -106,6 +106,7 @@ sub new { $self->{config} = Slic3r::Config->new; $self->build; + $self->update_tree; $self->_update; if ($self->hidden_options) { $self->{config}->apply(Slic3r::Config->new_from_defaults($self->hidden_options)); @@ -282,7 +283,6 @@ sub add_options_page { $page->Hide; $self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5); push @{$self->{pages}}, $page; - $self->update_tree; return $page; } @@ -961,7 +961,7 @@ sub _update_description { package Slic3r::GUI::Tab::Printer; use base 'Slic3r::GUI::Tab'; -use Wx qw(:sizer :button :bitmap :misc :id); +use Wx qw(wxTheApp :sizer :button :bitmap :misc :id); use Wx::Event qw(EVT_BUTTON); sub name { 'printer' } @@ -1039,7 +1039,9 @@ sub build { my ($opt_id) = @_; if ($opt_id eq 'extruders_count') { $self->update_dirty; - $self->_extruders_count_changed($optgroup->get_value('extruders_count')); + wxTheApp->CallAfter(sub { + $self->_extruders_count_changed($optgroup->get_value('extruders_count')); + }); } }); } @@ -1225,8 +1227,6 @@ sub _build_extruder_pages { $optgroup->append_single_option_line($_, $extruder_idx) for qw(retract_length_toolchange retract_restart_extra_toolchange); } - - $self->{extruder_pages}[$extruder_idx]{disabled} = 0; } # remove extra pages From f14c0e218345e39eb46e204292601939cccb29de Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 17:55:51 +0200 Subject: [PATCH 12/83] Bugfix: concave starting points were not correctly detected for slice holes. Includes regression test. #2857 --- lib/Slic3r/GCode.pm | 7 +++++++ lib/Slic3r/Test.pm | 7 +++++++ t/geometry.t | 17 ++++++++++++++++- t/perimeters.t | 27 ++++++++++++++++++++------- xs/src/libslic3r/Polygon.cpp | 6 ++++-- 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 1b7194bf90..955cdbee71 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -129,9 +129,16 @@ sub extrude_loop { $loop->split_at($last_pos); } elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') { # simplify polygon in order to skip false positives in concave/convex detection + # ($loop is always ccw as $polygon->simplify only works on ccw polygons) my $polygon = $loop->polygon; my @simplified = @{$polygon->simplify(scale $self->config->get_at('nozzle_diameter', $self->writer->extruder->id)/2)}; + # restore original winding order so that concave and convex detection always happens + # on the right/outer side of the polygon + if ($was_clockwise) { + $_->reverse for @simplified; + } + # concave vertices have priority my @candidates = map @{$_->concave_points(PI*4/3)}, @simplified; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 7f1fa53064..2889f79696 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -34,6 +34,13 @@ sub mesh { $facets = [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[0,2,8],[10,8,9],[0,8,6],[0,6,4],[4,7,9],[7,10,9],[2,3,9],[9,3,11],[12,1,5],[13,3,12],[14,12,5],[3,1,12],[11,3,13],[11,15,5],[11,13,15],[15,14,5],[5,4,9],[11,5,9],[8,13,12],[6,8,12],[10,15,13],[8,10,13],[15,10,14],[14,10,7],[14,7,12],[12,7,6] ], + } elsif ($name eq 'cube_with_concave_hole') { + $vertices = [ + [-10,-10,-5],[-10,-10,5],[-10,10,-5],[-10,10,5],[10,-10,-5],[10,-10,5],[-5,-5,-5],[5,-5,-5],[5,5,-5],[5,10,-5],[-5,5,-5],[3.06161699911402e-16,5,-5],[5,0,-5],[0,0,-5],[10,5,-5],[5,10,5],[-5,-5,5],[5,0,5],[5,-5,5],[-5,5,5],[10,5,5],[5,5,5],[3.06161699911402e-16,5,5],[0,0,5] + ]; + $facets = [ + [0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[10,2,11],[11,12,13],[0,2,10],[0,10,6],[0,6,4],[11,2,8],[4,7,12],[4,12,8],[12,11,8],[14,4,8],[2,3,9],[9,3,15],[16,1,5],[17,18,5],[19,3,16],[20,21,5],[18,16,5],[3,1,16],[22,3,19],[21,3,22],[21,17,5],[21,22,17],[21,15,3],[23,17,22],[5,4,14],[20,5,14],[20,14,21],[21,14,8],[9,15,21],[8,9,21],[10,19,16],[6,10,16],[11,22,19],[10,11,19],[13,23,11],[11,23,22],[23,13,12],[17,23,12],[17,12,18],[18,12,7],[18,7,16],[16,7,6] + ], } elsif ($name eq 'V') { $vertices = [ [-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20] diff --git a/t/geometry.t b/t/geometry.t index cec0ff320b..4529e6b1cb 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 38; +plan tests => 42; BEGIN { use FindBin; @@ -190,6 +190,21 @@ my $polygons = [ #========================================================== +{ + my $square = Slic3r::Polygon->new_scale( + [100,100], + [200,100], + [200,200], + [100,200], + ); + is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in ccw square'; + is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in ccw square'; + + $square->make_clockwise; + is scalar(@{$square->concave_points(PI*4/3)}), 4, 'fuor concave vertices detected in cw square'; + is scalar(@{$square->convex_points(PI*2/3)}), 0, 'no convex vertices detected in cw square'; +} + { my $square = Slic3r::Polygon->new_scale( [150,100], diff --git a/t/perimeters.t b/t/perimeters.t index ed6f6b4307..dc2644e9de 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -1,4 +1,4 @@ -use Test::More tests => 29; +use Test::More tests => 33; use strict; use warnings; @@ -47,14 +47,14 @@ use Slic3r::Test; ok !$has_cw_loops, 'all perimeters extruded ccw'; } - { + foreach my $model (qw(cube_with_hole cube_with_concave_hole)) { $config->set('external_perimeter_speed', 68); my $print = Slic3r::Test::init_print( - 'cube_with_hole', + $model, config => $config, duplicate => 2, # we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong inwards moves) ); - my $has_cw_loops = my $has_outwards_move = 0; + my $has_cw_loops = my $has_outwards_move = my $starts_on_convex_point = 0; my $cur_loop; my %external_loops = (); # print_z => count of external loops Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { @@ -74,10 +74,22 @@ use Slic3r::Test; if defined($external_loops{$self->Z}) && $external_loops{$self->Z} == 2; $external_loops{$self->Z}++; - my $loop_contains_point = Slic3r::Polygon->new_scale(@$cur_loop)->contains_point($move_dest); + my $is_contour = $external_loops{$self->Z} == 2; + my $is_hole = $external_loops{$self->Z} == 1; + + my $loop = Slic3r::Polygon->new_scale(@$cur_loop); + my $loop_contains_point = $loop->contains_point($move_dest); $has_outwards_move = 1 - if (!$loop_contains_point && $external_loops{$self->Z} == 2) # contour should include destination - || ($loop_contains_point && $external_loops{$self->Z} == 1); # hole should not + if (!$loop_contains_point && $is_contour) # contour should include destination + || ($loop_contains_point && $is_hole); # hole should not + + if ($model eq 'cube_with_concave_hole') { + # check that loop starts at a concave vertex + my $ccw_angle = $loop->first_point->ccw_angle(@$loop[-2,1]); + my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex + $starts_on_convex_point = 1 + if ($convex && $is_contour) || (!$convex && $is_hole); + } } $cur_loop = undef; } @@ -85,6 +97,7 @@ use Slic3r::Test; }); ok !$has_cw_loops, 'all perimeters extruded ccw'; ok !$has_outwards_move, 'move inwards after completing external loop'; + ok !$starts_on_convex_point, 'loops start on concave point if any'; } { diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index c6fb911316..93a191acba 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -218,7 +218,8 @@ Polygon::wkt() const return wkt.str(); } -// find all concave vertices (i.e. having an internal angle greater than the supplied angle) */ +// find all concave vertices (i.e. having an internal angle greater than the supplied angle) +// (external = right side, thus we consider ccw orientation) Points Polygon::concave_points(double angle) const { @@ -241,7 +242,8 @@ Polygon::concave_points(double angle) const return points; } -// find all convex vertices (i.e. having an internal angle smaller than the supplied angle) */ +// find all convex vertices (i.e. having an internal angle smaller than the supplied angle) +// (external = right side, thus we consider ccw orientation) Points Polygon::convex_points(double angle) const { From 6095427926bce69548b25a58abbd29e318232817 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 19:49:52 +0200 Subject: [PATCH 13/83] Fix admesh's STL parser in order to make it more tolerant for broken STL files having multiple 'solid' definitions. Still not the ideal STL parser, but handles the cases that were reported so far. #2471 #2842 --- xs/src/admesh/stlinit.c | 42 ++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/xs/src/admesh/stlinit.c b/xs/src/admesh/stlinit.c index efc4e11ba2..e92af81047 100644 --- a/xs/src/admesh/stlinit.c +++ b/xs/src/admesh/stlinit.c @@ -152,18 +152,19 @@ stl_count_facets(stl_file *stl, char *file) { } /* Find the number of facets */ - j = 0; - for(i = 0; i < file_size ; i++) { - j++; - if(getc(stl->fp) == '\n') { - if(j > 4) { /* don't count short lines */ - num_lines++; - } - j = 0; - } + char linebuf[100]; + while (fgets(linebuf, 100, stl->fp) != NULL) { + /* don't count short lines */ + if (strlen(linebuf) <= 4) continue; + + /* skip solid/endsolid lines as broken STL file generators may put several of them */ + if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue; + + ++num_lines; } + rewind(stl->fp); - + /* Get the header */ for(i = 0; (i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++); @@ -269,8 +270,6 @@ stl_read(stl_file *stl, int first_facet, int first) { fseek(stl->fp, HEADER_SIZE, SEEK_SET); } else { rewind(stl->fp); - /* Skip the first line of the file */ - while(getc(stl->fp) != '\n'); } for(i = first_facet; i < stl->stats.number_of_facets; i++) { @@ -288,13 +287,18 @@ stl_read(stl_file *stl, int first_facet, int first) { } else /* Read a single facet from an ASCII .STL file */ { - if((fscanf(stl->fp, "%*s %*s %f %f %f\n", &facet.normal.x, &facet.normal.y, &facet.normal.z) + \ - fscanf(stl->fp, "%*s %*s") + \ - fscanf(stl->fp, "%*s %f %f %f\n", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z) + \ - fscanf(stl->fp, "%*s %f %f %f\n", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z) + \ - fscanf(stl->fp, "%*s %f %f %f\n", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z) + \ - fscanf(stl->fp, "%*s") + \ - fscanf(stl->fp, "%*s")) != 12) { + // skip solid/endsolid + // (in this order, otherwise it won't work when they are paired in the middle of a file) + fscanf(stl->fp, "endsolid\n"); + fscanf(stl->fp, "solid %*s\n"); + + if((fscanf(stl->fp, " facet normal %f %f %f\n", &facet.normal.x, &facet.normal.y, &facet.normal.z) + \ + fscanf(stl->fp, " outer loop\n") + \ + fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z) + \ + fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z) + \ + fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z) + \ + fscanf(stl->fp, " endloop\n") + \ + fscanf(stl->fp, " endfacet\n")) != 12) { perror("Something is syntactically very wrong with this ASCII STL!"); stl->error = 1; return; From 4402dfa6f37e63d3c9dc9f4f682c0a9071b159b5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 23:11:27 +0200 Subject: [PATCH 14/83] Fix crash in GUI caused by recursive event calls. #2613 --- lib/Slic3r/GUI/Tab.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 5df72826ec..a7a7f90366 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -65,7 +65,9 @@ sub new { $self->{treectrl}->AddRoot("root"); $self->{pages} = []; $self->{treectrl}->SetIndent(0); + $self->{disable_tree_sel_changed_event} = 0; EVT_TREE_SEL_CHANGED($parent, $self->{treectrl}, sub { + return if $self->{disable_tree_sel_changed_event}; my $page = first { $_->{title} eq $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection) } @{$self->{pages}} or return; $_->Hide for @{$self->{pages}}; @@ -303,12 +305,15 @@ sub update_tree { foreach my $page (@{$self->{pages}}) { my $itemId = $self->{treectrl}->AppendItem($rootItem, $page->{title}, $page->{iconID}); if ($page->{title} eq $selected) { + $self->{disable_tree_sel_changed_event} = 1; $self->{treectrl}->SelectItem($itemId); + $self->{disable_tree_sel_changed_event} = 0; $have_selection = 1; } } if (!$have_selection) { + # this is triggered on first load, so we don't disable the sel change event $self->{treectrl}->SelectItem($self->{treectrl}->GetFirstChild($rootItem)); } } From 6c53b14ee457d64ca87667cad8f1e797f1399f3e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 23:19:26 +0200 Subject: [PATCH 15/83] Minor improvement to extruder count field --- lib/Slic3r/GUI/OptionsGroup/Field.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/OptionsGroup/Field.pm b/lib/Slic3r/GUI/OptionsGroup/Field.pm index 2721853d4d..c24f614a05 100644 --- a/lib/Slic3r/GUI/OptionsGroup/Field.pm +++ b/lib/Slic3r/GUI/OptionsGroup/Field.pm @@ -137,6 +137,7 @@ sub BUILD { $self->wxWindow($field); EVT_SPINCTRL($self->parent, $field, sub { + $self->tmp_value(undef); $self->_on_change($self->option->opt_id); }); EVT_TEXT($self->parent, $field, sub { @@ -147,11 +148,14 @@ sub BUILD { # gets the old one, and on_kill_focus resets the control to the old value. # As a workaround, we get the new value from $event->GetString and store # here temporarily so that we can return it from $self->get_value - $self->tmp_value($event->GetString); + $self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/; $self->_on_change($self->option->opt_id); - $self->tmp_value(undef); + # We don't reset tmp_value here because _on_change might put callbacks + # in the CallAfter queue, and we want the tmp value to be available from + # them as well. }); EVT_KILL_FOCUS($field, sub { + $self->tmp_value(undef); $self->_on_kill_focus($self->option->opt_id, @_); }); } From 1fcfdf4718d4e6a5eb7b93315998c3705594f163 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 23:22:44 +0200 Subject: [PATCH 16/83] Added a couple full_label values --- xs/src/libslic3r/PrintConfig.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index f14fac1a05..55ed26e1f2 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -653,12 +653,14 @@ PrintConfigDef::build_def() { Options["retract_length"].type = coFloats; Options["retract_length"].label = "Length"; + Options["retract_length"].full_label = "Retraction Length"; Options["retract_length"].tooltip = "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; Options["retract_length"].sidetext = "mm (zero to disable)"; Options["retract_length"].cli = "retract-length=f@"; Options["retract_length_toolchange"].type = coFloats; Options["retract_length_toolchange"].label = "Length"; + Options["retract_length_toolchange"].full_label = "Retraction Length (Toolchange)"; Options["retract_length_toolchange"].tooltip = "When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; Options["retract_length_toolchange"].sidetext = "mm (zero to disable)"; Options["retract_length_toolchange"].cli = "retract-length-toolchange=f@"; @@ -683,6 +685,7 @@ PrintConfigDef::build_def() { Options["retract_speed"].type = coInts; Options["retract_speed"].label = "Speed"; + Options["retract_speed"].full_label = "Retraction Speed"; Options["retract_speed"].tooltip = "The speed for retractions (it only applies to the extruder motor)."; Options["retract_speed"].sidetext = "mm/s"; Options["retract_speed"].cli = "retract-speed=f@"; @@ -716,6 +719,7 @@ PrintConfigDef::build_def() { Options["skirts"].type = coInt; Options["skirts"].label = "Loops (minimum)"; + Options["skirts"].full_label = "Skirt Loops"; Options["skirts"].tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely."; Options["skirts"].cli = "skirts=i"; Options["skirts"].min = 0; From c2c321c90b80e02cd121e5c384dd7b61e0d83a36 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 23:34:04 +0200 Subject: [PATCH 17/83] Restore the (modified) label in preset comboctrl --- lib/Slic3r/GUI/Plater.pm | 10 ++++++++-- lib/Slic3r/GUI/Tab.pm | 9 ++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e01fd060f3..e5ea7b5c25 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -437,7 +437,7 @@ sub GetFrame { sub update_presets { my $self = shift; - my ($group, $presets, $selected) = @_; + my ($group, $presets, $selected, $is_dirty) = @_; foreach my $choice (@{ $self->{preset_choosers}{$group} }) { my $sel = $choice->GetSelection; @@ -463,7 +463,13 @@ sub update_presets { } $choice->AppendString($preset->name, $bitmap); } - $choice->SetSelection($sel) if $sel <= $#$presets; + + if ($sel <= $#$presets) { + $choice->SetSelection($sel); + if ($is_dirty) { + $choice->SetString($sel, $choice->GetString($sel) . " (modified)"); + } + } } $self->{preset_choosers}{$group}[0]->SetSelection($selected); } diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index a7a7f90366..0aed46ca38 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -180,8 +180,11 @@ sub _update {} sub _on_presets_changed { my $self = shift; - $self->{on_presets_changed}->($self->{presets}, $self->{presets_choice}->GetSelection) - if $self->{on_presets_changed}; + $self->{on_presets_changed}->( + $self->{presets}, + scalar($self->{presets_choice}->GetSelection), + $self->is_dirty, + ) if $self->{on_presets_changed}; } sub on_preset_loaded {} @@ -1043,10 +1046,10 @@ sub build { $optgroup->on_change(sub { my ($opt_id) = @_; if ($opt_id eq 'extruders_count') { - $self->update_dirty; wxTheApp->CallAfter(sub { $self->_extruders_count_changed($optgroup->get_value('extruders_count')); }); + $self->update_dirty; } }); } From 0a9f1c466a10c3b26676bf1034ba86b7652525e7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 23:52:15 +0200 Subject: [PATCH 18/83] Minor improvements to filament choosers in the plater --- lib/Slic3r/GUI/Plater.pm | 19 +++++++++++++------ lib/Slic3r/GUI/Tab.pm | 4 ++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e5ea7b5c25..3d63a248f6 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -439,8 +439,14 @@ sub update_presets { my $self = shift; my ($group, $presets, $selected, $is_dirty) = @_; - foreach my $choice (@{ $self->{preset_choosers}{$group} }) { - my $sel = $choice->GetSelection; + my @choosers = @{ $self->{preset_choosers}{$group} }; + foreach my $choice (@choosers) { + if ($group eq 'filament' && @choosers > 1) { + # if we have more than one filament chooser, keep our selection + # instead of importing the one from the tab + $selected = $choice->GetSelection; + $is_dirty = 0; + } $choice->Clear; foreach my $preset (@$presets) { my $bitmap; @@ -464,14 +470,15 @@ sub update_presets { $choice->AppendString($preset->name, $bitmap); } - if ($sel <= $#$presets) { - $choice->SetSelection($sel); + if ($selected <= $#$presets) { if ($is_dirty) { - $choice->SetString($sel, $choice->GetString($sel) . " (modified)"); + $choice->SetString($selected, $choice->GetString($selected) . " (modified)"); } + # call SetSelection() only after SetString() otherwise the new string + # won't be picked up as the visible string + $choice->SetSelection($selected); } } - $self->{preset_choosers}{$group}[0]->SetSelection($selected); } sub filament_presets { diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 0aed46ca38..88d7db8bab 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -229,6 +229,10 @@ sub on_select_preset { 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if ($confirm->ShowModal == wxID_NO) { $self->{presets_choice}->SetSelection($self->current_preset); + + # trigger the on_presets_changed event so that we also restore the previous value + # in the plater selector + $self->_on_presets_changed; return; } } From b4cc92b9facaee5f2dc440cdf067f4e19b2e6712 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Jun 2015 23:58:34 +0200 Subject: [PATCH 19/83] Fix filament colors not being initialized in additional comboboxes --- lib/Slic3r/GUI/Plater.pm | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3d63a248f6..2f8d0bbbff 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1366,14 +1366,29 @@ sub on_extruders_change { my $choices = $self->{preset_choosers}{filament}; while (@$choices < $num_extruders) { + # copy strings from first choice my @presets = $choices->[0]->GetStrings; - push @$choices, Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); + + # initialize new choice + my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); + push @$choices, $choice; + + # copy icons from first choice + $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; + + # insert new choice into sizer $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); - $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); - EVT_COMBOBOX($choices->[-1], $choices->[-1], sub { $self->_on_select_preset('filament', @_) }); - my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; - $choices->[-1]->SetSelection($i || 0); + $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); + + # setup the listener + EVT_COMBOBOX($choice, $choice, sub { $self->_on_select_preset('filament', @_) }); + + # initialize selection + my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; + $choice->SetSelection($i || 0); } + + # remove unused choices if any while (@$choices > $num_extruders) { $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice From 03c754ab14a104f4b168cca0075a8920602d75b7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 10:49:24 +0200 Subject: [PATCH 20/83] Add close button and support Esc key and close on click inside the window to the About dialog since the title bar is not displayed on Windows and some Linux distros. #2688 --- lib/Slic3r/GUI/AboutDialog.pm | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm index 815da9e15e..6f2ca94632 100644 --- a/lib/Slic3r/GUI/AboutDialog.pm +++ b/lib/Slic3r/GUI/AboutDialog.pm @@ -3,8 +3,8 @@ use strict; use warnings; use utf8; -use Wx qw(:font :html :misc :dialog :sizer :systemsettings); -use Wx::Event qw(EVT_HTML_LINK_CLICKED); +use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id); +use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON); use Wx::Print; use Wx::Html; use base 'Wx::Dialog'; @@ -12,7 +12,7 @@ use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent) = @_; - my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300], &Wx::wxCLOSE_BOX); + my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300], wxCAPTION); $self->SetBackgroundColour(Wx::wxWHITE); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -65,7 +65,18 @@ sub new { $html->SetPage($text); $vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20); EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked); - + + my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE); + $self->SetEscapeId(wxID_CLOSE); + EVT_BUTTON($self, wxID_CLOSE, sub { + $self->EndModal(wxID_CLOSE); + $self->Close; + }); + $vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); + + EVT_LEFT_DOWN($self, sub { $self->Close }); + EVT_LEFT_DOWN($logo, sub { $self->Close }); + return $self; } From ca72fd4266b163914d70204320f86ab96d3bfeb6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 11:19:11 +0200 Subject: [PATCH 21/83] Fix ComboCtrl on Windows --- lib/Slic3r/GUI/Plater.pm | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 2f8d0bbbff..d48b89c3d2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -330,7 +330,13 @@ sub new { $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); $self->{preset_choosers}{$group} = [$choice]; - EVT_COMBOBOX($choice, $choice, sub { $self->_on_select_preset($group, @_) }); + # setup the listener + EVT_COMBOBOX($choice, $choice, sub { + my ($choice) = @_; + wxTheApp->CallAfter(sub { + $self->_on_select_preset($group, $choice); + }); + }); $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0); } @@ -423,7 +429,9 @@ sub _on_select_preset { wxTheApp->save_settings; return; } - $self->{on_select_preset}->($group, $choice->GetSelection) + + # call GetSelection() in scalar context as it's context-aware + $self->{on_select_preset}->($group, scalar $choice->GetSelection) if $self->{on_select_preset}; # get new config and generate on_config_change() event for updating plater and other things @@ -1381,7 +1389,12 @@ sub on_extruders_change { $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); # setup the listener - EVT_COMBOBOX($choice, $choice, sub { $self->_on_select_preset('filament', @_) }); + EVT_COMBOBOX($choice, $choice, sub { + my ($choice) = @_; + wxTheApp->CallAfter(sub { + $self->_on_select_preset('filament', $choice); + }); + }); # initialize selection my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; From e90f5fde4e09f5c3fa5391b54ebd53af857758b5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 11:20:45 +0200 Subject: [PATCH 22/83] Enlarge your about window --- lib/Slic3r/GUI/AboutDialog.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm index 6f2ca94632..6fc83b6045 100644 --- a/lib/Slic3r/GUI/AboutDialog.pm +++ b/lib/Slic3r/GUI/AboutDialog.pm @@ -12,7 +12,7 @@ use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent) = @_; - my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300], wxCAPTION); + my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 340], wxCAPTION); $self->SetBackgroundColour(Wx::wxWHITE); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); From 8613e174e7986d3346d331090cdb40cb183d6d9c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 11:48:56 +0200 Subject: [PATCH 23/83] Fix error in autospeed. #2810 --- lib/Slic3r/GCode.pm | 2 +- lib/Slic3r/Print/GCode.pm | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 955cdbee71..b10eb893fc 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -313,7 +313,7 @@ sub _extrude_path { $speed ||= $self->volumetric_speed / $path->mm3_per_mm; } my $F = $speed * 60; # convert mm/sec to mm/min - + use XXX; YYY $path->role, $speed, $self->volumetric_speed if $F == 0; # extrude arc or line $gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge && $self->enable_cooling_markers; my $path_length = unscale $path->length; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index ce60996c4b..8965687ae6 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -75,8 +75,9 @@ sub BUILD { } } } - my $min_mm3_per_mm = min(@mm3_per_mm) // 0; - if ($min_mm3_per_mm > 0) { + @mm3_per_mm = grep $_ != 0, @mm3_per_mm; + if (@mm3_per_mm) { + my $min_mm3_per_mm = min(@mm3_per_mm); # In order to honor max_print_speed we need to find a target volumetric # speed that we can use throughout the print. So we define this target # volumetric speed as the volumetric speed produced by printing the From c37b5c2e8714c9580b45af92192560b5419dafa0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 11:49:43 +0200 Subject: [PATCH 24/83] Remove debugging statement --- lib/Slic3r/GCode.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b10eb893fc..955cdbee71 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -313,7 +313,7 @@ sub _extrude_path { $speed ||= $self->volumetric_speed / $path->mm3_per_mm; } my $F = $speed * 60; # convert mm/sec to mm/min - use XXX; YYY $path->role, $speed, $self->volumetric_speed if $F == 0; + # extrude arc or line $gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge && $self->enable_cooling_markers; my $path_length = unscale $path->length; From 7b65a3551923fb499bf5caa63bea73a00b059366 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 11:54:38 +0200 Subject: [PATCH 25/83] Always limit volumetric speed with Max Volumetric Speed (not just when using autospeed). #2810 --- lib/Slic3r/GCode.pm | 7 +++++++ xs/src/libslic3r/PrintConfig.cpp | 14 +++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 955cdbee71..ecd58d0481 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -312,6 +312,13 @@ sub _extrude_path { if ($self->volumetric_speed != 0) { $speed ||= $self->volumetric_speed / $path->mm3_per_mm; } + if ($self->config->max_volumetric_speed > 0) { + # Cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + $speed = min( + $speed, + $self->config->max_volumetric_speed / $path->mm3_per_mm, + ); + } my $F = $speed * 60; # convert mm/sec to mm/min # extrude arc or line diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 55ed26e1f2..689a334c3e 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -151,7 +151,7 @@ PrintConfigDef::build_def() { Options["external_perimeter_speed"].type = coFloatOrPercent; Options["external_perimeter_speed"].label = "External perimeters"; Options["external_perimeter_speed"].category = "Speed"; - Options["external_perimeter_speed"].tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above."; + Options["external_perimeter_speed"].tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto."; Options["external_perimeter_speed"].sidetext = "mm/s or %"; Options["external_perimeter_speed"].cli = "external-perimeter-speed=s"; Options["external_perimeter_speed"].ratio_over = "perimeter_speed"; @@ -448,7 +448,7 @@ PrintConfigDef::build_def() { Options["infill_speed"].type = coFloat; Options["infill_speed"].label = "Infill"; Options["infill_speed"].category = "Speed"; - Options["infill_speed"].tooltip = "Speed for printing the internal fill."; + Options["infill_speed"].tooltip = "Speed for printing the internal fill. Set to zero for auto."; Options["infill_speed"].sidetext = "mm/s"; Options["infill_speed"].cli = "infill-speed=f"; Options["infill_speed"].aliases.push_back("print_feed_rate"); @@ -511,7 +511,7 @@ PrintConfigDef::build_def() { Options["max_volumetric_speed"].type = coFloat; Options["max_volumetric_speed"].label = "Max volumetric speed"; - Options["max_volumetric_speed"].tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the maximum volumetric speed your extruder supports."; + Options["max_volumetric_speed"].tooltip = "This experimental setting is used to set the maximum volumetric speed your extruder supports."; Options["max_volumetric_speed"].sidetext = "mm³/s"; Options["max_volumetric_speed"].cli = "max-volumetric-speed=f"; Options["max_volumetric_speed"].min = 0; @@ -595,7 +595,7 @@ PrintConfigDef::build_def() { Options["perimeter_speed"].type = coFloat; Options["perimeter_speed"].label = "Perimeters"; Options["perimeter_speed"].category = "Speed"; - Options["perimeter_speed"].tooltip = "Speed for perimeters (contours, aka vertical shells)."; + Options["perimeter_speed"].tooltip = "Speed for perimeters (contours, aka vertical shells). Set to zero for auto."; Options["perimeter_speed"].sidetext = "mm/s"; Options["perimeter_speed"].cli = "perimeter-speed=f"; Options["perimeter_speed"].aliases.push_back("perimeter_feed_rate"); @@ -736,7 +736,7 @@ PrintConfigDef::build_def() { Options["small_perimeter_speed"].type = coFloatOrPercent; Options["small_perimeter_speed"].label = "Small perimeters"; Options["small_perimeter_speed"].category = "Speed"; - Options["small_perimeter_speed"].tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above."; + Options["small_perimeter_speed"].tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto."; Options["small_perimeter_speed"].sidetext = "mm/s or %"; Options["small_perimeter_speed"].cli = "small-perimeter-speed=s"; Options["small_perimeter_speed"].ratio_over = "perimeter_speed"; @@ -774,7 +774,7 @@ PrintConfigDef::build_def() { Options["solid_infill_speed"].type = coFloatOrPercent; Options["solid_infill_speed"].label = "Solid infill"; Options["solid_infill_speed"].category = "Speed"; - Options["solid_infill_speed"].tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above."; + Options["solid_infill_speed"].tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above. Set to zero for auto."; Options["solid_infill_speed"].sidetext = "mm/s or %"; Options["solid_infill_speed"].cli = "solid-infill-speed=s"; Options["solid_infill_speed"].ratio_over = "infill_speed"; @@ -971,7 +971,7 @@ PrintConfigDef::build_def() { Options["top_solid_infill_speed"].type = coFloatOrPercent; Options["top_solid_infill_speed"].label = "Top solid infill"; Options["top_solid_infill_speed"].category = "Speed"; - Options["top_solid_infill_speed"].tooltip = "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above."; + Options["top_solid_infill_speed"].tooltip = "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above. Set to zero for auto."; Options["top_solid_infill_speed"].sidetext = "mm/s or %"; Options["top_solid_infill_speed"].cli = "top-solid-infill-speed=s"; Options["top_solid_infill_speed"].ratio_over = "solid_infill_speed"; From 56b993bb89f0d2f27ff4a2198e510125ed8ed90d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 16:10:15 +0200 Subject: [PATCH 26/83] More fixes for Unicode filenames support on Windows and OS X --- Build.PL | 1 + lib/Slic3r.pm | 15 ++++++++++++++- lib/Slic3r/GUI/Tab.pm | 4 +++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Build.PL b/Build.PL index 0c208813b6..ff86430bbf 100644 --- a/Build.PL +++ b/Build.PL @@ -22,6 +22,7 @@ my %prereqs = qw( IO::Scalar 0 threads 1.96 Time::HiRes 0 + Unicode::Normalize 0 ); my %recommends = qw( Class::XSAccessor 0 diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9fc1df029c..0e80381190 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -71,6 +71,7 @@ use Slic3r::Print::SupportMaterial; use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; use Thread::Semaphore; +use Unicode::Normalize; use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; @@ -261,7 +262,13 @@ sub resume_all_threads { sub encode_path { my ($path) = @_; - utf8::downgrade($path) if $^O eq 'MSWin32'; + $path = Unicode::Normalize::NFC($path); + if ($^O eq 'MSWin32') { + utf8::downgrade($path); + } else { + utf8::encode($path); + } + return $path; } @@ -273,6 +280,12 @@ sub decode_path { } else { utf8::decode($path); } + + # The filesystem might force a normalization form (like HFS+ does) so + # if we rely on the filename being comparable after the open() + readdir() + # roundtrip (like when creating and then selecting a preset), we need to + # restore our normalization form. + $path = Unicode::Normalize::NFC($path); return $path; } diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 88d7db8bab..d6b84d5cdc 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -204,6 +204,8 @@ sub select_preset { sub select_preset_by_name { my ($self, $name) = @_; + + $name = Unicode::Normalize::NFC($name); $self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}); } @@ -1462,7 +1464,7 @@ sub config { if ($self->default) { return Slic3r::Config->new_from_defaults(@$keys); } else { - if (!-e $self->file) { + if (!-e Slic3r::encode_path($self->file)) { Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); return undef; } From 854be6a1861f109763d93974e8eef39db823ea8e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 17:10:06 +0200 Subject: [PATCH 27/83] Handle OctoPrint upload of files having Unicode characters in filename or path. #2827 --- lib/Slic3r/GUI/Plater.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d48b89c3d2..fd09975b5f 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1230,13 +1230,15 @@ sub send_gcode { my $ua = LWP::UserAgent->new; $ua->timeout(180); + my $path = Slic3r::encode_path($self->{send_gcode_file}); my $res = $ua->post( "http://" . $self->{config}->octoprint_host . "/api/files/local", Content_Type => 'form-data', 'X-Api-Key' => $self->{config}->octoprint_apikey, Content => [ - # OctoPrint doesn't like Windows paths - file => [ $self->{send_gcode_file}, basename($self->{send_gcode_file}) ], + # OctoPrint doesn't like Windows paths so we use basename() + # Also, since we need to read from filesystem we process it through encode_path() + file => [ $path, basename($path) ], ], ); From ee66392e11583555977238e426d616ba5bbd4b03 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 19:44:29 +0200 Subject: [PATCH 28/83] Fix numerical issues causing incomplete raft under certain circumstances. Includes a minor refactoring of raft layer logic. #2723 --- lib/Slic3r/Print/Object.pm | 22 ++++++++++------------ lib/Slic3r/Print/SupportMaterial.pm | 4 ++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index cbee4359b4..1b5d875eb8 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -45,8 +45,9 @@ sub slice { $self->clear_layers; # make layers taking custom heights into account - my $print_z = my $slice_z = my $height = my $id = 0; - my $first_object_layer_height = -1; + my $id = 0; + my $print_z = 0; + my $first_object_layer_height = -1; my $first_object_layer_distance = -1; # add raft layers @@ -63,8 +64,8 @@ sub slice { { my @nozzle_diameters = ( map $self->print->config->get_at('nozzle_diameter', $_), - $self->config->support_material_extruder, - $self->config->support_material_interface_extruder, + $self->config->support_material_extruder-1, + $self->config->support_material_interface_extruder-1, ); $support_material_layer_height = 0.75 * min(@nozzle_diameters); } @@ -78,20 +79,17 @@ sub slice { ); $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; } - my $distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter); + $first_object_layer_distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter); # force first layer print_z according to the contact distance # (the loop below will raise print_z by such height) - if ($self->config->support_material_contact_distance == 0) { - $first_object_layer_height = $distance; - } else { - $first_object_layer_height = $nozzle_diameter; - } - $first_object_layer_distance = $distance; + $first_object_layer_height = $first_object_layer_distance - $self->config->support_material_contact_distance; } # loop until we have at least one layer and the max slice_z reaches the object height - my $max_z = unscale($self->size->z); + my $slice_z = 0; + my $height = 0; + my $max_z = unscale($self->size->z); while (($slice_z - $height) <= $max_z) { # assign the default height to the layer according to the general settings $height = ($id == 0) diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 6bd9fdb76e..dfe690a7ea 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -4,7 +4,7 @@ use Moo; use List::Util qw(sum min max); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(scale scaled_epsilon PI rad2deg deg2rad convex_hull); +use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull); use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2 intersection_pl offset2_ex diff_pl); use Slic3r::Surface ':types'; @@ -273,7 +273,7 @@ sub contact_area { my $contact_z = $layer->print_z - $self->contact_distance($layer->height, $nozzle_diameter); # ignore this contact area if it's too low - next if $contact_z < $self->object_config->get_value('first_layer_height'); + next if $contact_z < $self->object_config->get_value('first_layer_height') - epsilon; $contact{$contact_z} = [ @contact ]; $overhang{$contact_z} = [ @overhang ]; From 5ce4d8cfb04a3c57ba7a8cafa4de9f14e6334901 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 19:58:36 +0200 Subject: [PATCH 29/83] Bugfix: extra thin layers added to raft. #2723 --- lib/Slic3r/Print/SupportMaterial.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index dfe690a7ea..7fd79fff7d 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -350,16 +350,18 @@ sub support_layers_z { my $first_layer_height = $self->object_config->get_value('first_layer_height'); shift @z while @z && $z[0] <= $first_layer_height; unshift @z, $first_layer_height; - + use XXX; YYY \@z; # add raft layers by dividing the space between first layer and # first contact layer evenly if ($self->object_config->raft_layers > 1 && @z >= 2) { # $z[1] is last raft layer (contact layer for the first layer object) my $height = ($z[1] - $z[0]) / ($self->object_config->raft_layers - 1); + # since we already have two raft layers ($z[0] and $z[1]) we need to insert + # raft_layers-2 more splice @z, 1, 0, map { sprintf "%.2f", $_ } map { $z[0] + $height * $_ } - 0..($self->object_config->raft_layers - 1); + 1..($self->object_config->raft_layers - 2); } for (my $i = $#z; $i >= 0; $i--) { From 6018dafc461be44caa6573e2167c103973a9c977 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 20:03:57 +0200 Subject: [PATCH 30/83] Removed debugging statement --- lib/Slic3r/Print/SupportMaterial.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 7fd79fff7d..fe529224c8 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -350,7 +350,7 @@ sub support_layers_z { my $first_layer_height = $self->object_config->get_value('first_layer_height'); shift @z while @z && $z[0] <= $first_layer_height; unshift @z, $first_layer_height; - use XXX; YYY \@z; + # add raft layers by dividing the space between first layer and # first contact layer evenly if ($self->object_config->raft_layers > 1 && @z >= 2) { From 1dc5f5531e8d0e1ef70b6a72b320d2489416859e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 22:05:53 +0200 Subject: [PATCH 31/83] If --save is called without any other config option, export full default settings. #2110 --- slic3r.pl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/slic3r.pl b/slic3r.pl index a5957a98f3..0fbd3bbba5 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -82,7 +82,11 @@ foreach my $c (@external_configs, Slic3r::Config->new_from_cli(%cli_options)) { # save configuration if ($opt{save}) { - $cli_config->save($opt{save}); + if (@{$cli_config->get_keys} > 0) { + $cli_config->save($opt{save}); + } else { + Slic3r::Config->new_from_defaults->save($opt{save}); + } } # apply command line config on top of default config From c5f72a633abbc6b62a6c1c58490ae5118db35b46 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 2 Jun 2015 22:27:11 +0200 Subject: [PATCH 32/83] Bugfix: Repeat Last Quick Slice was affected by the plater exports. #2016 --- lib/Slic3r/GUI/MainFrame.pm | 18 +++++++++--------- lib/Slic3r/GUI/Plater.pm | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 58372fd99b..91f5230c4d 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -11,8 +11,8 @@ use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedi use Wx::Event qw(EVT_CLOSE EVT_MENU); use base 'Wx::Frame'; -our $last_input_file; -our $last_output_file; +our $qs_last_input_file; +our $qs_last_output_file; our $last_config; sub new { @@ -314,19 +314,19 @@ sub quick_slice { } $input_file = Slic3r::decode_path($dialog->GetPaths); $dialog->Destroy; - $last_input_file = $input_file unless $params{export_svg}; + $qs_last_input_file = $input_file unless $params{export_svg}; } else { - if (!defined $last_input_file) { + if (!defined $qs_last_input_file) { Wx::MessageDialog->new($self, "No previously sliced file.", 'Error', wxICON_ERROR | wxOK)->ShowModal(); return; } - if (! -e $last_input_file) { - Wx::MessageDialog->new($self, "Previously sliced file ($last_input_file) not found.", + if (! -e $qs_last_input_file) { + Wx::MessageDialog->new($self, "Previously sliced file ($qs_last_input_file) not found.", 'File Not Found', wxICON_ERROR | wxOK)->ShowModal(); return; } - $input_file = $last_input_file; + $input_file = $qs_last_input_file; } my $input_file_basename = basename($input_file); $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); @@ -361,7 +361,7 @@ sub quick_slice { # select output file my $output_file; if ($params{reslice}) { - $output_file = $last_output_file if defined $last_output_file; + $output_file = $qs_last_output_file if defined $qs_last_output_file; } elsif ($params{save_as}) { $output_file = $sprint->expanded_output_filepath; $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; @@ -373,7 +373,7 @@ sub quick_slice { return; } $output_file = Slic3r::decode_path($dlg->GetPath); - $last_output_file = $output_file unless $params{export_svg}; + $qs_last_output_file = $output_file unless $params{export_svg}; $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); wxTheApp->save_settings; $dlg->Destroy; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index fd09975b5f..d5a931f54e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1105,7 +1105,7 @@ sub export_gcode { my $path = Slic3r::decode_path($dlg->GetPath); $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path); wxTheApp->save_settings; - $self->{export_gcode_output_file} = $Slic3r::GUI::MainFrame::last_output_file = $path; + $self->{export_gcode_output_file} = $path; $dlg->Destroy; } @@ -1308,7 +1308,7 @@ sub _get_export_file { $dlg->Destroy; return undef; } - $output_file = $Slic3r::GUI::MainFrame::last_output_file = Slic3r::decode_path($dlg->GetPath); + $output_file = Slic3r::decode_path($dlg->GetPath); $dlg->Destroy; } return $output_file; From 52d3a047a09ab45d755c8224e06b303687597563 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 3 Jun 2015 09:39:10 +0200 Subject: [PATCH 33/83] One more raft issue causing extra layers. #2723 --- lib/Slic3r/Print/SupportMaterial.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index fe529224c8..0a18ebea29 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -364,7 +364,8 @@ sub support_layers_z { 1..($self->object_config->raft_layers - 2); } - for (my $i = $#z; $i >= 0; $i--) { + # create other layers (skip raft layers as they're already done and use thicker layers) + for (my $i = $#z; $i >= $self->object_config->raft_layers; $i--) { my $target_height = $support_material_height; if ($i > 0 && $top{ $z[$i-1] }) { $target_height = $nozzle_diameter; From eff0620ddf34be003cbae514f2f2406d50da36d9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 3 Jun 2015 13:19:43 +0200 Subject: [PATCH 34/83] Prompt user when overwriting a file during G-code export as well. #2882 --- lib/Slic3r/GUI/MainFrame.pm | 2 +- lib/Slic3r/GUI/Plater.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 91f5230c4d..8375a79944 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -367,7 +367,7 @@ sub quick_slice { $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', wxTheApp->output_path(dirname($output_file)), - basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE); + basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d5a931f54e..eee5061ee3 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1097,7 +1097,7 @@ sub export_gcode { } else { my $default_output_file = $self->{print}->expanded_output_filepath($main::opt{output}); my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)), - basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE); + basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; From 911bed827c9e3b8f88dbbf313a02f2dffd8be34c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 9 Jun 2015 14:54:52 +0200 Subject: [PATCH 35/83] Fixed regression in STL parser. #2886 --- xs/src/admesh/stlinit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/admesh/stlinit.c b/xs/src/admesh/stlinit.c index e92af81047..7f490c7882 100644 --- a/xs/src/admesh/stlinit.c +++ b/xs/src/admesh/stlinit.c @@ -290,7 +290,7 @@ stl_read(stl_file *stl, int first_facet, int first) { // skip solid/endsolid // (in this order, otherwise it won't work when they are paired in the middle of a file) fscanf(stl->fp, "endsolid\n"); - fscanf(stl->fp, "solid %*s\n"); + fscanf(stl->fp, "solid %*[^\n]\n"); // name might contain spaces so %*s doesn't work if((fscanf(stl->fp, " facet normal %f %f %f\n", &facet.normal.x, &facet.normal.y, &facet.normal.z) + \ fscanf(stl->fp, " outer loop\n") + \ From 1e23b82e240b3550e237440b73b6afc6a4e37750 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 9 Jun 2015 16:04:26 +0200 Subject: [PATCH 36/83] Bugfix: small gaps were left between infill and perimeters when solid infill extrusion width was much thinner than internal infill extrusion width. #2895 --- lib/Slic3r/Fill.pm | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 09fba7a291..a39b77fc4b 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -1,6 +1,7 @@ package Slic3r::Fill; use Moo; +use List::Util qw(max); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Fill::3DHoneycomb; use Slic3r::Fill::Base; @@ -143,8 +144,12 @@ sub make_fill { # we are going to grow such regions by overlapping them with the void (if any) # TODO: detect and investigate whether there could be narrow regions without # any void neighbors - my $distance_between_surfaces = $infill_flow->scaled_spacing; { + my $distance_between_surfaces = max( + $infill_flow->scaled_spacing, + $solid_infill_flow->scaled_spacing, + $top_solid_infill_flow->scaled_spacing, + ); my $collapsed = diff( [ map @{$_->expolygon}, @surfaces ], offset2([ map @{$_->expolygon}, @surfaces ], -$distance_between_surfaces/2, +$distance_between_surfaces/2), @@ -163,9 +168,6 @@ sub make_fill { )}; } - # add spacing between surfaces - @surfaces = map @{$_->offset(-$distance_between_surfaces / 2)}, @surfaces; - if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", @@ -234,11 +236,14 @@ sub make_fill { $f->z($layerm->print_z); $f->angle(deg2rad($layerm->config->fill_angle)); $f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); - my @polylines = $f->fill_surface( - $surface, + + # apply half spacing using this flow's own spacing and generate infill + my @polylines = map $f->fill_surface( + $_, density => $density/100, layer_height => $h, - ); + ), @{ $surface->offset(-scale($f->spacing)/2) }; + next unless @polylines; # calculate actual flow from spacing (which might have been adjusted by the infill From e7159746888bc9b66a3d7fd44357eb34698eacec Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 9 Jun 2015 17:15:01 +0200 Subject: [PATCH 37/83] Better validation for speeds (prevent 0mm/s travel speed). #2893 --- xs/src/libslic3r/PrintConfig.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 689a334c3e..6284cd0279 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -155,6 +155,7 @@ PrintConfigDef::build_def() { Options["external_perimeter_speed"].sidetext = "mm/s or %"; Options["external_perimeter_speed"].cli = "external-perimeter-speed=s"; Options["external_perimeter_speed"].ratio_over = "perimeter_speed"; + Options["external_perimeter_speed"].min = 0; Options["external_perimeters_first"].type = coBool; Options["external_perimeters_first"].label = "External perimeters first"; @@ -350,6 +351,7 @@ PrintConfigDef::build_def() { Options["first_layer_speed"].tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds."; Options["first_layer_speed"].sidetext = "mm/s or %"; Options["first_layer_speed"].cli = "first-layer-speed=s"; + Options["first_layer_speed"].min = 0; Options["first_layer_temperature"].type = coInts; Options["first_layer_temperature"].label = "First layer"; @@ -499,7 +501,6 @@ PrintConfigDef::build_def() { Options["min_print_speed"].sidetext = "mm/s"; Options["min_print_speed"].cli = "min-print-speed=f"; Options["min_print_speed"].min = 0; - Options["min_print_speed"].max = 1000; Options["max_print_speed"].type = coFloat; Options["max_print_speed"].label = "Max print speed"; @@ -507,7 +508,6 @@ PrintConfigDef::build_def() { Options["max_print_speed"].sidetext = "mm/s"; Options["max_print_speed"].cli = "max-print-speed=f"; Options["max_print_speed"].min = 1; - Options["max_print_speed"].max = 1000; Options["max_volumetric_speed"].type = coFloat; Options["max_volumetric_speed"].label = "Max volumetric speed"; @@ -515,7 +515,6 @@ PrintConfigDef::build_def() { Options["max_volumetric_speed"].sidetext = "mm³/s"; Options["max_volumetric_speed"].cli = "max-volumetric-speed=f"; Options["max_volumetric_speed"].min = 0; - Options["max_volumetric_speed"].max = 1000; Options["min_skirt_length"].type = coFloat; Options["min_skirt_length"].label = "Minimum extrusion length"; @@ -689,7 +688,6 @@ PrintConfigDef::build_def() { Options["retract_speed"].tooltip = "The speed for retractions (it only applies to the extruder motor)."; Options["retract_speed"].sidetext = "mm/s"; Options["retract_speed"].cli = "retract-speed=f@"; - Options["retract_speed"].max = 1000; Options["seam_position"].type = coEnum; Options["seam_position"].label = "Seam position"; @@ -740,6 +738,7 @@ PrintConfigDef::build_def() { Options["small_perimeter_speed"].sidetext = "mm/s or %"; Options["small_perimeter_speed"].cli = "small-perimeter-speed=s"; Options["small_perimeter_speed"].ratio_over = "perimeter_speed"; + Options["small_perimeter_speed"].min = 0; Options["solid_infill_below_area"].type = coFloat; Options["solid_infill_below_area"].label = "Solid infill threshold area"; @@ -779,6 +778,7 @@ PrintConfigDef::build_def() { Options["solid_infill_speed"].cli = "solid-infill-speed=s"; Options["solid_infill_speed"].ratio_over = "infill_speed"; Options["solid_infill_speed"].aliases.push_back("solid_infill_feed_rate"); + Options["solid_infill_speed"].min = 0; Options["solid_layers"].type = coInt; Options["solid_layers"].label = "Solid layers"; @@ -890,6 +890,7 @@ PrintConfigDef::build_def() { Options["support_material_interface_speed"].sidetext = "mm/s or %"; Options["support_material_interface_speed"].cli = "support-material-interface-speed=s"; Options["support_material_interface_speed"].ratio_over = "support_material_speed"; + Options["support_material_interface_speed"].min = 0; Options["support_material_pattern"].type = coEnum; Options["support_material_pattern"].label = "Pattern"; @@ -975,6 +976,7 @@ PrintConfigDef::build_def() { Options["top_solid_infill_speed"].sidetext = "mm/s or %"; Options["top_solid_infill_speed"].cli = "top-solid-infill-speed=s"; Options["top_solid_infill_speed"].ratio_over = "solid_infill_speed"; + Options["top_solid_infill_speed"].min = 0; Options["top_solid_layers"].type = coInt; Options["top_solid_layers"].label = "Top"; @@ -990,7 +992,7 @@ PrintConfigDef::build_def() { Options["travel_speed"].sidetext = "mm/s"; Options["travel_speed"].cli = "travel-speed=f"; Options["travel_speed"].aliases.push_back("travel_feed_rate"); - Options["travel_speed"].min = 0; + Options["travel_speed"].min = 1; Options["use_firmware_retraction"].type = coBool; Options["use_firmware_retraction"].label = "Use firmware retraction"; From 4a91ea817aa15bafdb4cb2dac723361a95d9d072 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 9 Jun 2015 17:18:43 +0200 Subject: [PATCH 38/83] Releasing 1.2.8 --- xs/src/libslic3r/libslic3r.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index fc3bd17932..09b2b6a23e 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -6,7 +6,7 @@ #include #include -#define SLIC3R_VERSION "1.2.8-dev" +#define SLIC3R_VERSION "1.2.8" #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 From 552430db679cbceebe5b7db761c5624fd53eead7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Jun 2015 11:41:55 +0200 Subject: [PATCH 39/83] More fixes for Unicode path handling (thanks @josefprusa for Czech test VM) --- Build.PL | 2 ++ lib/Slic3r.pm | 18 +++++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Build.PL b/Build.PL index ff86430bbf..e637ebf314 100644 --- a/Build.PL +++ b/Build.PL @@ -7,6 +7,8 @@ use Config; use File::Spec; my %prereqs = qw( + Encode 0 + Encode::Locale 1.05 ExtUtils::MakeMaker 6.80 ExtUtils::ParseXS 3.22 File::Basename 0 diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 0e80381190..154af904b8 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -30,7 +30,7 @@ warn "Running Slic3r under Perl 5.16 is not supported nor recommended\n" if $^V == v5.16; use FindBin; -our $var = "$FindBin::Bin/var"; +our $var = decode_path($FindBin::Bin) . "/var"; use Moo 1.003001; @@ -71,6 +71,8 @@ use Slic3r::Print::SupportMaterial; use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; use Thread::Semaphore; +use Encode::Locale 1.05; +use Encode; use Unicode::Normalize; use constant SCALING_FACTOR => 0.000001; @@ -263,11 +265,7 @@ sub encode_path { my ($path) = @_; $path = Unicode::Normalize::NFC($path); - if ($^O eq 'MSWin32') { - utf8::downgrade($path); - } else { - utf8::encode($path); - } + $path = Encode::encode(locale_fs => $path); return $path; } @@ -275,17 +273,15 @@ sub encode_path { sub decode_path { my ($path) = @_; - if ($^O eq 'MSWin32') { - utf8::upgrade($path); - } else { - utf8::decode($path); - } + $path = Encode::decode(locale_fs => $path) + unless utf8::is_utf8($path); # The filesystem might force a normalization form (like HFS+ does) so # if we rely on the filename being comparable after the open() + readdir() # roundtrip (like when creating and then selecting a preset), we need to # restore our normalization form. $path = Unicode::Normalize::NFC($path); + return $path; } From 7a34078f5f8c55339a9ff64b07769a93ee8a0a52 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Jun 2015 11:42:37 +0200 Subject: [PATCH 40/83] Bump version number --- xs/src/libslic3r/libslic3r.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 09b2b6a23e..f71481a6f7 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -6,7 +6,7 @@ #include #include -#define SLIC3R_VERSION "1.2.8" +#define SLIC3R_VERSION "1.2.9-dev" #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 From 38a9e32a287c4a601e256ebd96533f68c0c8dcc0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Jun 2015 19:48:46 +0200 Subject: [PATCH 41/83] Limit bridge over sparse infill to areas that can absorb such extrudate. #2899 --- xs/src/libslic3r/PrintObject.cpp | 62 ++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 46d08e06f2..7c184c2580 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -349,30 +349,70 @@ PrintObject::bridge_over_infill() double fill_density = (*region)->config.fill_density.value; if (fill_density == 100) continue; + // get bridge flow + Flow bridge_flow = (*region)->flow( + frSolidInfill, + -1, // layer height, not relevant for bridge flow + true, // bridge + false, // first layer + -1, // custom width, not relevant for bridge flow + *this + ); + FOREACH_LAYER(this, layer_it) { if (layer_it == this->layers.begin()) continue; Layer* layer = *layer_it; - Layer* lower_layer = *(layer_it - 1); LayerRegion* layerm = layer->get_region(region_id); - // compute the areas needing bridge math - Polygons internal_solid, lower_internal; + // extract the stInternalSolid surfaces that might be transformed into bridges + Polygons internal_solid; layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); - FOREACH_LAYERREGION(lower_layer, lower_layerm_it) - (*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal); + // check whether the lower area is deep enough for absorbing the extra flow + // (for obvious physical reasons but also for preventing the bridge extrudates + // from overflowing in 3D preview) ExPolygons to_bridge; - intersection(internal_solid, lower_internal, &to_bridge); - if (to_bridge.empty()) continue; - - ExPolygons not_to_bridge; - diff(internal_solid, to_bridge, ¬_to_bridge, true); + { + Polygons to_bridge_pp = internal_solid; + + // iterate through lower layers spanned by bridge_flow + double bottom_z = layer->print_z - bridge_flow.height; + for (int i = (layer_it - this->layers.begin()) - 1; i >= 0; --i) { + Layer* lower_layer = this->layers[i]; + + // stop iterating if layer is lower than bottom_z + if (lower_layer->print_z < bottom_z) break; + + // iterate through regions and collect internal surfaces + Polygons lower_internal; + FOREACH_LAYERREGION(lower_layer, lower_layerm_it) + (*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal); + + // intersect such lower internal surfaces with the candidate solid surfaces + intersection(to_bridge_pp, lower_internal, &to_bridge_pp); + } + + // there's no point in bridging too thin/short regions + { + double min_width = bridge_flow.scaled_width() * 3; + offset2(to_bridge_pp, &to_bridge_pp, -min_width, +min_width); + } + + if (to_bridge_pp.empty()) continue; + + // convert into ExPolygons + union_(to_bridge_pp, &to_bridge); + } #ifdef SLIC3R_DEBUG - printf("Bridging %zu internal areas at layer %d\n", to_bridge.size(), layer->id()); + printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); #endif + // compute the remaning internal solid surfaces as difference + ExPolygons not_to_bridge; + diff(internal_solid, to_bridge, ¬_to_bridge, true); + // build the new collection of fill_surfaces { Surfaces new_surfaces; From bc69d6da81e1104b9eb8f5bc0c1467da25bae723 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 14 Jun 2015 11:28:33 +0200 Subject: [PATCH 42/83] Minor adjustment of infill_overlap math --- lib/Slic3r/Layer/PerimeterGenerator.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index acb3bbba09..9caada38e5 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -320,7 +320,10 @@ sub process { # two or more loops $inset += $pspacing/2; } - $inset -= $self->config->get_abs_value_over('infill_overlap', $pwidth); + + # only apply infill overlap if we actually have one perimeter + $inset -= $self->config->get_abs_value_over('infill_overlap', $inset + $ispacing/2) + if $inset > 0; my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); $self->fill_surfaces->append($_) From 539cde8d7a6ad49b3a7404a7776a3c4d55f1f35f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 15 Jun 2015 17:00:10 +0200 Subject: [PATCH 43/83] Raise the thickness threshold used for generating thin walls. TODO: don't enforce this at the segment level but consider the average thickness of an entire polyline and compare it to the total length. #2910 --- lib/Slic3r/Layer/PerimeterGenerator.pm | 10 ++++------ xs/src/libslic3r/Geometry.cpp | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index 9caada38e5..d77f57bdbc 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -24,7 +24,6 @@ has '_holes_pt' => (is => 'rw'); has '_ext_mm3_per_mm' => (is => 'rw'); has '_mm3_per_mm' => (is => 'rw'); has '_mm3_per_mm_overhang' => (is => 'rw'); -has '_thin_wall_polylines' => (is => 'rw', default => sub { [] }); # generated loops will be put here has 'loops' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new }); @@ -133,7 +132,7 @@ sub process { # the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width # (actually, something larger than that still may exist due to mitering or other causes) - my $min_width = $ext_pwidth / 4; + my $min_width = $ext_pwidth / 2; @thin_walls = @{offset2_ex($diff, -$min_width/2, +$min_width/2)}; # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop @@ -145,10 +144,9 @@ sub process { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "medial_axis.svg", - no_arrows => 1, - expolygons => union_ex($diff), - green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ], - red_polylines => $self->_thin_wall_polylines, + no_arrows => 1, + expolygons => \@expp, + polylines => \@thin_walls, ); } } diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index 5a7b2e3dcf..827029aa07 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -504,7 +504,7 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const //printf(" => too thin, skipping\n"); return false; } - + return true; } From 6dc3caa8b24daf32fea8f03224495cb69ed6af08 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 15 Jun 2015 17:08:12 +0200 Subject: [PATCH 44/83] Typo --- lib/Slic3r/Layer/PerimeterGenerator.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index d77f57bdbc..07da50f506 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -145,7 +145,7 @@ sub process { Slic3r::SVG::output( "medial_axis.svg", no_arrows => 1, - expolygons => \@expp, + #expolygons => \@expp, polylines => \@thin_walls, ); } From be635c69e7d8d9a33ccea4a0dae230fead9f8af9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 15 Jun 2015 17:17:36 +0200 Subject: [PATCH 45/83] Fixed regression casusing some rare STL files not to parsed correctly because of lack of the solid name. #2914 --- xs/src/admesh/stlinit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/admesh/stlinit.c b/xs/src/admesh/stlinit.c index 7f490c7882..e6f3556f29 100644 --- a/xs/src/admesh/stlinit.c +++ b/xs/src/admesh/stlinit.c @@ -290,7 +290,7 @@ stl_read(stl_file *stl, int first_facet, int first) { // skip solid/endsolid // (in this order, otherwise it won't work when they are paired in the middle of a file) fscanf(stl->fp, "endsolid\n"); - fscanf(stl->fp, "solid %*[^\n]\n"); // name might contain spaces so %*s doesn't work + fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") if((fscanf(stl->fp, " facet normal %f %f %f\n", &facet.normal.x, &facet.normal.y, &facet.normal.z) + \ fscanf(stl->fp, " outer loop\n") + \ From 455a1062ef73ac5240496b69ecc19cb4df881cb9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Jun 2015 00:34:05 +0200 Subject: [PATCH 46/83] Fix minor rendering glitch in 2D toolpaths preview --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 3daa3b94cd..7c1f487134 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -262,8 +262,9 @@ sub set_z { } } + # reverse layers so that we draw the lowermost (i.e. current) on top $self->z($z); - $self->layers([ @layers ]); + $self->layers([ reverse @layers ]); $self->Refresh; } From 3cd6c78044ebef6e3fe83d3e773611bbe065a23b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Jun 2015 10:38:28 +0200 Subject: [PATCH 47/83] Releasing 1.2.9 --- xs/src/libslic3r/libslic3r.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index f71481a6f7..965174959b 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -6,7 +6,7 @@ #include #include -#define SLIC3R_VERSION "1.2.9-dev" +#define SLIC3R_VERSION "1.2.9" #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 From 4a39665804905df9de466130191c0d59902a8400 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 20 Jun 2015 14:16:23 +0200 Subject: [PATCH 48/83] Bugfix: binary ASCII files were not written with the correct fopen() mode. #2928 --- xs/src/admesh/stl_io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/admesh/stl_io.c b/xs/src/admesh/stl_io.c index 2a530997b4..e015cee99e 100644 --- a/xs/src/admesh/stl_io.c +++ b/xs/src/admesh/stl_io.c @@ -269,7 +269,7 @@ stl_write_binary(stl_file *stl, const char *file, const char *label) { if (stl->error) return; /* Open the file */ - fp = fopen(file, "w"); + fp = fopen(file, "wb"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ From be1f35c51619a9ab4baf61862dd5083652f68257 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 20 Jun 2015 15:56:54 +0200 Subject: [PATCH 49/83] Disable testing of modules that have known broken tests --- Build.PL | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Build.PL b/Build.PL index e637ebf314..0c6b011d39 100644 --- a/Build.PL +++ b/Build.PL @@ -118,7 +118,11 @@ EOF foreach my $module (sort keys %modules) { my $version = $modules{$module}; my @cmd = ($cpanm, @cpanm_args); - push @cmd, '--notest', if $module eq 'OpenGL'; # temporary workaround for upstream bug in test + + # temporary workaround for upstream bug in test + push @cmd, '--notest' + if $module =~ /^(?:OpenGL|Math::PlanePath|Test::Harness)$/; + push @cmd, "$module~$version"; if ($module eq 'XML::SAX::ExpatXS' && $^O eq 'MSWin32') { my $mingw = 'C:\dev\CitrusPerl\mingw64'; From 3b7cb6722c42211319bd4c9ef5b9e7e95cb0e3c1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 20 Jun 2015 16:10:59 +0200 Subject: [PATCH 50/83] Add perl 5.22 to Travis CI --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4cbb89cace..bf27536526 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ perl: - "5.14" - "5.18" - "5.20" + - "5.22" branches: only: - master From 724e668a9427c3ab0cfbf2e939cef345b2e27c1a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 20 Jun 2015 16:33:12 +0200 Subject: [PATCH 51/83] Revert "Add perl 5.22 to Travis CI" This reverts commit 3b7cb6722c42211319bd4c9ef5b9e7e95cb0e3c1. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bf27536526..4cbb89cace 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ perl: - "5.14" - "5.18" - "5.20" - - "5.22" branches: only: - master From f361d8ad4337019af05618dd3dca8d28497c5182 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 17:56:38 +0200 Subject: [PATCH 52/83] Ported PlaceholderParser::apply_env_variables() to XS --- lib/Slic3r/GCode/PlaceholderParser.pm | 14 -------------- xs/src/libslic3r/PlaceholderParser.cpp | 21 +++++++++++++++++++-- xs/src/libslic3r/PlaceholderParser.hpp | 1 + xs/xsp/PlaceholderParser.xsp | 3 ++- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/Slic3r/GCode/PlaceholderParser.pm b/lib/Slic3r/GCode/PlaceholderParser.pm index 2f76c4ff4f..1bbb22f676 100644 --- a/lib/Slic3r/GCode/PlaceholderParser.pm +++ b/lib/Slic3r/GCode/PlaceholderParser.pm @@ -2,20 +2,6 @@ package Slic3r::GCode::PlaceholderParser; use strict; use warnings; -sub new { - # TODO: move this code to C++ constructor, remove this method - my ($class) = @_; - - my $self = $class->_new; - $self->apply_env_variables; - return $self; -} - -sub apply_env_variables { - my ($self) = @_; - $self->_single_set($_, $ENV{$_}) for grep /^SLIC3R_/, keys %ENV; -} - sub process { my ($self, $string, $extra) = @_; diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index 583cf51e62..1d40f94b72 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -2,14 +2,16 @@ #include #include #include +#include // provides **environ + +extern char **environ; namespace Slic3r { PlaceholderParser::PlaceholderParser() { this->_single["version"] = SLIC3R_VERSION; - // TODO: port these methods to C++, then call them here - // this->apply_env_variables(); + this->apply_env_variables(); this->update_timestamp(); } @@ -76,6 +78,21 @@ void PlaceholderParser::apply_config(DynamicPrintConfig &config) } } +void +PlaceholderParser::apply_env_variables() +{ + for (char** env = environ; *env; env++) { + if (strncmp(*env, "SLIC3R_", 7) == 0) { + std::stringstream ss(*env); + std::string key, value; + std::getline(ss, key, '='); + ss >> value; + + this->set(key, value); + } + } +} + void PlaceholderParser::set(const std::string &key, const std::string &value) { diff --git a/xs/src/libslic3r/PlaceholderParser.hpp b/xs/src/libslic3r/PlaceholderParser.hpp index 25d1bcdc36..1488e19f26 100644 --- a/xs/src/libslic3r/PlaceholderParser.hpp +++ b/xs/src/libslic3r/PlaceholderParser.hpp @@ -20,6 +20,7 @@ class PlaceholderParser PlaceholderParser(); void update_timestamp(); void apply_config(DynamicPrintConfig &config); + void apply_env_variables(); void set(const std::string &key, const std::string &value); void set(const std::string &key, int value); void set(const std::string &key, const std::vector &values); diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp index b36a471892..5a0cccc106 100644 --- a/xs/xsp/PlaceholderParser.xsp +++ b/xs/xsp/PlaceholderParser.xsp @@ -7,10 +7,11 @@ %} %name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser { - %name{_new} PlaceholderParser(); + PlaceholderParser(); ~PlaceholderParser(); void update_timestamp(); + void apply_env_variables(); void apply_config(DynamicPrintConfig *config) %code%{ THIS->apply_config(*config); %}; void set(std::string key, std::string value); From 249088b4f8d5ac61c97c6590e6d8b3cb3ba6f88b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 18:18:25 +0200 Subject: [PATCH 53/83] Ported Config::setenv() to XS --- lib/Slic3r/Config.pm | 8 ------- xs/src/libslic3r/Config.cpp | 46 ++++++++++++++++++++++++++----------- xs/src/libslic3r/Config.hpp | 7 +++--- xs/xsp/Config.xsp | 24 +++++++++---------- 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index bc04558891..23ab7ea897 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -192,14 +192,6 @@ sub save { __PACKAGE__->write_ini($file, $self->as_ini); } -sub setenv { - my $self = shift; - - foreach my $opt_key (@{$self->get_keys}) { - $ENV{"SLIC3R_" . uc $opt_key} = $self->serialize($opt_key); - } -} - # this method is idempotent by design and only applies to ::DynamicConfig or ::Full # objects because it performs cross checks sub validate { diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index d1c51ac531..07a8e86371 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -1,4 +1,5 @@ #include "Config.hpp" +#include // for setenv() namespace Slic3r { @@ -10,8 +11,7 @@ ConfigBase::has(const t_config_option_key opt_key) { void ConfigBase::apply(const ConfigBase &other, bool ignore_nonexistent) { // get list of option keys to apply - t_config_option_keys opt_keys; - other.keys(&opt_keys); + t_config_option_keys opt_keys = other.keys(); // loop through options and apply them for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { @@ -37,8 +37,7 @@ t_config_option_keys ConfigBase::diff(ConfigBase &other) { t_config_option_keys diff; - t_config_option_keys my_keys; - this->keys(&my_keys); + t_config_option_keys my_keys = this->keys(); for (t_config_option_keys::const_iterator opt_key = my_keys.begin(); opt_key != my_keys.end(); ++opt_key) { if (other.has(*opt_key) && other.serialize(*opt_key) != this->serialize(*opt_key)) { diff.push_back(*opt_key); @@ -98,14 +97,31 @@ ConfigBase::get_abs_value(const t_config_option_key opt_key, double ratio_over) return opt->get_abs_value(ratio_over); } +void +ConfigBase::setenv_() +{ + t_config_option_keys opt_keys = this->keys(); + for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { + // prepend the SLIC3R_ prefix + std::ostringstream ss; + ss << "SLIC3R_"; + ss << *it; + std::string envname = ss.str(); + + // capitalize environment variable name + for (size_t i = 0; i < envname.size(); ++i) + envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i]; + + setenv(envname.c_str(), this->serialize(*it).c_str(), 1); + } +} + #ifdef SLIC3RXS SV* ConfigBase::as_hash() { HV* hv = newHV(); - t_config_option_keys opt_keys; - this->keys(&opt_keys); - + t_config_option_keys opt_keys = this->keys(); for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) (void)hv_store( hv, it->c_str(), it->length(), this->get(*it), 0 ); @@ -368,10 +384,12 @@ DynamicConfig::option(const t_config_option_key opt_key) const { return const_cast(this)->option(opt_key, false); } -void -DynamicConfig::keys(t_config_option_keys *keys) const { +t_config_option_keys +DynamicConfig::keys() const { + t_config_option_keys keys; for (t_options_map::const_iterator it = this->options.begin(); it != this->options.end(); ++it) - keys->push_back(it->first); + keys.push_back(it->first); + return keys; } void @@ -379,12 +397,14 @@ DynamicConfig::erase(const t_config_option_key opt_key) { this->options.erase(opt_key); } -void -StaticConfig::keys(t_config_option_keys *keys) const { +t_config_option_keys +StaticConfig::keys() const { + t_config_option_keys keys; for (t_optiondef_map::const_iterator it = this->def->begin(); it != this->def->end(); ++it) { const ConfigOption* opt = this->option(it->first); - if (opt != NULL) keys->push_back(it->first); + if (opt != NULL) keys.push_back(it->first); } + return keys; } const ConfigOption* diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 49e999bc09..be433633db 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -515,7 +515,7 @@ class ConfigBase bool has(const t_config_option_key opt_key); virtual ConfigOption* option(const t_config_option_key opt_key, bool create = false) = 0; virtual const ConfigOption* option(const t_config_option_key opt_key) const = 0; - virtual void keys(t_config_option_keys *keys) const = 0; + virtual t_config_option_keys keys() const = 0; void apply(const ConfigBase &other, bool ignore_nonexistent = false); bool equals(ConfigBase &other); t_config_option_keys diff(ConfigBase &other); @@ -524,6 +524,7 @@ class ConfigBase void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false); double get_abs_value(const t_config_option_key opt_key); double get_abs_value(const t_config_option_key opt_key, double ratio_over); + void setenv_(); #ifdef SLIC3RXS SV* as_hash(); @@ -545,7 +546,7 @@ class DynamicConfig : public ConfigBase template T* opt(const t_config_option_key opt_key, bool create = false); ConfigOption* option(const t_config_option_key opt_key, bool create = false); const ConfigOption* option(const t_config_option_key opt_key) const; - void keys(t_config_option_keys *keys) const; + t_config_option_keys keys() const; void erase(const t_config_option_key opt_key); private: @@ -556,7 +557,7 @@ class DynamicConfig : public ConfigBase class StaticConfig : public ConfigBase { public: - void keys(t_config_option_keys *keys) const; + t_config_option_keys keys() const; virtual ConfigOption* option(const t_config_option_key opt_key, bool create = false) = 0; const ConfigOption* option(const t_config_option_key opt_key) const; diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 4ec282edd5..3628d524ef 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -27,10 +27,10 @@ %code{% RETVAL = THIS->equals(*other); %}; void apply_static(FullPrintConfig* other) %code{% THIS->apply(*other, true); %}; - std::vector get_keys() - %code{% THIS->keys(&RETVAL); %}; + %name{get_keys} std::vector keys(); void erase(t_config_option_key opt_key); void normalize(); + %name{setenv} void setenv_(); }; %name{Slic3r::Config::GCode} class GCodeConfig { @@ -51,9 +51,9 @@ %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; - std::vector get_keys() - %code{% THIS->keys(&RETVAL); %}; + %name{get_keys} std::vector keys(); std::string get_extrusion_axis(); + %name{setenv} void setenv_(); }; %name{Slic3r::Config::Print} class PrintConfig { @@ -72,9 +72,9 @@ double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; - std::vector get_keys() - %code{% THIS->keys(&RETVAL); %}; + %name{get_keys} std::vector keys(); std::string get_extrusion_axis(); + %name{setenv} void setenv_(); }; %name{Slic3r::Config::PrintRegion} class PrintRegionConfig { @@ -95,8 +95,8 @@ %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; - std::vector get_keys() - %code{% THIS->keys(&RETVAL); %}; + %name{get_keys} std::vector keys(); + %name{setenv} void setenv_(); }; %name{Slic3r::Config::PrintObject} class PrintObjectConfig { @@ -117,8 +117,8 @@ %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; - std::vector get_keys() - %code{% THIS->keys(&RETVAL); %}; + %name{get_keys} std::vector keys(); + %name{setenv} void setenv_(); }; %name{Slic3r::Config::Full} class FullPrintConfig { @@ -143,9 +143,9 @@ %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; - std::vector get_keys() - %code{% THIS->keys(&RETVAL); %}; + %name{get_keys} std::vector keys(); std::string get_extrusion_axis(); + %name{setenv} void setenv_(); }; %package{Slic3r::Config}; From 580d28d071c6c7d3338dc643b99e41773a540846 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 19:35:22 +0200 Subject: [PATCH 54/83] Finished porting PlaceholderParser to XS --- lib/Slic3r.pm | 1 - lib/Slic3r/GCode.pm | 8 ++-- lib/Slic3r/GCode/PlaceholderParser.pm | 28 ----------- lib/Slic3r/Print.pm | 10 ---- lib/Slic3r/Print/GCode.pm | 20 ++++---- t/custom_gcode.t | 3 +- xs/src/libslic3r/PlaceholderParser.cpp | 66 +++++++++++++++++++++----- xs/src/libslic3r/PlaceholderParser.hpp | 15 ++++-- xs/xsp/PlaceholderParser.xsp | 32 ++----------- xs/xsp/Print.xsp | 2 +- 10 files changed, 87 insertions(+), 98 deletions(-) delete mode 100644 lib/Slic3r/GCode/PlaceholderParser.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 154af904b8..b2ba77e284 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -48,7 +48,6 @@ use Slic3r::GCode; use Slic3r::GCode::ArcFitting; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; -use Slic3r::GCode::PlaceholderParser; use Slic3r::GCode::PressureRegulator; use Slic3r::GCode::Reader; use Slic3r::GCode::SpiralVase; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index ecd58d0481..b486ba0b0e 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -482,10 +482,10 @@ sub set_extruder { # append custom toolchange G-code if (defined $self->writer->extruder && $self->config->toolchange_gcode) { - $gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->config->toolchange_gcode, { - previous_extruder => $self->writer->extruder->id, - next_extruder => $extruder_id, - }); + my $pp = $self->placeholder_parser->clone; + $pp->set('previous_extruder' => $self->writer->extruder->id); + $pp->set('next_extruder' => $extruder_id); + $gcode .= sprintf "%s\n", $pp->process($self->config->toolchange_gcode); } # if ooze prevention is enabled, park current extruder in the nearest diff --git a/lib/Slic3r/GCode/PlaceholderParser.pm b/lib/Slic3r/GCode/PlaceholderParser.pm deleted file mode 100644 index 1bbb22f676..0000000000 --- a/lib/Slic3r/GCode/PlaceholderParser.pm +++ /dev/null @@ -1,28 +0,0 @@ -package Slic3r::GCode::PlaceholderParser; -use strict; -use warnings; - -sub process { - my ($self, $string, $extra) = @_; - - # extra variables have priority over the stored ones - if ($extra) { - my $regex = join '|', keys %$extra; - $string =~ s/\[($regex)\]/$extra->{$1}/eg; - } - { - my $regex = join '|', @{$self->_single_keys}; - $string =~ s/\[($regex)\]/$self->_single_get("$1")/eg; - } - { - my $regex = join '|', @{$self->_multiple_keys}; - $string =~ s/\[($regex)\]/$self->_multiple_get("$1")/egx; - - # unhandled indices are populated using the first value - $string =~ s/\[($regex)_\d+\]/$self->_multiple_get("$1")/egx; - } - - return $string; -} - -1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 77488e5594..174dcf3353 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -14,16 +14,6 @@ use Slic3r::Print::State ':steps'; our $status_cb; -sub new { - # TODO: port PlaceholderParser methods to C++, then its own constructor - # can call them and no need for this new() method at all - my ($class) = @_; - my $self = $class->_new; - $self->placeholder_parser->apply_env_variables; - $self->placeholder_parser->update_timestamp; - return $self; -} - sub set_status_cb { my ($class, $cb) = @_; $status_cb = $cb; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 8965687ae6..1b7cfbc4b0 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -378,15 +378,19 @@ sub process_layer { } # set new layer - this will change Z and force a retraction if retract_layer_change is enabled - $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->before_layer_gcode, { - layer_num => $self->_gcodegen->layer_index + 1, - layer_z => $layer->print_z, - }) . "\n" if $self->print->config->before_layer_gcode; + if ($self->print->config->before_layer_gcode) { + my $pp = $self->_gcodegen->placeholder_parser->clone; + $pp->set('layer_num' => $self->_gcodegen->layer_index + 1); + $pp->set('layer_z' => $layer->print_z); + $gcode .= $pp->process($self->print->config->before_layer_gcode) . "\n"; + } $gcode .= $self->_gcodegen->change_layer($layer); # this will increase $self->_gcodegen->layer_index - $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, { - layer_num => $self->_gcodegen->layer_index, - layer_z => $layer->print_z, - }) . "\n" if $self->print->config->layer_gcode; + if ($self->print->config->layer_gcode) { + my $pp = $self->_gcodegen->placeholder_parser->clone; + $pp->set('layer_num' => $self->_gcodegen->layer_index); + $pp->set('layer_z' => $layer->print_z); + $gcode .= $pp->process($self->print->config->layer_gcode) . "\n"; + } # extrude skirt along raft layers and normal object layers # (not along interlaced support material layers) diff --git a/t/custom_gcode.t b/t/custom_gcode.t index cbd5741f49..9d68d1eed7 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -47,7 +47,8 @@ use Slic3r::Test; { my $parser = Slic3r::GCode::PlaceholderParser->new; $parser->apply_config(my $config = Slic3r::Config->new_from_defaults); - is $parser->process('[temperature_[foo]]', { foo => '1' }), + $parser->set('foo' => '0'); + is $parser->process('[temperature_[foo]]'), $config->temperature->[0], "nested config options"; } diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index 1d40f94b72..983905703b 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -10,7 +10,7 @@ namespace Slic3r { PlaceholderParser::PlaceholderParser() { - this->_single["version"] = SLIC3R_VERSION; + this->set("version", SLIC3R_VERSION); this->apply_env_variables(); this->update_timestamp(); } @@ -64,16 +64,17 @@ void PlaceholderParser::apply_config(DynamicPrintConfig &config) if (const ConfigOptionVectorBase* optv = dynamic_cast(opt)) { // set placeholders for options with multiple values + // TODO: treat [bed_shape] as single, not multiple this->set(key, optv->vserialize()); } else if (const ConfigOptionPoint* optp = dynamic_cast(opt)) { - this->_single[key] = optp->serialize(); + this->set(key, optp->serialize()); Pointf val = *optp; - this->_multiple[key + "_X"] = val.x; - this->_multiple[key + "_Y"] = val.y; + this->set(key + "_X", val.x); + this->set(key + "_Y", val.y); } else { // set single-value placeholders - this->_single[key] = opt->serialize(); + this->set(key, opt->serialize()); } } } @@ -109,18 +110,57 @@ PlaceholderParser::set(const std::string &key, int value) } void -PlaceholderParser::set(const std::string &key, const std::vector &values) +PlaceholderParser::set(const std::string &key, std::vector values) { - for (std::vector::const_iterator v = values.begin(); v != values.end(); ++v) { + if (values.empty()) { + this->_multiple.erase(key); + this->_single.erase(key); + } else { + this->_multiple[key] = values; + this->_single[key] = values.front(); + } +} + +std::string +PlaceholderParser::process(std::string str) const +{ + // replace single options, like [foo] + for (t_strstr_map::const_iterator it = this->_single.begin(); it != this->_single.end(); ++it) { std::stringstream ss; - ss << key << "_" << (v - values.begin()); - - this->_multiple[ ss.str() ] = *v; - if (v == values.begin()) { - this->_multiple[key] = *v; + ss << '[' << it->first << ']'; + this->find_and_replace(str, ss.str(), it->second); + } + + // replace multiple options like [foo_0] by looping until we have enough values + // or until a previous match was found (this handles non-existing indices reasonably + // without a regex) + for (t_strstrs_map::const_iterator it = this->_multiple.begin(); it != this->_multiple.end(); ++it) { + const std::vector &values = it->second; + bool found = false; + for (size_t i = 0; (i < values.size()) || found; ++i) { + std::stringstream ss; + ss << '[' << it->first << '_' << i << ']'; + if (i < values.size()) { + found = this->find_and_replace(str, ss.str(), values[i]); + } else { + found = this->find_and_replace(str, ss.str(), values.front()); + } } } - this->_single.erase(key); + + return str; +} + +bool +PlaceholderParser::find_and_replace(std::string &source, std::string const &find, std::string const &replace) const +{ + bool found = false; + for (std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos; ) { + source.replace(i, find.length(), replace); + i += replace.length(); + found = true; + } + return found; } #ifdef SLIC3RXS diff --git a/xs/src/libslic3r/PlaceholderParser.hpp b/xs/src/libslic3r/PlaceholderParser.hpp index 1488e19f26..882b60a925 100644 --- a/xs/src/libslic3r/PlaceholderParser.hpp +++ b/xs/src/libslic3r/PlaceholderParser.hpp @@ -11,19 +11,26 @@ namespace Slic3r { +typedef std::map t_strstr_map; +typedef std::map > t_strstrs_map; + class PlaceholderParser { public: - std::map _single; - std::map _multiple; - + t_strstr_map _single; + t_strstrs_map _multiple; + PlaceholderParser(); void update_timestamp(); void apply_config(DynamicPrintConfig &config); void apply_env_variables(); void set(const std::string &key, const std::string &value); void set(const std::string &key, int value); - void set(const std::string &key, const std::vector &values); + void set(const std::string &key, std::vector values); + std::string process(std::string str) const; + + private: + bool find_and_replace(std::string &source, std::string const &find, std::string const &replace) const; }; } diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp index 5a0cccc106..e25aa52f3f 100644 --- a/xs/xsp/PlaceholderParser.xsp +++ b/xs/xsp/PlaceholderParser.xsp @@ -9,38 +9,14 @@ %name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser { PlaceholderParser(); ~PlaceholderParser(); + Clone clone() + %code{% RETVAL = THIS; %}; void update_timestamp(); void apply_env_variables(); void apply_config(DynamicPrintConfig *config) %code%{ THIS->apply_config(*config); %}; void set(std::string key, std::string value); - void set_multiple(std::string key, std::vector values) - %code%{ THIS->set(key, values); %}; - - void _single_set(std::string k, std::string v) - %code%{ THIS->_single[k] = v; %}; - - std::string _single_get(std::string k) - %code%{ RETVAL = THIS->_single[k]; %}; - std::string _multiple_get(std::string k) - %code%{ RETVAL = THIS->_multiple[k]; %}; - - std::vector _single_keys() - %code{% - for (std::map::iterator i = THIS->_single.begin(); - i != THIS->_single.end(); ++i) - { - RETVAL.push_back(i->first); - } - %}; - - std::vector _multiple_keys() - %code{% - for (std::map::iterator i = THIS->_multiple.begin(); - i != THIS->_multiple.end(); ++i) - { - RETVAL.push_back(i->first); - } - %}; + %name{set_multiple} void set(std::string key, std::vector values); + std::string process(std::string str) const; }; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index b1396efe05..635b858311 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -115,7 +115,7 @@ _constant() %name{Slic3r::Print} class Print { - %name{_new} Print(); + Print(); ~Print(); Ref config() From bf9cd1b8e6384c957f25ebfaedeb11bc9430b0a4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 20:14:05 +0200 Subject: [PATCH 55/83] Ported Slic3r::GCode::AvoidCrossingPerimeters to XS --- lib/Slic3r.pm | 1 + lib/Slic3r/GCode.pm | 55 ++---------------------------- lib/Slic3r/Print/GCode.pm | 18 +++++----- xs/MANIFEST | 2 ++ xs/lib/Slic3r/XS.pm | 1 + xs/src/libslic3r/GCode.cpp | 70 ++++++++++++++++++++++++++++++++++++++ xs/src/libslic3r/GCode.hpp | 41 ++++++++++++---------- xs/xsp/GCode.xsp | 31 +++++++++++++++++ xs/xsp/my.map | 4 +++ xs/xsp/typemap.xspt | 4 +++ 10 files changed, 148 insertions(+), 79 deletions(-) create mode 100644 xs/src/libslic3r/GCode.cpp create mode 100644 xs/xsp/GCode.xsp diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index b2ba77e284..57aabe31b0 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -194,6 +194,7 @@ sub thread_cleanup { *Slic3r::ExtrusionPath::DESTROY = sub {}; *Slic3r::ExtrusionPath::Collection::DESTROY = sub {}; *Slic3r::Flow::DESTROY = sub {}; + *Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {}; *Slic3r::GCode::PlaceholderParser::DESTROY = sub {}; *Slic3r::GCode::Writer::DESTROY = sub {}; *Slic3r::Geometry::BoundingBox::DESTROY = sub {}; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b486ba0b0e..f8388568b0 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -365,15 +365,15 @@ sub travel_to { if ($needs_retraction && $self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) { - $travel = $self->avoid_crossing_perimeters->travel_to($self, $point); + $travel = $self->avoid_crossing_perimeters->travel_to($point, $self->origin, $self->last_pos); # check again whether the new travel path still needs a retraction $needs_retraction = $self->needs_retraction($travel, $role); } # Re-allow avoid_crossing_perimeters for the next travel moves - $self->avoid_crossing_perimeters->disable_once(0); - $self->avoid_crossing_perimeters->use_external_mp_once(0); + $self->avoid_crossing_perimeters->set_disable_once(0); + $self->avoid_crossing_perimeters->set_use_external_mp_once(0); # generate G-code for the travel move my $gcode = ""; @@ -615,53 +615,4 @@ sub wipe { return $gcode; } -package Slic3r::GCode::AvoidCrossingPerimeters; -use Moo; - -has '_external_mp' => (is => 'rw'); -has '_layer_mp' => (is => 'rw'); -has 'use_external_mp' => (is => 'rw', default => sub {0}); -has 'use_external_mp_once' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move - -# this flag disables avoid_crossing_perimeters just for the next travel move -# we enable it by default for the first travel move in print -has 'disable_once' => (is => 'rw', default => sub {1}); - -sub init_external_mp { - my ($self, $islands) = @_; - $self->_external_mp(Slic3r::MotionPlanner->new($islands)); -} - -sub init_layer_mp { - my ($self, $islands) = @_; - $self->_layer_mp(Slic3r::MotionPlanner->new($islands)); -} - -sub travel_to { - my ($self, $gcodegen, $point) = @_; - - if ($self->use_external_mp || $self->use_external_mp_once) { - # get current origin set in $gcodegen - # (the one that will be used to translate the G-code coordinates by) - my $scaled_origin = Slic3r::Point->new_scale(@{$gcodegen->origin}); - - # represent last_pos in absolute G-code coordinates - my $last_pos = $gcodegen->last_pos->clone; - $last_pos->translate(@$scaled_origin); - - # represent $point in absolute G-code coordinates - $point = $point->clone; - $point->translate(@$scaled_origin); - # calculate path - my $travel = $self->_external_mp->shortest_path($last_pos, $point); - - # translate the path back into the shifted coordinate system that $gcodegen - # is currently using for writing coordinates - $travel->translate(@{$scaled_origin->negative}); - return $travel; - } else { - return $self->_layer_mp->shortest_path($gcodegen->last_pos, $point); - } -} - 1; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 1b7cfbc4b0..b71deaf60a 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -248,7 +248,7 @@ sub export { if ($finished_objects > 0) { $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); $gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer - $gcodegen->avoid_crossing_perimeters->use_external_mp_once(1); + $gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1); print $fh $gcodegen->retract; print $fh $gcodegen->travel_to( Slic3r::Point->new(0,0), @@ -258,7 +258,7 @@ sub export { $gcodegen->enable_cooling_markers(1); # disable motion planner when traveling to first object point - $gcodegen->avoid_crossing_perimeters->disable_once(1); + $gcodegen->avoid_crossing_perimeters->set_disable_once(1); } my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers}; @@ -398,7 +398,7 @@ sub process_layer { && !$self->_skirt_done->{$layer->print_z} && (!$layer->isa('Slic3r::Layer::Support') || $layer->id < $object->config->raft_layers)) { $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); - $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); + $self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1); my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders}; $gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]); # skip skirt if we have a large brim @@ -431,12 +431,12 @@ sub process_layer { } } $self->_skirt_done->{$layer->print_z} = 1; - $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0); + $self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0); # allow a straight travel move to the first object point if this is the first layer # (but don't in next layers) if ($layer->id == 0) { - $self->_gcodegen->avoid_crossing_perimeters->disable_once(1); + $self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1); } } @@ -444,19 +444,19 @@ sub process_layer { if (!$self->_brim_done) { $gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1); $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); - $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); + $self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1); $gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) for @{$self->print->brim}; $self->_brim_done(1); - $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0); + $self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0); # allow a straight travel move to the first object point - $self->_gcodegen->avoid_crossing_perimeters->disable_once(1); + $self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1); } for my $copy (@$object_copies) { # when starting a new object, use the external motion planner for the first travel move - $self->_gcodegen->avoid_crossing_perimeters->use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy"; + $self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy"; $self->_last_obj_copy("$copy"); $self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); diff --git a/xs/MANIFEST b/xs/MANIFEST index 63e454f726..7d7923d266 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1670,6 +1670,7 @@ src/libslic3r/ExtrusionEntityCollection.cpp src/libslic3r/ExtrusionEntityCollection.hpp src/libslic3r/Flow.cpp src/libslic3r/Flow.hpp +src/libslic3r/GCode.cpp src/libslic3r/GCode.hpp src/libslic3r/GCodeWriter.cpp src/libslic3r/GCodeWriter.hpp @@ -1762,6 +1763,7 @@ xsp/ExtrusionEntityCollection.xsp xsp/ExtrusionLoop.xsp xsp/ExtrusionPath.xsp xsp/Flow.xsp +xsp/GCode.xsp xsp/GCodeWriter.xsp xsp/Geometry.xsp xsp/GUI_3DScene.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 842f4526f3..3196eb320e 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -215,6 +215,7 @@ for my $class (qw( Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection Slic3r::Flow + Slic3r::GCode::AvoidCrossingPerimeters Slic3r::GCode::PlaceholderParser Slic3r::Geometry::BoundingBox Slic3r::Geometry::BoundingBoxf diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp new file mode 100644 index 0000000000..b21a9eca73 --- /dev/null +++ b/xs/src/libslic3r/GCode.cpp @@ -0,0 +1,70 @@ +#include "GCode.hpp" + +namespace Slic3r { + +AvoidCrossingPerimeters::AvoidCrossingPerimeters() + : use_external_mp(false), use_external_mp_once(false), disable_once(true), + _external_mp(NULL), _layer_mp(NULL) +{ +} + +AvoidCrossingPerimeters::~AvoidCrossingPerimeters() +{ + if (this->_external_mp != NULL) + delete this->_external_mp; + + if (this->_layer_mp != NULL) + delete this->_layer_mp; +} + +void +AvoidCrossingPerimeters::init_external_mp(const ExPolygons &islands) +{ + if (this->_external_mp != NULL) + delete this->_external_mp; + + this->_external_mp = new MotionPlanner(islands); +} + +void +AvoidCrossingPerimeters::init_layer_mp(const ExPolygons &islands) +{ + if (this->_layer_mp != NULL) + delete this->_layer_mp; + + this->_layer_mp = new MotionPlanner(islands); +} + +Polyline +AvoidCrossingPerimeters::travel_to(Point point, const Pointf &gcodegen_origin, + const Point &gcodegen_last_pos) +{ + if (this->use_external_mp || this->use_external_mp_once) { + // get current origin set in gcodegen + // (the one that will be used to translate the G-code coordinates by) + Point scaled_origin = Point::new_scale(gcodegen_origin.x, gcodegen_origin.y); + + // represent last_pos in absolute G-code coordinates + Point last_pos = gcodegen_last_pos; + last_pos.translate(scaled_origin); + + // represent point in absolute G-code coordinates + point.translate(scaled_origin); + + // calculate path + Polyline travel = this->_external_mp->shortest_path(last_pos, point); + + // translate the path back into the shifted coordinate system that gcodegen + // is currently using for writing coordinates + travel.translate(scaled_origin.negative()); + return travel; + } else { + return this->_layer_mp->shortest_path(gcodegen_last_pos, point); + } +} + +#ifdef SLIC3RXS +REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters"); +#endif + +} diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index a889532ea6..a9af44781a 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -2,32 +2,37 @@ #define slic3r_GCode_hpp_ #include +#include "ExPolygon.hpp" +#include "MotionPlanner.hpp" #include namespace Slic3r { // draft for a binary representation of a G-code line -enum GCodeCmdType { - gcctSyncMotion, - gcctExtrude, - gcctResetE, - gcctSetTemp, - gcctSetTempWait, - gcctToolchange, - gcctCustom -}; - -class GCodeCmd { +class AvoidCrossingPerimeters { public: - GCodeCmdType type; - float X, Y, Z, E, F; - unsigned short T, S; - std::string custom, comment; - float xy_dist; // cache - GCodeCmd(GCodeCmdType type) - : type(type), X(0), Y(0), Z(0), E(0), F(0), T(-1), S(0), xy_dist(-1) {}; + // this flag triggers the use of the external configuration space + bool use_external_mp; + bool use_external_mp_once; // just for the next travel move + + // this flag disables avoid_crossing_perimeters just for the next travel move + // we enable it by default for the first travel move in print + bool disable_once; + + AvoidCrossingPerimeters(); + ~AvoidCrossingPerimeters(); + void init_external_mp(const ExPolygons &islands); + void init_layer_mp(const ExPolygons &islands); + + //Polyline travel_to(GCode &gcodegen, const Point &point); + Polyline travel_to(Point point, const Pointf &gcodegen_origin, + const Point &gcodegen_last_pos); + + private: + MotionPlanner* _external_mp; + MotionPlanner* _layer_mp; }; } diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp new file mode 100644 index 0000000000..007b2b1645 --- /dev/null +++ b/xs/xsp/GCode.xsp @@ -0,0 +1,31 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/GCode.hpp" +%} + +%name{Slic3r::GCode::AvoidCrossingPerimeters} class AvoidCrossingPerimeters { + AvoidCrossingPerimeters(); + ~AvoidCrossingPerimeters(); + + void init_external_mp(ExPolygons islands); + void init_layer_mp(ExPolygons islands); + Clone travel_to(Point* point, Pointf* gcodegen_origin, Point* gcodegen_last_pos) + %code{% RETVAL = THIS->travel_to(*point, *gcodegen_origin, *gcodegen_last_pos); %}; + + bool use_external_mp() + %code{% RETVAL = THIS->use_external_mp; %}; + void set_use_external_mp(bool value) + %code{% THIS->use_external_mp = value; %}; + + bool use_external_mp_once() + %code{% RETVAL = THIS->use_external_mp_once; %}; + void set_use_external_mp_once(bool value) + %code{% THIS->use_external_mp_once = value; %}; + + bool disable_once() + %code{% RETVAL = THIS->disable_once; %}; + void set_disable_once(bool value) + %code{% THIS->disable_once = value; %}; +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index da9d3adc71..fb12889907 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -170,6 +170,10 @@ PlaceholderParser* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +AvoidCrossingPerimeters* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + MotionPlanner* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index afc256d132..53e0213371 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -125,6 +125,10 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{AvoidCrossingPerimeters*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; + %typemap{Points}; %typemap{Pointfs}; From 76b2e8855177c3e7dedd25a6bf2977e4adbb177c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 20:15:52 +0200 Subject: [PATCH 56/83] Removed setenv() test as we can't test environment variables in Perl since they are now set in XS --- t/config.t | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/t/config.t b/t/config.t index 7950dbb184..7f85dae84c 100644 --- a/t/config.t +++ b/t/config.t @@ -1,4 +1,4 @@ -use Test::More tests => 2; +use Test::More tests => 1; use strict; use warnings; @@ -10,13 +10,6 @@ BEGIN { use Slic3r; use Slic3r::Test; -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('layer_height', 0.123); - $config->setenv; - is $ENV{SLIC3R_LAYER_HEIGHT}, '0.123', 'setenv'; -} - { my $config = Slic3r::Config->new_from_defaults; $config->set('perimeter_extrusion_width', '250%'); From b43dd92766e40ad993bccd96e1832466e3d8c293 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 20:57:16 +0200 Subject: [PATCH 57/83] Ported Slic3r::GCode::Wipe storage to XS --- lib/Slic3r.pm | 1 + lib/Slic3r/GCode.pm | 20 +++++++++----------- xs/lib/Slic3r/XS.pm | 1 + xs/src/libslic3r/GCode.cpp | 21 +++++++++++++++++++++ xs/src/libslic3r/GCode.hpp | 11 +++++++++++ xs/xsp/GCode.xsp | 18 ++++++++++++++++++ xs/xsp/my.map | 4 ++++ xs/xsp/typemap.xspt | 4 ++++ 8 files changed, 69 insertions(+), 11 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 57aabe31b0..26acb564cc 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -196,6 +196,7 @@ sub thread_cleanup { *Slic3r::Flow::DESTROY = sub {}; *Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {}; *Slic3r::GCode::PlaceholderParser::DESTROY = sub {}; + *Slic3r::GCode::Wipe::DESTROY = sub {}; *Slic3r::GCode::Writer::DESTROY = sub {}; *Slic3r::Geometry::BoundingBox::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf::DESTROY = sub {}; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index f8388568b0..38374975dc 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -42,7 +42,7 @@ sub set_extruders { $self->writer->set_extruders($extruder_ids); # enable wipe path generation if any extruder has wipe enabled - $self->wipe->enable(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids); + $self->wipe->set_enable(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids); } sub set_origin { @@ -54,7 +54,7 @@ sub set_origin { scale ($self->origin->y - $pointf->y), #- ); $self->last_pos->translate(@translate); - $self->wipe->path->translate(@translate) if $self->wipe->path; + $self->wipe->path->translate(@translate) if $self->wipe->has_path; $self->origin($pointf); } @@ -99,7 +99,7 @@ sub change_layer { $gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer_index . ')'); # forget last wiping path as wiping after raising Z is pointless - $self->wipe->path(undef); + $self->wipe->reset_path; return $gcode; } @@ -208,7 +208,7 @@ sub extrude_loop { # reset acceleration $gcode .= $self->writer->set_acceleration($self->config->default_acceleration); - $self->wipe->path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path + $self->wipe->set_path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path # make a little move inwards before leaving loop if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) { @@ -333,7 +333,7 @@ sub _extrude_path { $self->config->gcode_comments ? " ; $description" : ""); if ($self->wipe->enable) { - $self->wipe->path($path->polyline->clone); + $self->wipe->set_path($path->polyline->clone); $self->wipe->path->reverse; } } @@ -429,7 +429,7 @@ sub retract { my $gcode = ""; # wipe (if it's enabled for this extruder and we have a stored wipe path) - if ($self->config->get_at('wipe', $self->writer->extruder->id) && $self->wipe->path) { + if ($self->config->get_at('wipe', $self->writer->extruder->id) && $self->wipe->has_path) { $gcode .= $self->wipe->wipe($self, $toolchange); } @@ -555,13 +555,11 @@ sub post_toolchange { } package Slic3r::GCode::Wipe; -use Moo; +use strict; +use warnings; use Slic3r::Geometry qw(scale); -has 'enable' => (is => 'rw', default => sub { 0 }); -has 'path' => (is => 'rw'); - sub wipe { my ($self, $gcodegen, $toolchange) = @_; @@ -609,7 +607,7 @@ sub wipe { $gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted); # prevent wiping again on same path - $self->path(undef); + $self->reset_path; } return $gcode; diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 3196eb320e..2abad09b6e 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -217,6 +217,7 @@ for my $class (qw( Slic3r::Flow Slic3r::GCode::AvoidCrossingPerimeters Slic3r::GCode::PlaceholderParser + Slic3r::GCode::Wipe Slic3r::Geometry::BoundingBox Slic3r::Geometry::BoundingBoxf Slic3r::Geometry::BoundingBoxf3 diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index b21a9eca73..bdc8220906 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -67,4 +67,25 @@ AvoidCrossingPerimeters::travel_to(Point point, const Pointf &gcodegen_origin, REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters"); #endif +Wipe::Wipe() + : enable(false) +{ +} + +bool +Wipe::has_path() +{ + return !this->path.points.empty(); +} + +void +Wipe::reset_path() +{ + this->path = Polyline(); +} + +#ifdef SLIC3RXS +REGISTER_CLASS(Wipe, "GCode::Wipe"); +#endif + } diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index a9af44781a..a44fce616e 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -35,6 +35,17 @@ class AvoidCrossingPerimeters { MotionPlanner* _layer_mp; }; +class Wipe { + public: + bool enable; + Polyline path; + + Wipe(); + bool has_path(); + void reset_path(); + //std::string wipe(GCode &gcodegen, bool toolchange = false); +}; + } #endif diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 007b2b1645..ec939a8b64 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -29,3 +29,21 @@ void set_disable_once(bool value) %code{% THIS->disable_once = value; %}; }; + +%name{Slic3r::GCode::Wipe} class Wipe { + Wipe(); + ~Wipe(); + + bool has_path(); + void reset_path(); + + bool enable() + %code{% RETVAL = THIS->enable; %}; + void set_enable(bool value) + %code{% THIS->enable = value; %}; + + Ref path() + %code{% RETVAL = &(THIS->path); %}; + void set_path(Polyline* value) + %code{% THIS->path = *value; %}; +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index fb12889907..d594ceacd6 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -174,6 +174,10 @@ AvoidCrossingPerimeters* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +Wipe* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + MotionPlanner* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 53e0213371..675da7beb5 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -129,6 +129,10 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{Wipe*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; + %typemap{Points}; %typemap{Pointfs}; From 280f3f38d7d4168dffe173bf9aaf4a77e87f2ff2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 21:01:42 +0200 Subject: [PATCH 58/83] Ported Slic3r::GCode::OozePrevention storage to XS --- lib/Slic3r.pm | 1 + lib/Slic3r/GCode.pm | 6 ++---- lib/Slic3r/Print/GCode.pm | 4 ++-- xs/lib/Slic3r/XS.pm | 1 + xs/src/libslic3r/GCode.cpp | 9 +++++++++ xs/src/libslic3r/GCode.hpp | 8 ++++++++ xs/xsp/GCode.xsp | 15 +++++++++++++++ xs/xsp/my.map | 4 ++++ 8 files changed, 42 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 26acb564cc..f0e85e629d 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -195,6 +195,7 @@ sub thread_cleanup { *Slic3r::ExtrusionPath::Collection::DESTROY = sub {}; *Slic3r::Flow::DESTROY = sub {}; *Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {}; + *Slic3r::GCode::OozePrevention::DESTROY = sub {}; *Slic3r::GCode::PlaceholderParser::DESTROY = sub {}; *Slic3r::GCode::Wipe::DESTROY = sub {}; *Slic3r::GCode::Writer::DESTROY = sub {}; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 38374975dc..a1524a12db 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -504,13 +504,11 @@ sub set_extruder { } package Slic3r::GCode::OozePrevention; -use Moo; +use strict; +use warnings; use Slic3r::Geometry qw(scale); -has 'enable' => (is => 'rw', default => sub { 0 }); -has 'standby_points' => (is => 'rw'); - sub pre_toolchange { my ($self, $gcodegen) = @_; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index b71deaf60a..79200df4f5 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -212,8 +212,8 @@ sub export { } my $convex_hull = convex_hull([ map @$_, @skirts ]); - $gcodegen->ooze_prevention->enable(1); - $gcodegen->ooze_prevention->standby_points( + $gcodegen->ooze_prevention->set_enable(1); + $gcodegen->ooze_prevention->set_standby_points( [ map @{$_->equally_spaced_points(scale 10)}, @{offset([$convex_hull], scale 3)} ] ); diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 2abad09b6e..8c6956bbc8 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -216,6 +216,7 @@ for my $class (qw( Slic3r::ExtrusionPath::Collection Slic3r::Flow Slic3r::GCode::AvoidCrossingPerimeters + Slic3r::GCode::OozePrevention Slic3r::GCode::PlaceholderParser Slic3r::GCode::Wipe Slic3r::Geometry::BoundingBox diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index bdc8220906..313af8a2e6 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -67,6 +67,15 @@ AvoidCrossingPerimeters::travel_to(Point point, const Pointf &gcodegen_origin, REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters"); #endif +OozePrevention::OozePrevention() + : enable(false) +{ +} + +#ifdef SLIC3RXS +REGISTER_CLASS(OozePrevention, "GCode::OozePrevention"); +#endif + Wipe::Wipe() : enable(false) { diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index a44fce616e..bfefa40447 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -35,6 +35,14 @@ class AvoidCrossingPerimeters { MotionPlanner* _layer_mp; }; +class OozePrevention { + public: + bool enable; + Points standby_points; + + OozePrevention(); +}; + class Wipe { public: bool enable; diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index ec939a8b64..c981259a00 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -30,6 +30,21 @@ %code{% THIS->disable_once = value; %}; }; +%name{Slic3r::GCode::OozePrevention} class OozePrevention { + OozePrevention(); + ~OozePrevention(); + + bool enable() + %code{% RETVAL = THIS->enable; %}; + void set_enable(bool value) + %code{% THIS->enable = value; %}; + + Points standby_points() + %code{% RETVAL = THIS->standby_points; %}; + void set_standby_points(Points points) + %code{% THIS->standby_points = points; %}; +}; + %name{Slic3r::GCode::Wipe} class Wipe { Wipe(); ~Wipe(); diff --git a/xs/xsp/my.map b/xs/xsp/my.map index d594ceacd6..5f36db0784 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -178,6 +178,10 @@ Wipe* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +OozePrevention* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + MotionPlanner* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T From ab858f320dd0d6a3dc535d4fa20ee4f31b350837 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 21:02:36 +0200 Subject: [PATCH 59/83] Updated test --- xs/t/20_print.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/t/20_print.t b/xs/t/20_print.t index d8e8699bf5..bef4d050c9 100644 --- a/xs/t/20_print.t +++ b/xs/t/20_print.t @@ -7,7 +7,7 @@ use Slic3r::XS; use Test::More tests => 5; { - my $print = Slic3r::Print->_new; + my $print = Slic3r::Print->new; isa_ok $print, 'Slic3r::Print'; isa_ok $print->config, 'Slic3r::Config::Print::Ref'; isa_ok $print->default_object_config, 'Slic3r::Config::PrintObject::Ref'; From 801f629fdc973f7f60dc5e4c2135940f89c3b959 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 21:47:17 +0200 Subject: [PATCH 60/83] Ported Slic3r::GCode storage to XS --- lib/Slic3r.pm | 1 + lib/Slic3r/GCode.pm | 67 +++++++++--------------- lib/Slic3r/GCode/CoolingBuffer.pm | 2 +- lib/Slic3r/Print/GCode.pm | 17 +++--- t/cooling.t | 17 +++--- t/gcode.t | 6 +-- xs/lib/Slic3r/XS.pm | 2 + xs/src/libslic3r/GCode.cpp | 11 ++++ xs/src/libslic3r/GCode.hpp | 34 ++++++++++++ xs/xsp/GCode.xsp | 86 +++++++++++++++++++++++++++++++ xs/xsp/Layer.xsp | 8 ++- xs/xsp/my.map | 4 ++ xs/xsp/typemap.xspt | 8 +++ 13 files changed, 199 insertions(+), 64 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index f0e85e629d..54d1e524f5 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -194,6 +194,7 @@ sub thread_cleanup { *Slic3r::ExtrusionPath::DESTROY = sub {}; *Slic3r::ExtrusionPath::Collection::DESTROY = sub {}; *Slic3r::Flow::DESTROY = sub {}; + *Slic3r::GCode::DESTROY = sub {}; *Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {}; *Slic3r::GCode::OozePrevention::DESTROY = sub {}; *Slic3r::GCode::PlaceholderParser::DESTROY = sub {}; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index a1524a12db..7885cc9b66 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -1,5 +1,6 @@ package Slic3r::GCode; -use Moo; +use strict; +use warnings; use List::Util qw(min max first); use Slic3r::ExtrusionLoop ':roles'; @@ -7,28 +8,6 @@ use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(epsilon scale unscale PI X Y B); use Slic3r::Geometry::Clipper qw(union_ex); -# Origin of print coordinates expressed in unscaled G-code coordinates. -# This affects the input arguments supplied to the extrude*() and travel_to() -# methods. -has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf->new }); - -has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new }); -has 'writer' => (is => 'ro', default => sub { Slic3r::GCode::Writer->new }); -has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new }); -has 'ooze_prevention' => (is => 'rw', default => sub { Slic3r::GCode::OozePrevention->new }); -has 'wipe' => (is => 'rw', default => sub { Slic3r::GCode::Wipe->new }); -has 'avoid_crossing_perimeters' => (is => 'rw', default => sub { Slic3r::GCode::AvoidCrossingPerimeters->new }); -has 'enable_loop_clipping' => (is => 'rw', default => sub {1}); -has 'enable_cooling_markers' => (is =>'rw', default => sub {0}); -has 'layer_count' => (is => 'ro'); -has 'layer_index' => (is => 'rw', default => sub {-1}); # just a counter -has 'layer' => (is => 'rw'); -has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos -has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds -has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds -has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } ); -has 'volumetric_speed' => (is => 'rw', default => sub {0}); - sub apply_print_config { my ($self, $print_config) = @_; @@ -56,7 +35,7 @@ sub set_origin { $self->last_pos->translate(@translate); $self->wipe->path->translate(@translate) if $self->wipe->has_path; - $self->origin($pointf); + $self->_set_origin($pointf); } sub preamble { @@ -76,9 +55,14 @@ sub preamble { sub change_layer { my ($self, $layer) = @_; - $self->layer($layer); - $self->layer_index($self->layer_index + 1); - $self->first_layer($layer->id == 0); + { + my $l = $layer->isa('Slic3r::Layer::Support') + ? $layer->as_layer + : $layer; + $self->set_layer($l); + } + $self->set_layer_index($self->layer_index + 1); + $self->set_first_layer($layer->id == 0); # avoid computing islands and overhangs if they're not needed if ($self->config->avoid_crossing_perimeters) { @@ -88,7 +72,7 @@ sub change_layer { } my $gcode = ""; - if (defined $self->layer_count) { + if ($self->layer_count > 0) { $gcode .= $self->writer->update_progress($self->layer_index, $self->layer_count); } @@ -146,11 +130,9 @@ sub extrude_loop { @candidates = map @{$_->convex_points(PI*2/3)}, @simplified if !@candidates; # retrieve the last start position for this object - my $obj_ptr = 0; - if (defined $self->layer) { - $obj_ptr = $self->layer->object->ptr; - if (defined $self->_seam_position->{$obj_ptr}) { - $last_pos = $self->_seam_position->{$obj_ptr}; + if ($self->has_layer) { + if ($self->_has_seam_position($self->layer->object)) { + $last_pos = $self->_seam_position($self->layer->object); } } @@ -175,7 +157,8 @@ sub extrude_loop { $point = $last_pos->projection_onto_polygon($polygon); $loop->split_at($point); } - $self->_seam_position->{$obj_ptr} = $point; + $self->_set_seam_position($self->layer->object, $point) + if $self->has_layer; } elsif ($self->config->seam_position eq 'random') { if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { my $polygon = $loop->polygon; @@ -211,7 +194,7 @@ sub extrude_loop { $self->wipe->set_path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path # make a little move inwards before leaving loop - if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) { + if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && $self->has_layer && $self->config->perimeters > 1) { my $last_path_polyline = $paths[-1]->polyline; # detect angle between last and first segment # the side depends on the original winding order of the polygon (left for contours, right for holes) @@ -258,7 +241,7 @@ sub _extrude_path { { my $first_point = $path->first_point; $gcode .= $self->travel_to($first_point, $path->role, "move to first $description point") - if !defined $self->last_pos || !$self->last_pos->coincides_with($first_point); + if !$self->last_pos_defined || !$self->last_pos->coincides_with($first_point); } # compensate retraction @@ -338,11 +321,11 @@ sub _extrude_path { } } $gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge && $self->enable_cooling_markers; - $self->last_pos($path->last_point); + $self->set_last_pos($path->last_point); if ($self->config->cooling) { my $path_time = $path_length / $F * 60; - $self->elapsed_time($self->elapsed_time + $path_time); + $self->set_elapsed_time($self->elapsed_time + $path_time); } return $gcode; @@ -394,12 +377,12 @@ sub needs_retraction { return 0; } - if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_polyline($travel)) { + if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->as_support_layer->support_islands->contains_polyline($travel)) { # skip retraction if this is a travel move inside a support material island return 0; } - if ($self->config->only_retract_when_crossing_perimeters && defined $self->layer) { + if ($self->config->only_retract_when_crossing_perimeters && $self->has_layer) { if ($self->config->fill_density > 0 && $self->layer->any_internal_region_slice_contains_polyline($travel)) { # skip retraction if travel is contained in an internal slice *and* @@ -527,7 +510,7 @@ sub pre_toolchange { } if ($gcodegen->config->standby_temperature_delta != 0) { - my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0 + my $temp = $gcodegen->has_layer && $gcodegen->layer->id == 0 ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); # we assume that heating is always slower than cooling, so no need to block @@ -543,7 +526,7 @@ sub post_toolchange { my $gcode = ""; if ($gcodegen->config->standby_temperature_delta != 0) { - my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0 + my $temp = $gcodegen->has_layer && $gcodegen->layer->id == 0 ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); $gcode .= $gcodegen->writer->set_temperature($temp, 1); diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm index 69dfc458f4..62ffb2c766 100644 --- a/lib/Slic3r/GCode/CoolingBuffer.pm +++ b/lib/Slic3r/GCode/CoolingBuffer.pm @@ -27,7 +27,7 @@ sub append { $self->last_z->{$obj_id} = $print_z; $self->gcode($self->gcode . $gcode); $self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time); - $self->gcodegen->elapsed_time(0); + $self->gcodegen->set_elapsed_time(0); return $return; } diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 79200df4f5..4323e0965b 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -35,12 +35,11 @@ sub BUILD { } # set up our helper object - my $gcodegen = Slic3r::GCode->new( - placeholder_parser => $self->placeholder_parser, - layer_count => $layer_count, - enable_cooling_markers => 1, - ); + my $gcodegen = Slic3r::GCode->new; $self->_gcodegen($gcodegen); + $gcodegen->set_placeholder_parser($self->placeholder_parser); + $gcodegen->set_layer_count($layer_count); + $gcodegen->set_enable_cooling_markers(1); $gcodegen->apply_print_config($self->config); $gcodegen->set_extruders($self->print->extruders); @@ -92,7 +91,7 @@ sub BUILD { $self->config->max_volumetric_speed, ); } - $gcodegen->volumetric_speed($volumetric_speed); + $gcodegen->set_volumetric_speed($volumetric_speed); } } } @@ -247,7 +246,7 @@ sub export { # no collision happens hopefully. if ($finished_objects > 0) { $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); - $gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer + $gcodegen->set_enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer $gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1); print $fh $gcodegen->retract; print $fh $gcodegen->travel_to( @@ -255,7 +254,7 @@ sub export { undef, 'move to origin position for next object', ); - $gcodegen->enable_cooling_markers(1); + $gcodegen->set_enable_cooling_markers(1); # disable motion planner when traveling to first object point $gcodegen->avoid_crossing_perimeters->set_disable_once(1); @@ -364,7 +363,7 @@ sub process_layer { } # if we're going to apply spiralvase to this layer, disable loop clipping - $self->_gcodegen->enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable); + $self->_gcodegen->set_enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable); if (!$self->_second_layer_things_done && $layer->id == 1) { for my $extruder (@{$self->_gcodegen->writer->extruders}) { diff --git a/t/cooling.t b/t/cooling.t index 6fc5cf71b0..42d0414c11 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -18,9 +18,12 @@ sub buffer { my $print_config = Slic3r::Config::Print->new; $print_config->apply_dynamic($config); + my $gcodegen = Slic3r::GCode->new; + $gcodegen->apply_print_config($print_config); + $gcodegen->set_layer_count(10); my $buffer = Slic3r::GCode::CoolingBuffer->new( config => $print_config, - gcodegen => Slic3r::GCode->new(print_config => $print_config, layer_count => 10, extruders => []), + gcodegen => $gcodegen, ); return $buffer; } @@ -30,14 +33,14 @@ $config->set('disable_fan_first_layers', 0); { my $buffer = buffer($config); - $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time + 1); + $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time + 1); my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; } { my $buffer = buffer($config); - $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time - 1); my $gcode = $buffer->append("G1 X50 F2500\nG1 X100 E1 F3000\nG1 E4 F400", 0, 0, 0.4) . $buffer->flush; unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold'; like $gcode, qr/F2500/, 'speed is not altered for travel moves'; @@ -46,7 +49,7 @@ $config->set('disable_fan_first_layers', 0); { my $buffer = buffer($config); - $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time + 1); + $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time + 1); my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; } @@ -56,7 +59,7 @@ $config->set('disable_fan_first_layers', 0); my $gcode = ""; for my $obj_id (0 .. 1) { # use an elapsed time which is < the slowdown threshold but greater than it when summed twice - $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4); } $gcode .= $buffer->flush; @@ -69,7 +72,7 @@ $config->set('disable_fan_first_layers', 0); for my $layer_id (0 .. 1) { for my $obj_id (0 .. 1) { # use an elapsed time which is < the threshold but greater than it when summed twice - $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights } } @@ -83,7 +86,7 @@ $config->set('disable_fan_first_layers', 0); for my $layer_id (0 .. 1) { for my $obj_id (0 .. 1) { # use an elapsed time which is < the threshold even when summed twice - $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time/2 - 1); + $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time/2 - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights } } diff --git a/t/gcode.t b/t/gcode.t index fb85ee5e4a..ef2209a802 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -13,10 +13,8 @@ use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Test; { - my $gcodegen = Slic3r::GCode->new( - layer_count => 1, - extruders => [], - ); + my $gcodegen = Slic3r::GCode->new(); + $gcodegen->set_layer_count(1); $gcodegen->set_origin(Slic3r::Pointf->new(10, 10)); is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly'; } diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 8c6956bbc8..c27154a208 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -215,10 +215,12 @@ for my $class (qw( Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection Slic3r::Flow + Slic3r::GCode Slic3r::GCode::AvoidCrossingPerimeters Slic3r::GCode::OozePrevention Slic3r::GCode::PlaceholderParser Slic3r::GCode::Wipe + Slic3r::GCode::Writer Slic3r::Geometry::BoundingBox Slic3r::Geometry::BoundingBoxf Slic3r::Geometry::BoundingBoxf3 diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 313af8a2e6..506e119824 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -97,4 +97,15 @@ Wipe::reset_path() REGISTER_CLASS(Wipe, "GCode::Wipe"); #endif +GCode::GCode() + : enable_loop_clipping(true), enable_cooling_markers(false), layer_count(0), + layer_index(-1), first_layer(false), elapsed_time(0), volumetric_speed(0), + last_pos_defined(false) +{ +} + +#ifdef SLIC3RXS +REGISTER_CLASS(GCode, "GCode"); +#endif + } diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index bfefa40447..8eed9600f5 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -3,7 +3,13 @@ #include #include "ExPolygon.hpp" +#include "GCodeWriter.hpp" +#include "Layer.hpp" #include "MotionPlanner.hpp" +#include "Point.hpp" +#include "PlaceholderParser.hpp" +#include "Print.hpp" +#include "PrintConfig.hpp" #include namespace Slic3r { @@ -54,6 +60,34 @@ class Wipe { //std::string wipe(GCode &gcodegen, bool toolchange = false); }; +class GCode { + public: + + /* Origin of print coordinates expressed in unscaled G-code coordinates. + This affects the input arguments supplied to the extrude*() and travel_to() + methods. */ + Pointf origin; + FullPrintConfig config; + GCodeWriter writer; + PlaceholderParser* placeholder_parser; + OozePrevention ooze_prevention; + Wipe wipe; + AvoidCrossingPerimeters avoid_crossing_perimeters; + bool enable_loop_clipping; + bool enable_cooling_markers; + size_t layer_count; + int layer_index; // just a counter + Layer* layer; + std::map _seam_position; + bool first_layer; // this flag triggers first layer speeds + unsigned int elapsed_time; // seconds + Point last_pos; + bool last_pos_defined; + double volumetric_speed; + + GCode(); +}; + } #endif diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index c981259a00..8000b973aa 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -62,3 +62,89 @@ void set_path(Polyline* value) %code{% THIS->path = *value; %}; }; + +%name{Slic3r::GCode} class GCode { + GCode(); + ~GCode(); + + Ref origin() + %code{% RETVAL = &(THIS->origin); %}; + void _set_origin(Pointf* value) + %code{% THIS->origin = *value; %}; + + Ref config() + %code{% RETVAL = &(THIS->config); %}; + + Ref writer() + %code{% RETVAL = &(THIS->writer); %}; + + Ref placeholder_parser() + %code{% RETVAL = THIS->placeholder_parser; %}; + void set_placeholder_parser(PlaceholderParser* ptr) + %code{% THIS->placeholder_parser = ptr; %}; + + Ref ooze_prevention() + %code{% RETVAL = &(THIS->ooze_prevention); %}; + + Ref wipe() + %code{% RETVAL = &(THIS->wipe); %}; + + Ref avoid_crossing_perimeters() + %code{% RETVAL = &(THIS->avoid_crossing_perimeters); %}; + + bool enable_loop_clipping() + %code{% RETVAL = THIS->enable_loop_clipping; %}; + void set_enable_loop_clipping(bool value) + %code{% THIS->enable_loop_clipping = value; %}; + + bool enable_cooling_markers() + %code{% RETVAL = THIS->enable_cooling_markers; %}; + void set_enable_cooling_markers(bool value) + %code{% THIS->enable_cooling_markers = value; %}; + + int layer_count() + %code{% RETVAL = THIS->layer_count; %}; + void set_layer_count(int value) + %code{% THIS->layer_count = value; %}; + + int layer_index() + %code{% RETVAL = THIS->layer_index; %}; + void set_layer_index(int value) + %code{% THIS->layer_index = value; %}; + + bool has_layer() + %code{% RETVAL = THIS->layer != NULL; %}; + Ref layer() + %code{% RETVAL = THIS->layer; %}; + void set_layer(Layer* ptr) + %code{% THIS->layer = ptr; %}; + + bool _has_seam_position(PrintObject* ptr) + %code{% RETVAL = THIS->_seam_position.count(ptr) > 0; %}; + Clone _seam_position(PrintObject* ptr) + %code{% RETVAL = THIS->_seam_position[ptr]; %}; + void _set_seam_position(PrintObject* ptr, Point* pos) + %code{% THIS->_seam_position[ptr] = *pos; %}; + + bool first_layer() + %code{% RETVAL = THIS->first_layer; %}; + void set_first_layer(bool value) + %code{% THIS->first_layer = value; %}; + + unsigned int elapsed_time() + %code{% RETVAL = THIS->elapsed_time; %}; + void set_elapsed_time(unsigned int value) + %code{% THIS->elapsed_time = value; %}; + + bool last_pos_defined() + %code{% RETVAL = THIS->last_pos_defined; %}; + Ref last_pos() + %code{% RETVAL = &(THIS->last_pos); %}; + void set_last_pos(Point* value) + %code{% THIS->last_pos = *value; THIS->last_pos_defined = true; %}; + + double volumetric_speed() + %code{% RETVAL = THIS->volumetric_speed; %}; + void set_volumetric_speed(double value) + %code{% THIS->volumetric_speed = value; %}; +}; diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index e8372206cc..c7795b453e 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -70,6 +70,9 @@ int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; + Ref as_support_layer() + %code%{ RETVAL = dynamic_cast(THIS); %}; + void make_slices(); void merge_slices(); bool any_internal_region_slice_contains_polyline(Polyline* polyline) @@ -80,7 +83,10 @@ %name{Slic3r::Layer::Support} class SupportLayer { // owned by PrintObject, no constructor/destructor - + + Ref as_layer() + %code%{ RETVAL = THIS; %}; + Ref support_islands() %code%{ RETVAL = &THIS->support_islands; %}; Ref support_fills() diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 5f36db0784..af6f9f0427 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -182,6 +182,10 @@ OozePrevention* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +GCode* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + MotionPlanner* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 675da7beb5..8b4eb08507 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -133,6 +133,14 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{OozePrevention*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; + +%typemap{GCode*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; + %typemap{Points}; %typemap{Pointfs}; From b4019bb438705978aac692b3535fa695d546fb85 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 23:00:52 +0200 Subject: [PATCH 61/83] Ported more Slic3r::GCode methods to XS --- lib/Slic3r/GCode.pm | 141 +-------------------------- t/shells.t | 4 +- xs/MANIFEST | 1 + xs/src/libslic3r/GCode.cpp | 167 ++++++++++++++++++++++++++++++-- xs/src/libslic3r/GCode.hpp | 24 +++-- xs/src/libslic3r/MultiPoint.cpp | 18 ++++ xs/src/libslic3r/MultiPoint.hpp | 3 + xs/t/21_gcode.t | 17 ++++ xs/xsp/GCode.xsp | 27 ++++-- 9 files changed, 237 insertions(+), 165 deletions(-) create mode 100644 xs/t/21_gcode.t diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 7885cc9b66..8881cf0cde 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -8,13 +8,6 @@ use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(epsilon scale unscale PI X Y B); use Slic3r::Geometry::Clipper qw(union_ex); -sub apply_print_config { - my ($self, $print_config) = @_; - - $self->writer->apply_print_config($print_config); - $self->config->apply_print_config($print_config); -} - sub set_extruders { my ($self, $extruder_ids) = @_; @@ -24,34 +17,6 @@ sub set_extruders { $self->wipe->set_enable(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids); } -sub set_origin { - my ($self, $pointf) = @_; - - # if origin increases (goes towards right), last_pos decreases because it goes towards left - my @translate = ( - scale ($self->origin->x - $pointf->x), - scale ($self->origin->y - $pointf->y), #- - ); - $self->last_pos->translate(@translate); - $self->wipe->path->translate(@translate) if $self->wipe->has_path; - - $self->_set_origin($pointf); -} - -sub preamble { - my ($self) = @_; - - my $gcode = $self->writer->preamble; - - # Perform a *silent* move to z_offset: we need this to initialize the Z - # position of our writer object so that any initial lift taking place - # before the first layer change will raise the extruder from the correct - # initial Z instead of 0. - $self->writer->travel_to_z($self->config->z_offset, ''); - - return $gcode; -} - sub change_layer { my ($self, $layer) = @_; @@ -348,7 +313,7 @@ sub travel_to { if ($needs_retraction && $self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) { - $travel = $self->avoid_crossing_perimeters->travel_to($point, $self->origin, $self->last_pos); + $travel = $self->avoid_crossing_perimeters->travel_to($self, $point); # check again whether the new travel path still needs a retraction $needs_retraction = $self->needs_retraction($travel, $role); @@ -404,51 +369,6 @@ sub needs_retraction { return 1; } -sub retract { - my ($self, $toolchange) = @_; - - return "" if !defined $self->writer->extruder; - - my $gcode = ""; - - # wipe (if it's enabled for this extruder and we have a stored wipe path) - if ($self->config->get_at('wipe', $self->writer->extruder->id) && $self->wipe->has_path) { - $gcode .= $self->wipe->wipe($self, $toolchange); - } - - # The parent class will decide whether we need to perform an actual retraction - # (the extruder might be already retracted fully or partially). We call these - # methods even if we performed wipe, since this will ensure the entire retraction - # length is honored in case wipe path was too short.p - $gcode .= $toolchange ? $self->writer->retract_for_toolchange : $self->writer->retract; - - $gcode .= $self->writer->reset_e; - $gcode .= $self->writer->lift - if $self->writer->extruder->retract_length > 0 || $self->config->use_firmware_retraction; - - return $gcode; -} - -sub unretract { - my ($self) = @_; - - my $gcode = ""; - $gcode .= $self->writer->unlift; - $gcode .= $self->writer->unretract; - return $gcode; -} - -# convert a model-space scaled point into G-code coordinates -sub point_to_gcode { - my ($self, $point) = @_; - - my $extruder_offset = $self->config->get_at('extruder_offset', $self->writer->extruder->id); - return Slic3r::Pointf->new( - ($point->x * &Slic3r::SCALING_FACTOR) + $self->origin->x - $extruder_offset->x, - ($point->y * &Slic3r::SCALING_FACTOR) + $self->origin->y - $extruder_offset->y, #** - ); -} - sub set_extruder { my ($self, $extruder_id) = @_; @@ -535,63 +455,4 @@ sub post_toolchange { return $gcode; } -package Slic3r::GCode::Wipe; -use strict; -use warnings; - -use Slic3r::Geometry qw(scale); - -sub wipe { - my ($self, $gcodegen, $toolchange) = @_; - - my $gcode = ""; - - # Reduce feedrate a bit; travel speed is often too high to move on existing material. - # Too fast = ripping of existing material; too slow = short wipe path, thus more blob. - my $wipe_speed = $gcodegen->writer->config->get('travel_speed') * 0.8; - - # get the retraction length - my $length = $toolchange - ? $gcodegen->writer->extruder->retract_length_toolchange - : $gcodegen->writer->extruder->retract_length; - - if ($length) { - # Calculate how long we need to travel in order to consume the required - # amount of retraction. In other words, how far do we move in XY at $wipe_speed - # for the time needed to consume retract_length at retract_speed? - my $wipe_dist = scale($length / $gcodegen->writer->extruder->retract_speed * $wipe_speed); - - # Take the stored wipe path and replace first point with the current actual position - # (they might be different, for example, in case of loop clipping). - my $wipe_path = Slic3r::Polyline->new( - $gcodegen->last_pos, - @{$self->path}[1..$#{$self->path}], - ); - # - $wipe_path->clip_end($wipe_path->length - $wipe_dist); - - # subdivide the retraction in segments - my $retracted = 0; - foreach my $line (@{$wipe_path->lines}) { - my $segment_length = $line->length; - # Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one - # due to rounding (TODO: test and/or better math for this) - my $dE = $length * ($segment_length / $wipe_dist) * 0.95; - $gcode .= $gcodegen->writer->set_speed($wipe_speed*60); - $gcode .= $gcodegen->writer->extrude_to_xy( - $gcodegen->point_to_gcode($line->b), - -$dE, - 'wipe and retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''), - ); - $retracted += $dE; - } - $gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted); - - # prevent wiping again on same path - $self->reset_path; - } - - return $gcode; -} - 1; diff --git a/t/shells.t b/t/shells.t index f3fd62d14c..e7eb5da13b 100644 --- a/t/shells.t +++ b/t/shells.t @@ -192,7 +192,7 @@ use Slic3r::Test; push @z_steps, $info->{dist_Z} if $started_extruding && $info->{dist_Z} > 0; $travel_moves_after_first_extrusion++ - if $info->{travel} && $started_extruding && !exists $args->{Z}; + if $info->{travel} && $info->{dist_XY} > 0 && $started_extruding && !exists $args->{Z}; } elsif ($cmd eq 'M104') { $first_layer_temperature_set = 1 if $args->{S} == 205; $temperature_set = 1 if $args->{S} == 200; @@ -271,7 +271,7 @@ use Slic3r::Test; foreach my $segment (@this_layer) { # check that segment's dist_Z is proportioned to its dist_XY $all_layer_segments_have_same_slope = 1 - if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.1; + if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.2; } @this_layer = (); diff --git a/xs/MANIFEST b/xs/MANIFEST index 7d7923d266..2011b9cf35 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1752,6 +1752,7 @@ t/17_boundingbox.t t/18_motionplanner.t t/19_model.t t/20_print.t +t/21_gcode.t xsp/BoundingBox.xsp xsp/BridgeDetector.xsp xsp/Clipper.xsp diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 506e119824..9acfe47d35 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -36,16 +36,15 @@ AvoidCrossingPerimeters::init_layer_mp(const ExPolygons &islands) } Polyline -AvoidCrossingPerimeters::travel_to(Point point, const Pointf &gcodegen_origin, - const Point &gcodegen_last_pos) +AvoidCrossingPerimeters::travel_to(GCode &gcodegen, Point point) { if (this->use_external_mp || this->use_external_mp_once) { // get current origin set in gcodegen // (the one that will be used to translate the G-code coordinates by) - Point scaled_origin = Point::new_scale(gcodegen_origin.x, gcodegen_origin.y); + Point scaled_origin = Point::new_scale(gcodegen.origin.x, gcodegen.origin.y); // represent last_pos in absolute G-code coordinates - Point last_pos = gcodegen_last_pos; + Point last_pos = gcodegen.last_pos(); last_pos.translate(scaled_origin); // represent point in absolute G-code coordinates @@ -59,7 +58,7 @@ AvoidCrossingPerimeters::travel_to(Point point, const Pointf &gcodegen_origin, travel.translate(scaled_origin.negative()); return travel; } else { - return this->_layer_mp->shortest_path(gcodegen_last_pos, point); + return this->_layer_mp->shortest_path(gcodegen.last_pos(), point); } } @@ -93,6 +92,62 @@ Wipe::reset_path() this->path = Polyline(); } +std::string +Wipe::wipe(GCode &gcodegen, bool toolchange) +{ + std::string gcode; + + /* Reduce feedrate a bit; travel speed is often too high to move on existing material. + Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ + double wipe_speed = gcodegen.writer.config.travel_speed.value * 0.8; + + // get the retraction length + double length = toolchange + ? gcodegen.writer.extruder()->retract_length_toolchange() + : gcodegen.writer.extruder()->retract_length(); + + if (length > 0) { + /* Calculate how long we need to travel in order to consume the required + amount of retraction. In other words, how far do we move in XY at wipe_speed + for the time needed to consume retract_length at retract_speed? */ + double wipe_dist = scale_(length / gcodegen.writer.extruder()->retract_speed() * wipe_speed); + + /* Take the stored wipe path and replace first point with the current actual position + (they might be different, for example, in case of loop clipping). */ + Polyline wipe_path; + wipe_path.append(gcodegen.last_pos()); + wipe_path.append( + this->path.points.begin() + 1, + this->path.points.end() + ); + + wipe_path.clip_end(wipe_path.length() - wipe_dist); + + // subdivide the retraction in segments + double retracted = 0; + Lines lines = wipe_path.lines(); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { + double segment_length = line->length(); + /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one + due to rounding (TODO: test and/or better math for this) */ + double dE = length * (segment_length / wipe_dist) * 0.95; + gcode += gcodegen.writer.set_speed(wipe_speed*60); + gcode += gcodegen.writer.extrude_to_xy( + gcodegen.point_to_gcode(line->b), + -dE, + (std::string)"wipe and retract" + (gcodegen.enable_cooling_markers ? ";_WIPE" : "") + ); + retracted += dE; + } + gcodegen.writer.extruder()->retracted += retracted; + + // prevent wiping again on same path + this->reset_path(); + } + + return gcode; +} + #ifdef SLIC3RXS REGISTER_CLASS(Wipe, "GCode::Wipe"); #endif @@ -100,10 +155,110 @@ REGISTER_CLASS(Wipe, "GCode::Wipe"); GCode::GCode() : enable_loop_clipping(true), enable_cooling_markers(false), layer_count(0), layer_index(-1), first_layer(false), elapsed_time(0), volumetric_speed(0), - last_pos_defined(false) + _last_pos_defined(false) { } +Point& +GCode::last_pos() +{ + return this->_last_pos; +} + +void +GCode::set_last_pos(const Point &pos) +{ + this->_last_pos = pos; + this->_last_pos_defined = true; +} + +bool +GCode::last_pos_defined() const +{ + return this->_last_pos_defined; +} + +void +GCode::apply_print_config(const PrintConfig &print_config) +{ + this->writer.apply_print_config(print_config); + this->config.apply(print_config); +} + +void +GCode::set_origin(const Pointf &pointf) +{ + // if origin increases (goes towards right), last_pos decreases because it goes towards left + Point translate( + scale_(this->origin.x - pointf.x), + scale_(this->origin.y - pointf.y) + ); + this->_last_pos.translate(translate); + this->wipe.path.translate(translate); + + this->origin = pointf; +} + +std::string +GCode::preamble() +{ + std::string gcode = this->writer.preamble(); + + /* Perform a *silent* move to z_offset: we need this to initialize the Z + position of our writer object so that any initial lift taking place + before the first layer change will raise the extruder from the correct + initial Z instead of 0. */ + this->writer.travel_to_z(this->config.z_offset.value); + + return gcode; +} + +std::string +GCode::retract(bool toolchange) +{ + std::string gcode; + + if (this->writer.extruder() == NULL) + return gcode; + + // wipe (if it's enabled for this extruder and we have a stored wipe path) + if (this->config.wipe.get_at(this->writer.extruder()->id) && this->wipe.has_path()) { + gcode += this->wipe.wipe(*this, toolchange); + } + + /* The parent class will decide whether we need to perform an actual retraction + (the extruder might be already retracted fully or partially). We call these + methods even if we performed wipe, since this will ensure the entire retraction + length is honored in case wipe path was too short. */ + gcode += toolchange ? this->writer.retract_for_toolchange() : this->writer.retract(); + + gcode += this->writer.reset_e(); + if (this->writer.extruder()->retract_length() > 0 || this->config.use_firmware_retraction) + gcode += this->writer.lift(); + + return gcode; +} + +std::string +GCode::unretract() +{ + std::string gcode; + gcode += this->writer.unlift(); + gcode += this->writer.unretract(); + return gcode; +} + +// convert a model-space scaled point into G-code coordinates +Pointf +GCode::point_to_gcode(const Point &point) +{ + Pointf extruder_offset = this->config.extruder_offset.get_at(this->writer.extruder()->id); + return Pointf( + unscale(point.x) + this->origin.x - extruder_offset.x, + unscale(point.y) + this->origin.y - extruder_offset.y + ); +} + #ifdef SLIC3RXS REGISTER_CLASS(GCode, "GCode"); #endif diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 8eed9600f5..8b37386e9f 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -14,7 +14,7 @@ namespace Slic3r { -// draft for a binary representation of a G-code line +class GCode; class AvoidCrossingPerimeters { public: @@ -31,10 +31,7 @@ class AvoidCrossingPerimeters { ~AvoidCrossingPerimeters(); void init_external_mp(const ExPolygons &islands); void init_layer_mp(const ExPolygons &islands); - - //Polyline travel_to(GCode &gcodegen, const Point &point); - Polyline travel_to(Point point, const Pointf &gcodegen_origin, - const Point &gcodegen_last_pos); + Polyline travel_to(GCode &gcodegen, Point point); private: MotionPlanner* _external_mp; @@ -57,7 +54,7 @@ class Wipe { Wipe(); bool has_path(); void reset_path(); - //std::string wipe(GCode &gcodegen, bool toolchange = false); + std::string wipe(GCode &gcodegen, bool toolchange = false); }; class GCode { @@ -81,11 +78,22 @@ class GCode { std::map _seam_position; bool first_layer; // this flag triggers first layer speeds unsigned int elapsed_time; // seconds - Point last_pos; - bool last_pos_defined; double volumetric_speed; GCode(); + Point& last_pos(); + void set_last_pos(const Point &pos); + bool last_pos_defined() const; + void apply_print_config(const PrintConfig &print_config); + void set_origin(const Pointf &pointf); + std::string preamble(); + std::string retract(bool toolchange = false); + std::string unretract(); + Pointf point_to_gcode(const Point &point); + + private: + Point _last_pos; + bool _last_pos_defined; }; } diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp index 614306e429..2e7131e6ea 100644 --- a/xs/src/libslic3r/MultiPoint.cpp +++ b/xs/src/libslic3r/MultiPoint.cpp @@ -100,6 +100,24 @@ MultiPoint::remove_duplicate_points() } } +void +MultiPoint::append(const Point &point) +{ + this->points.push_back(point); +} + +void +MultiPoint::append(const Points &points) +{ + this->append(points.begin(), points.end()); +} + +void +MultiPoint::append(const Points::const_iterator &begin, const Points::const_iterator &end) +{ + this->points.insert(this->points.end(), begin, end); +} + Points MultiPoint::_douglas_peucker(const Points &points, const double tolerance) { diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index 12c500836f..63ee3bda7c 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -33,6 +33,9 @@ class MultiPoint bool has_boundary_point(const Point &point) const; BoundingBox bounding_box() const; void remove_duplicate_points(); + void append(const Point &point); + void append(const Points &points); + void append(const Points::const_iterator &begin, const Points::const_iterator &end); static Points _douglas_peucker(const Points &points, const double tolerance); diff --git a/xs/t/21_gcode.t b/xs/t/21_gcode.t new file mode 100644 index 0000000000..307de6421b --- /dev/null +++ b/xs/t/21_gcode.t @@ -0,0 +1,17 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Slic3r::XS; +use Test::More tests => 2; + +{ + my $gcodegen = Slic3r::GCode->new; + $gcodegen->set_origin(Slic3r::Pointf->new(10,0)); + is_deeply $gcodegen->origin->pp, [10,0], 'set_origin'; + $gcodegen->origin->translate(5,5); + is_deeply $gcodegen->origin->pp, [15,5], 'origin returns reference to point'; +} + +__END__ diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 8000b973aa..41bdbc0425 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -11,8 +11,8 @@ void init_external_mp(ExPolygons islands); void init_layer_mp(ExPolygons islands); - Clone travel_to(Point* point, Pointf* gcodegen_origin, Point* gcodegen_last_pos) - %code{% RETVAL = THIS->travel_to(*point, *gcodegen_origin, *gcodegen_last_pos); %}; + Clone travel_to(GCode* gcode, Point* point) + %code{% RETVAL = THIS->travel_to(*gcode, *point); %}; bool use_external_mp() %code{% RETVAL = THIS->use_external_mp; %}; @@ -51,6 +51,8 @@ bool has_path(); void reset_path(); + std::string wipe(GCode* gcodegen, bool toolchange = false) + %code{% RETVAL = THIS->wipe(*gcodegen, toolchange); %}; bool enable() %code{% RETVAL = THIS->enable; %}; @@ -69,8 +71,6 @@ Ref origin() %code{% RETVAL = &(THIS->origin); %}; - void _set_origin(Pointf* value) - %code{% THIS->origin = *value; %}; Ref config() %code{% RETVAL = &(THIS->config); %}; @@ -136,15 +136,24 @@ void set_elapsed_time(unsigned int value) %code{% THIS->elapsed_time = value; %}; - bool last_pos_defined() - %code{% RETVAL = THIS->last_pos_defined; %}; + bool last_pos_defined(); Ref last_pos() - %code{% RETVAL = &(THIS->last_pos); %}; - void set_last_pos(Point* value) - %code{% THIS->last_pos = *value; THIS->last_pos_defined = true; %}; + %code{% RETVAL = &(THIS->last_pos()); %}; + void set_last_pos(Point* pos) + %code{% THIS->set_last_pos(*pos); %}; double volumetric_speed() %code{% RETVAL = THIS->volumetric_speed; %}; void set_volumetric_speed(double value) %code{% THIS->volumetric_speed = value; %}; + + void apply_print_config(PrintConfig* print_config) + %code{% THIS->apply_print_config(*print_config); %}; + void set_origin(Pointf* pointf) + %code{% THIS->set_origin(*pointf); %}; + std::string preamble(); + std::string retract(bool toolchange = false); + std::string unretract(); + Clone point_to_gcode(Point* point) + %code{% RETVAL = THIS->point_to_gcode(*point); %}; }; From 5571144c0ec51a3ffbabe5a357982f2a8b593c8c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 1 Jul 2015 23:14:40 +0200 Subject: [PATCH 62/83] Ported Slic3r::GCode::needs_retraction() to XS --- lib/Slic3r/GCode.pm | 35 ---------------------------------- xs/src/libslic3r/GCode.cpp | 39 ++++++++++++++++++++++++++++++++++++++ xs/src/libslic3r/GCode.hpp | 1 + xs/xsp/GCode.xsp | 2 ++ 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 8881cf0cde..ae1ea04534 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -334,41 +334,6 @@ sub travel_to { return $gcode; } -sub needs_retraction { - my ($self, $travel, $role) = @_; - - if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)) { - # skip retraction if the move is shorter than the configured threshold - return 0; - } - - if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->as_support_layer->support_islands->contains_polyline($travel)) { - # skip retraction if this is a travel move inside a support material island - return 0; - } - - if ($self->config->only_retract_when_crossing_perimeters && $self->has_layer) { - if ($self->config->fill_density > 0 - && $self->layer->any_internal_region_slice_contains_polyline($travel)) { - # skip retraction if travel is contained in an internal slice *and* - # internal infill is enabled (so that stringing is entirely not visible) - return 0; - } elsif ($self->layer->any_bottom_region_slice_contains_polyline($travel) - && defined $self->layer->upper_layer - && $self->layer->upper_layer->slices->contains_polyline($travel) - && ($self->config->bottom_solid_layers >= 2 || $self->config->fill_density > 0)) { - # skip retraction if travel is contained in an *infilled* bottom slice - # but only if it's also covered by an *infilled* upper layer's slice - # so that it's not visible from above (a bottom surface might not have an - # upper slice in case of a thin membrane) - return 0; - } - } - - # retract if only_retract_when_crossing_perimeters is disabled or doesn't apply - return 1; -} - sub set_extruder { my ($self, $extruder_id) = @_; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 9acfe47d35..d7b2fcbfd4 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -1,4 +1,5 @@ #include "GCode.hpp" +#include "ExtrusionEntity.hpp" namespace Slic3r { @@ -213,6 +214,44 @@ GCode::preamble() return gcode; } +bool +GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) +{ + if (travel.length() < scale_(this->config.retract_before_travel.get_at(this->writer.extruder()->id))) { + // skip retraction if the move is shorter than the configured threshold + return false; + } + + if (role == erSupportMaterial) { + SupportLayer* support_layer = dynamic_cast(this->layer); + if (support_layer->support_islands.contains(travel)) { + // skip retraction if this is a travel move inside a support material island + return false; + } + } + + if (this->config.only_retract_when_crossing_perimeters && this->layer != NULL) { + if (this->config.fill_density.value > 0 + && this->layer->any_internal_region_slice_contains(travel)) { + /* skip retraction if travel is contained in an internal slice *and* + internal infill is enabled (so that stringing is entirely not visible) */ + return false; + } else if (this->layer->any_bottom_region_slice_contains(travel) + && this->layer->upper_layer != NULL + && this->layer->upper_layer->slices.contains(travel) + && (this->config.bottom_solid_layers.value >= 2 || this->config.fill_density.value > 0)) { + /* skip retraction if travel is contained in an *infilled* bottom slice + but only if it's also covered by an *infilled* upper layer's slice + so that it's not visible from above (a bottom surface might not have an + upper slice in case of a thin membrane) */ + return false; + } + } + + // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply + return true; +} + std::string GCode::retract(bool toolchange) { diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 8b37386e9f..2dbbebddb8 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -87,6 +87,7 @@ class GCode { void apply_print_config(const PrintConfig &print_config); void set_origin(const Pointf &pointf); std::string preamble(); + bool needs_retraction(const Polyline &travel, ExtrusionRole role); std::string retract(bool toolchange = false); std::string unretract(); Pointf point_to_gcode(const Point &point); diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 41bdbc0425..fdfc3d8947 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -152,6 +152,8 @@ void set_origin(Pointf* pointf) %code{% THIS->set_origin(*pointf); %}; std::string preamble(); + bool needs_retraction(Polyline* travel, ExtrusionRole role) + %code{% RETVAL = THIS->needs_retraction(*travel, role); %}; std::string retract(bool toolchange = false); std::string unretract(); Clone point_to_gcode(Point* point) From b14290b9f67b3dfe12a5547eafcd434ae9fd0c75 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 14:29:20 +0200 Subject: [PATCH 63/83] Make tests happy --- lib/Slic3r/ExtrusionPath.pm | 3 ++- lib/Slic3r/GCode.pm | 4 ++-- lib/Slic3r/Layer/PerimeterGenerator.pm | 2 +- t/perimeters.t | 11 +++++++---- xs/src/libslic3r/ExtrusionEntity.hpp | 1 + xs/src/libslic3r/GCode.cpp | 4 ++-- xs/src/libslic3r/GCode.hpp | 2 +- xs/xsp/ExtrusionLoop.xsp | 3 +-- xs/xsp/ExtrusionPath.xsp | 1 + xs/xsp/GCode.xsp | 2 +- 10 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index 0c324ab09b..3e9e6b8c72 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -6,7 +6,8 @@ use parent qw(Exporter); our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE - EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE); + EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE + EXTR_ROLE_NONE); our %EXPORT_TAGS = (roles => \@EXPORT_OK); 1; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index ae1ea04534..96a3170fd4 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -306,7 +306,7 @@ sub travel_to { my $travel = Slic3r::Polyline->new($self->last_pos, $point); # check whether a straight travel move would need retraction - my $needs_retraction = $self->needs_retraction($travel, $role); + my $needs_retraction = $self->needs_retraction($travel, $role // EXTR_ROLE_NONE); # if a retraction would be needed, try to use avoid_crossing_perimeters to plan a # multi-hop travel path inside the configuration space @@ -316,7 +316,7 @@ sub travel_to { $travel = $self->avoid_crossing_perimeters->travel_to($self, $point); # check again whether the new travel path still needs a retraction - $needs_retraction = $self->needs_retraction($travel, $role); + $needs_retraction = $self->needs_retraction($travel, $role // EXTR_ROLE_NONE); } # Re-allow avoid_crossing_perimeters for the next travel moves diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index 07da50f506..daf2ba8f80 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -355,7 +355,7 @@ sub _traverse_loops { # there's only one contour loop). $loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER; } else { - $loop_role = EXTR_ROLE_PERIMETER; + $loop_role = EXTRL_ROLE_DEFAULT; } # detect overhanging/bridging perimeters diff --git a/t/perimeters.t b/t/perimeters.t index dc2644e9de..d103eb3f27 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -329,13 +329,16 @@ use Slic3r::Test; scalar(@$expolygons), 'expected number of collections'; ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @{$g->loops}), 'everything is returned as collections'; - is scalar(map @$_, @{$g->loops}), + + my $flattened_loops = $g->loops->flatten; + my @loops = @$flattened_loops; + is scalar(@loops), $expected{total}, 'expected number of loops'; - is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, map @$_, @{$g->loops}), + is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops), $expected{external}, 'expected number of external loops'; - is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, map @$_, @{$g->loops}), + is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops), $expected{cinternal}, 'expected number of internal contour loops'; - is scalar(grep $_->polygon->is_counter_clockwise, map @$_, @{$g->loops}), + is scalar(grep $_->polygon->is_counter_clockwise, @loops), $expected{ccw}, 'expected number of ccw loops'; return $g; diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 74541832f0..9be5b4125a 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -13,6 +13,7 @@ class Extruder; /* Each ExtrusionRole value identifies a distinct set of { extruder, speed } */ enum ExtrusionRole { + erNone, erPerimeter, erExternalPerimeter, erOverhangPerimeter, diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index d7b2fcbfd4..a7241b7d62 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -156,7 +156,7 @@ REGISTER_CLASS(Wipe, "GCode::Wipe"); GCode::GCode() : enable_loop_clipping(true), enable_cooling_markers(false), layer_count(0), layer_index(-1), first_layer(false), elapsed_time(0), volumetric_speed(0), - _last_pos_defined(false) + _last_pos_defined(false), layer(NULL), placeholder_parser(NULL) { } @@ -224,7 +224,7 @@ GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) if (role == erSupportMaterial) { SupportLayer* support_layer = dynamic_cast(this->layer); - if (support_layer->support_islands.contains(travel)) { + if (support_layer != NULL && support_layer->support_islands.contains(travel)) { // skip retraction if this is a travel move inside a support material island return false; } diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 2dbbebddb8..846b822998 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -87,7 +87,7 @@ class GCode { void apply_print_config(const PrintConfig &print_config); void set_origin(const Pointf &pointf); std::string preamble(); - bool needs_retraction(const Polyline &travel, ExtrusionRole role); + bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); std::string retract(bool toolchange = false); std::string unretract(); Pointf point_to_gcode(const Point &point); diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp index 0da0fe0d46..3b7adcd6a1 100644 --- a/xs/xsp/ExtrusionLoop.xsp +++ b/xs/xsp/ExtrusionLoop.xsp @@ -38,9 +38,8 @@ ExtrusionLoop::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->paths.size()-1); - int i = 0; for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) { - av_store(av, i++, perl_to_SV_ref(*it)); + av_store(av, it - THIS->paths.begin(), perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp index 57fee2b619..4918628404 100644 --- a/xs/xsp/ExtrusionPath.xsp +++ b/xs/xsp/ExtrusionPath.xsp @@ -133,6 +133,7 @@ ExtrusionPath::subtract_expolygons(ExPolygonCollection* collection) IV _constant() ALIAS: + EXTR_ROLE_NONE = erNone EXTR_ROLE_PERIMETER = erPerimeter EXTR_ROLE_EXTERNAL_PERIMETER = erExternalPerimeter EXTR_ROLE_OVERHANG_PERIMETER = erOverhangPerimeter diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index fdfc3d8947..5095fcfab3 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -152,7 +152,7 @@ void set_origin(Pointf* pointf) %code{% THIS->set_origin(*pointf); %}; std::string preamble(); - bool needs_retraction(Polyline* travel, ExtrusionRole role) + bool needs_retraction(Polyline* travel, ExtrusionRole role = erNone) %code{% RETVAL = THIS->needs_retraction(*travel, role); %}; std::string retract(bool toolchange = false); std::string unretract(); From ff5747bb6002d41e223eac0d6b53eebda79fcd84 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 14:31:21 +0200 Subject: [PATCH 64/83] Macro for readability --- xs/src/libslic3r/GCode.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index a7241b7d62..abeba12e99 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -153,6 +153,8 @@ Wipe::wipe(GCode &gcodegen, bool toolchange) REGISTER_CLASS(Wipe, "GCode::Wipe"); #endif +#define EXTRUDER_CONFIG(OPT) this->config.OPT.get_at(this->writer.extruder()->id) + GCode::GCode() : enable_loop_clipping(true), enable_cooling_markers(false), layer_count(0), layer_index(-1), first_layer(false), elapsed_time(0), volumetric_speed(0), @@ -217,7 +219,7 @@ GCode::preamble() bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) { - if (travel.length() < scale_(this->config.retract_before_travel.get_at(this->writer.extruder()->id))) { + if (travel.length() < scale_(EXTRUDER_CONFIG(retract_before_travel))) { // skip retraction if the move is shorter than the configured threshold return false; } @@ -261,7 +263,7 @@ GCode::retract(bool toolchange) return gcode; // wipe (if it's enabled for this extruder and we have a stored wipe path) - if (this->config.wipe.get_at(this->writer.extruder()->id) && this->wipe.has_path()) { + if (EXTRUDER_CONFIG(wipe) && this->wipe.has_path()) { gcode += this->wipe.wipe(*this, toolchange); } @@ -291,7 +293,7 @@ GCode::unretract() Pointf GCode::point_to_gcode(const Point &point) { - Pointf extruder_offset = this->config.extruder_offset.get_at(this->writer.extruder()->id); + Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); return Pointf( unscale(point.x) + this->origin.x - extruder_offset.x, unscale(point.y) + this->origin.y - extruder_offset.y From 72355a95008de8bd280cab0a0899935f6d687ede Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 14:35:21 +0200 Subject: [PATCH 65/83] Use macro in PrintConfig.hpp --- xs/src/libslic3r/PrintConfig.hpp | 262 ++++++++++++++++--------------- 1 file changed, 132 insertions(+), 130 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 4a3c4a2696..2bef5695f9 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -3,6 +3,8 @@ #include "Config.hpp" +#define OPT_PTR(KEY) if (opt_key == #KEY) return &this->KEY + namespace Slic3r { enum GCodeFlavor { @@ -148,29 +150,29 @@ class PrintObjectConfig : public virtual StaticPrintConfig }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { - if (opt_key == "dont_support_bridges") return &this->dont_support_bridges; - if (opt_key == "extrusion_width") return &this->extrusion_width; - if (opt_key == "first_layer_height") return &this->first_layer_height; - if (opt_key == "infill_only_where_needed") return &this->infill_only_where_needed; - if (opt_key == "interface_shells") return &this->interface_shells; - if (opt_key == "layer_height") return &this->layer_height; - if (opt_key == "raft_layers") return &this->raft_layers; - if (opt_key == "seam_position") return &this->seam_position; - if (opt_key == "support_material") return &this->support_material; - if (opt_key == "support_material_angle") return &this->support_material_angle; - if (opt_key == "support_material_contact_distance") return &this->support_material_contact_distance; - if (opt_key == "support_material_enforce_layers") return &this->support_material_enforce_layers; - if (opt_key == "support_material_extruder") return &this->support_material_extruder; - if (opt_key == "support_material_extrusion_width") return &this->support_material_extrusion_width; - if (opt_key == "support_material_interface_extruder") return &this->support_material_interface_extruder; - if (opt_key == "support_material_interface_layers") return &this->support_material_interface_layers; - if (opt_key == "support_material_interface_spacing") return &this->support_material_interface_spacing; - if (opt_key == "support_material_interface_speed") return &this->support_material_interface_speed; - if (opt_key == "support_material_pattern") return &this->support_material_pattern; - if (opt_key == "support_material_spacing") return &this->support_material_spacing; - if (opt_key == "support_material_speed") return &this->support_material_speed; - if (opt_key == "support_material_threshold") return &this->support_material_threshold; - if (opt_key == "xy_size_compensation") return &this->xy_size_compensation; + OPT_PTR(dont_support_bridges); + OPT_PTR(extrusion_width); + OPT_PTR(first_layer_height); + OPT_PTR(infill_only_where_needed); + OPT_PTR(interface_shells); + OPT_PTR(layer_height); + OPT_PTR(raft_layers); + OPT_PTR(seam_position); + OPT_PTR(support_material); + OPT_PTR(support_material_angle); + OPT_PTR(support_material_contact_distance); + OPT_PTR(support_material_enforce_layers); + OPT_PTR(support_material_extruder); + OPT_PTR(support_material_extrusion_width); + OPT_PTR(support_material_interface_extruder); + OPT_PTR(support_material_interface_layers); + OPT_PTR(support_material_interface_spacing); + OPT_PTR(support_material_interface_speed); + OPT_PTR(support_material_pattern); + OPT_PTR(support_material_spacing); + OPT_PTR(support_material_speed); + OPT_PTR(support_material_threshold); + OPT_PTR(xy_size_compensation); return NULL; }; @@ -258,38 +260,38 @@ class PrintRegionConfig : public virtual StaticPrintConfig }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { - if (opt_key == "bottom_solid_layers") return &this->bottom_solid_layers; - if (opt_key == "bridge_flow_ratio") return &this->bridge_flow_ratio; - if (opt_key == "bridge_speed") return &this->bridge_speed; - if (opt_key == "external_fill_pattern") return &this->external_fill_pattern; - if (opt_key == "external_perimeter_extrusion_width") return &this->external_perimeter_extrusion_width; - if (opt_key == "external_perimeter_speed") return &this->external_perimeter_speed; - if (opt_key == "external_perimeters_first") return &this->external_perimeters_first; - if (opt_key == "extra_perimeters") return &this->extra_perimeters; - if (opt_key == "fill_angle") return &this->fill_angle; - if (opt_key == "fill_density") return &this->fill_density; - if (opt_key == "fill_pattern") return &this->fill_pattern; - if (opt_key == "gap_fill_speed") return &this->gap_fill_speed; - if (opt_key == "infill_extruder") return &this->infill_extruder; - if (opt_key == "infill_extrusion_width") return &this->infill_extrusion_width; - if (opt_key == "infill_every_layers") return &this->infill_every_layers; - if (opt_key == "infill_overlap") return &this->infill_overlap; - if (opt_key == "infill_speed") return &this->infill_speed; - if (opt_key == "overhangs") return &this->overhangs; - if (opt_key == "perimeter_extruder") return &this->perimeter_extruder; - if (opt_key == "perimeter_extrusion_width") return &this->perimeter_extrusion_width; - if (opt_key == "perimeter_speed") return &this->perimeter_speed; - if (opt_key == "perimeters") return &this->perimeters; - if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed; - if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area; - if (opt_key == "solid_infill_extruder") return &this->solid_infill_extruder; - if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width; - if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers; - if (opt_key == "solid_infill_speed") return &this->solid_infill_speed; - if (opt_key == "thin_walls") return &this->thin_walls; - if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width; - if (opt_key == "top_solid_infill_speed") return &this->top_solid_infill_speed; - if (opt_key == "top_solid_layers") return &this->top_solid_layers; + OPT_PTR(bottom_solid_layers); + OPT_PTR(bridge_flow_ratio); + OPT_PTR(bridge_speed); + OPT_PTR(external_fill_pattern); + OPT_PTR(external_perimeter_extrusion_width); + OPT_PTR(external_perimeter_speed); + OPT_PTR(external_perimeters_first); + OPT_PTR(extra_perimeters); + OPT_PTR(fill_angle); + OPT_PTR(fill_density); + OPT_PTR(fill_pattern); + OPT_PTR(gap_fill_speed); + OPT_PTR(infill_extruder); + OPT_PTR(infill_extrusion_width); + OPT_PTR(infill_every_layers); + OPT_PTR(infill_overlap); + OPT_PTR(infill_speed); + OPT_PTR(overhangs); + OPT_PTR(perimeter_extruder); + OPT_PTR(perimeter_extrusion_width); + OPT_PTR(perimeter_speed); + OPT_PTR(perimeters); + OPT_PTR(small_perimeter_speed); + OPT_PTR(solid_infill_below_area); + OPT_PTR(solid_infill_extruder); + OPT_PTR(solid_infill_extrusion_width); + OPT_PTR(solid_infill_every_layers); + OPT_PTR(solid_infill_speed); + OPT_PTR(thin_walls); + OPT_PTR(top_infill_extrusion_width); + OPT_PTR(top_solid_infill_speed); + OPT_PTR(top_solid_layers); return NULL; }; @@ -357,29 +359,29 @@ class GCodeConfig : public virtual StaticPrintConfig }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { - if (opt_key == "before_layer_gcode") return &this->before_layer_gcode; - if (opt_key == "end_gcode") return &this->end_gcode; - if (opt_key == "extrusion_axis") return &this->extrusion_axis; - if (opt_key == "extrusion_multiplier") return &this->extrusion_multiplier; - if (opt_key == "filament_diameter") return &this->filament_diameter; - if (opt_key == "gcode_comments") return &this->gcode_comments; - if (opt_key == "gcode_flavor") return &this->gcode_flavor; - if (opt_key == "layer_gcode") return &this->layer_gcode; - if (opt_key == "max_print_speed") return &this->max_print_speed; - if (opt_key == "max_volumetric_speed") return &this->max_volumetric_speed; - if (opt_key == "pressure_advance") return &this->pressure_advance; - if (opt_key == "retract_length") return &this->retract_length; - if (opt_key == "retract_length_toolchange") return &this->retract_length_toolchange; - if (opt_key == "retract_lift") return &this->retract_lift; - if (opt_key == "retract_restart_extra") return &this->retract_restart_extra; - if (opt_key == "retract_restart_extra_toolchange") return &this->retract_restart_extra_toolchange; - if (opt_key == "retract_speed") return &this->retract_speed; - if (opt_key == "start_gcode") return &this->start_gcode; - if (opt_key == "toolchange_gcode") return &this->toolchange_gcode; - if (opt_key == "travel_speed") return &this->travel_speed; - if (opt_key == "use_firmware_retraction") return &this->use_firmware_retraction; - if (opt_key == "use_relative_e_distances") return &this->use_relative_e_distances; - if (opt_key == "use_volumetric_e") return &this->use_volumetric_e; + OPT_PTR(before_layer_gcode); + OPT_PTR(end_gcode); + OPT_PTR(extrusion_axis); + OPT_PTR(extrusion_multiplier); + OPT_PTR(filament_diameter); + OPT_PTR(gcode_comments); + OPT_PTR(gcode_flavor); + OPT_PTR(layer_gcode); + OPT_PTR(max_print_speed); + OPT_PTR(max_volumetric_speed); + OPT_PTR(pressure_advance); + OPT_PTR(retract_length); + OPT_PTR(retract_length_toolchange); + OPT_PTR(retract_lift); + OPT_PTR(retract_restart_extra); + OPT_PTR(retract_restart_extra_toolchange); + OPT_PTR(retract_speed); + OPT_PTR(start_gcode); + OPT_PTR(toolchange_gcode); + OPT_PTR(travel_speed); + OPT_PTR(use_firmware_retraction); + OPT_PTR(use_relative_e_distances); + OPT_PTR(use_volumetric_e); return NULL; }; @@ -516,56 +518,56 @@ class PrintConfig : public GCodeConfig }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { - if (opt_key == "avoid_crossing_perimeters") return &this->avoid_crossing_perimeters; - if (opt_key == "bed_shape") return &this->bed_shape; - if (opt_key == "bed_temperature") return &this->bed_temperature; - if (opt_key == "bridge_acceleration") return &this->bridge_acceleration; - if (opt_key == "bridge_fan_speed") return &this->bridge_fan_speed; - if (opt_key == "brim_width") return &this->brim_width; - if (opt_key == "complete_objects") return &this->complete_objects; - if (opt_key == "cooling") return &this->cooling; - if (opt_key == "default_acceleration") return &this->default_acceleration; - if (opt_key == "disable_fan_first_layers") return &this->disable_fan_first_layers; - if (opt_key == "duplicate_distance") return &this->duplicate_distance; - if (opt_key == "extruder_clearance_height") return &this->extruder_clearance_height; - if (opt_key == "extruder_clearance_radius") return &this->extruder_clearance_radius; - if (opt_key == "extruder_offset") return &this->extruder_offset; - if (opt_key == "fan_always_on") return &this->fan_always_on; - if (opt_key == "fan_below_layer_time") return &this->fan_below_layer_time; - if (opt_key == "filament_colour") return &this->filament_colour; - if (opt_key == "first_layer_acceleration") return &this->first_layer_acceleration; - if (opt_key == "first_layer_bed_temperature") return &this->first_layer_bed_temperature; - if (opt_key == "first_layer_extrusion_width") return &this->first_layer_extrusion_width; - if (opt_key == "first_layer_speed") return &this->first_layer_speed; - if (opt_key == "first_layer_temperature") return &this->first_layer_temperature; - if (opt_key == "gcode_arcs") return &this->gcode_arcs; - if (opt_key == "infill_acceleration") return &this->infill_acceleration; - if (opt_key == "infill_first") return &this->infill_first; - if (opt_key == "max_fan_speed") return &this->max_fan_speed; - if (opt_key == "min_fan_speed") return &this->min_fan_speed; - if (opt_key == "min_print_speed") return &this->min_print_speed; - if (opt_key == "min_skirt_length") return &this->min_skirt_length; - if (opt_key == "notes") return &this->notes; - if (opt_key == "nozzle_diameter") return &this->nozzle_diameter; - if (opt_key == "only_retract_when_crossing_perimeters") return &this->only_retract_when_crossing_perimeters; - if (opt_key == "ooze_prevention") return &this->ooze_prevention; - if (opt_key == "output_filename_format") return &this->output_filename_format; - if (opt_key == "perimeter_acceleration") return &this->perimeter_acceleration; - if (opt_key == "post_process") return &this->post_process; - if (opt_key == "resolution") return &this->resolution; - if (opt_key == "retract_before_travel") return &this->retract_before_travel; - if (opt_key == "retract_layer_change") return &this->retract_layer_change; - if (opt_key == "skirt_distance") return &this->skirt_distance; - if (opt_key == "skirt_height") return &this->skirt_height; - if (opt_key == "skirts") return &this->skirts; - if (opt_key == "slowdown_below_layer_time") return &this->slowdown_below_layer_time; - if (opt_key == "spiral_vase") return &this->spiral_vase; - if (opt_key == "standby_temperature_delta") return &this->standby_temperature_delta; - if (opt_key == "temperature") return &this->temperature; - if (opt_key == "threads") return &this->threads; - if (opt_key == "vibration_limit") return &this->vibration_limit; - if (opt_key == "wipe") return &this->wipe; - if (opt_key == "z_offset") return &this->z_offset; + OPT_PTR(avoid_crossing_perimeters); + OPT_PTR(bed_shape); + OPT_PTR(bed_temperature); + OPT_PTR(bridge_acceleration); + OPT_PTR(bridge_fan_speed); + OPT_PTR(brim_width); + OPT_PTR(complete_objects); + OPT_PTR(cooling); + OPT_PTR(default_acceleration); + OPT_PTR(disable_fan_first_layers); + OPT_PTR(duplicate_distance); + OPT_PTR(extruder_clearance_height); + OPT_PTR(extruder_clearance_radius); + OPT_PTR(extruder_offset); + OPT_PTR(fan_always_on); + OPT_PTR(fan_below_layer_time); + OPT_PTR(filament_colour); + OPT_PTR(first_layer_acceleration); + OPT_PTR(first_layer_bed_temperature); + OPT_PTR(first_layer_extrusion_width); + OPT_PTR(first_layer_speed); + OPT_PTR(first_layer_temperature); + OPT_PTR(gcode_arcs); + OPT_PTR(infill_acceleration); + OPT_PTR(infill_first); + OPT_PTR(max_fan_speed); + OPT_PTR(min_fan_speed); + OPT_PTR(min_print_speed); + OPT_PTR(min_skirt_length); + OPT_PTR(notes); + OPT_PTR(nozzle_diameter); + OPT_PTR(only_retract_when_crossing_perimeters); + OPT_PTR(ooze_prevention); + OPT_PTR(output_filename_format); + OPT_PTR(perimeter_acceleration); + OPT_PTR(post_process); + OPT_PTR(resolution); + OPT_PTR(retract_before_travel); + OPT_PTR(retract_layer_change); + OPT_PTR(skirt_distance); + OPT_PTR(skirt_height); + OPT_PTR(skirts); + OPT_PTR(slowdown_below_layer_time); + OPT_PTR(spiral_vase); + OPT_PTR(standby_temperature_delta); + OPT_PTR(temperature); + OPT_PTR(threads); + OPT_PTR(vibration_limit); + OPT_PTR(wipe); + OPT_PTR(z_offset); // look in parent class ConfigOption* opt; @@ -587,8 +589,8 @@ class HostConfig : public virtual StaticPrintConfig }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { - if (opt_key == "octoprint_host") return &this->octoprint_host; - if (opt_key == "octoprint_apikey") return &this->octoprint_apikey; + OPT_PTR(octoprint_host); + OPT_PTR(octoprint_apikey); return NULL; }; From a6f4c8e56720f4491c3e529799f139c2308c7a18 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 15:02:20 +0200 Subject: [PATCH 66/83] Ported GCode::set_extruder() and OozePrevention --- lib/Slic3r/GCode.pm | 86 ------------------------------------ xs/src/libslic3r/GCode.cpp | 89 ++++++++++++++++++++++++++++++++++++++ xs/src/libslic3r/GCode.hpp | 6 +++ xs/xsp/GCode.xsp | 6 +++ 4 files changed, 101 insertions(+), 86 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 96a3170fd4..db5ce284c8 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -334,90 +334,4 @@ sub travel_to { return $gcode; } -sub set_extruder { - my ($self, $extruder_id) = @_; - - $self->placeholder_parser->set('current_extruder', $extruder_id); - return "" if !$self->writer->need_toolchange($extruder_id); - - # if we are running a single-extruder setup, just set the extruder and return nothing - if (!$self->writer->multiple_extruders) { - return $self->writer->toolchange($extruder_id); - } - - # prepend retraction on the current extruder - my $gcode = $self->retract(1); - - # append custom toolchange G-code - if (defined $self->writer->extruder && $self->config->toolchange_gcode) { - my $pp = $self->placeholder_parser->clone; - $pp->set('previous_extruder' => $self->writer->extruder->id); - $pp->set('next_extruder' => $extruder_id); - $gcode .= sprintf "%s\n", $pp->process($self->config->toolchange_gcode); - } - - # if ooze prevention is enabled, park current extruder in the nearest - # standby point and set it to the standby temperature - $gcode .= $self->ooze_prevention->pre_toolchange($self) - if $self->ooze_prevention->enable && defined $self->writer->extruder; - - # append the toolchange command - $gcode .= $self->writer->toolchange($extruder_id); - - # set the new extruder to the operating temperature - $gcode .= $self->ooze_prevention->post_toolchange($self) - if $self->ooze_prevention->enable; - - return $gcode; -} - -package Slic3r::GCode::OozePrevention; -use strict; -use warnings; - -use Slic3r::Geometry qw(scale); - -sub pre_toolchange { - my ($self, $gcodegen) = @_; - - my $gcode = ""; - - # move to the nearest standby point - if (@{$self->standby_points}) { - # get current position in print coordinates - my $pos = Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1]); - - my $standby_point = Slic3r::Pointf->new_unscale(@{$pos->nearest_point($self->standby_points)}); - # We don't call $gcodegen->travel_to() because we don't need retraction (it was already - # triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates - # of the destination point must not be transformed by origin nor current extruder offset. - $gcode .= $gcodegen->writer->travel_to_xy($standby_point, 'move to standby position'); - } - - if ($gcodegen->config->standby_temperature_delta != 0) { - my $temp = $gcodegen->has_layer && $gcodegen->layer->id == 0 - ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) - : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); - # we assume that heating is always slower than cooling, so no need to block - $gcode .= $gcodegen->writer->set_temperature($temp + $gcodegen->config->standby_temperature_delta, 0); - } - - return $gcode; -} - -sub post_toolchange { - my ($self, $gcodegen) = @_; - - my $gcode = ""; - - if ($gcodegen->config->standby_temperature_delta != 0) { - my $temp = $gcodegen->has_layer && $gcodegen->layer->id == 0 - ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) - : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); - $gcode .= $gcodegen->writer->set_temperature($temp, 1); - } - - return $gcode; -} - 1; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index abeba12e99..fe6e3089e1 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -72,6 +72,57 @@ OozePrevention::OozePrevention() { } +std::string +OozePrevention::pre_toolchange(GCode &gcodegen) +{ + std::string gcode; + + // move to the nearest standby point + if (!this->standby_points.empty()) { + // get current position in print coordinates + Pointf3 writer_pos = gcodegen.writer.get_position(); + Point pos = Point::new_scale(writer_pos.x, writer_pos.y); + + // find standby point + Point standby_point; + pos.nearest_point(this->standby_points, &standby_point); + + /* We don't call gcodegen.travel_to() because we don't need retraction (it was already + triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates + of the destination point must not be transformed by origin nor current extruder offset. */ + gcode += gcodegen.writer.travel_to_xy(Pointf::new_unscale(standby_point), + "move to standby position"); + } + + if (gcodegen.config.standby_temperature_delta.value != 0) { + // we assume that heating is always slower than cooling, so no need to block + gcode += gcodegen.writer.set_temperature + (this->_get_temp(gcodegen) + gcodegen.config.standby_temperature_delta.value, false); + } + + return gcode; +} + +std::string +OozePrevention::post_toolchange(GCode &gcodegen) +{ + std::string gcode; + + if (gcodegen.config.standby_temperature_delta.value != 0) { + gcode += gcodegen.writer.set_temperature(this->_get_temp(gcodegen), true); + } + + return gcode; +} + +int +OozePrevention::_get_temp(GCode &gcodegen) +{ + return (gcodegen.layer != NULL && gcodegen.layer->id() == 0) + ? gcodegen.config.first_layer_temperature.get_at(gcodegen.writer.extruder()->id) + : gcodegen.config.temperature.get_at(gcodegen.writer.extruder()->id); +} + #ifdef SLIC3RXS REGISTER_CLASS(OozePrevention, "GCode::OozePrevention"); #endif @@ -289,6 +340,44 @@ GCode::unretract() return gcode; } +std::string +GCode::set_extruder(unsigned int extruder_id) +{ + this->placeholder_parser->set("current_extruder", extruder_id); + if (!this->writer.need_toolchange(extruder_id)) + return ""; + + // if we are running a single-extruder setup, just set the extruder and return nothing + if (!this->writer.multiple_extruders) { + return this->writer.toolchange(extruder_id); + } + + // prepend retraction on the current extruder + std::string gcode = this->retract(true); + + // append custom toolchange G-code + if (this->writer.extruder() != NULL && !this->config.toolchange_gcode.value.empty()) { + PlaceholderParser pp = *this->placeholder_parser; + pp.set("previous_extruder", this->writer.extruder()->id); + pp.set("next_extruder", extruder_id); + gcode += pp.process(this->config.toolchange_gcode.value) + '\n'; + } + + // if ooze prevention is enabled, park current extruder in the nearest + // standby point and set it to the standby temperature + if (this->ooze_prevention.enable && this->writer.extruder() != NULL) + gcode += this->ooze_prevention.pre_toolchange(*this); + + // append the toolchange command + gcode += this->writer.toolchange(extruder_id); + + // set the new extruder to the operating temperature + if (this->ooze_prevention.enable) + gcode += this->ooze_prevention.post_toolchange(*this); + + return gcode; +} + // convert a model-space scaled point into G-code coordinates Pointf GCode::point_to_gcode(const Point &point) diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 846b822998..ee6f78c574 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -44,6 +44,11 @@ class OozePrevention { Points standby_points; OozePrevention(); + std::string pre_toolchange(GCode &gcodegen); + std::string post_toolchange(GCode &gcodegen); + + private: + int _get_temp(GCode &gcodegen); }; class Wipe { @@ -90,6 +95,7 @@ class GCode { bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); std::string retract(bool toolchange = false); std::string unretract(); + std::string set_extruder(unsigned int extruder_id); Pointf point_to_gcode(const Point &point); private: diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 5095fcfab3..3465c3c2c8 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -43,6 +43,11 @@ %code{% RETVAL = THIS->standby_points; %}; void set_standby_points(Points points) %code{% THIS->standby_points = points; %}; + + std::string pre_toolchange(GCode* gcodegen) + %code{% RETVAL = THIS->pre_toolchange(*gcodegen); %}; + std::string post_toolchange(GCode* gcodegen) + %code{% RETVAL = THIS->post_toolchange(*gcodegen); %}; }; %name{Slic3r::GCode::Wipe} class Wipe { @@ -156,6 +161,7 @@ %code{% RETVAL = THIS->needs_retraction(*travel, role); %}; std::string retract(bool toolchange = false); std::string unretract(); + std::string set_extruder(unsigned int extruder_id); Clone point_to_gcode(Point* point) %code{% RETVAL = THIS->point_to_gcode(*point); %}; }; From b025efe729f000787eecc7cd217ef65d634ebaf8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 15:12:04 +0200 Subject: [PATCH 67/83] Ported GCode::travel_to() to XS --- lib/Slic3r/GCode.pm | 38 ----------------------------------- xs/src/libslic3r/GCode.cpp | 41 ++++++++++++++++++++++++++++++++++++++ xs/src/libslic3r/GCode.hpp | 1 + xs/xsp/GCode.xsp | 2 ++ 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index db5ce284c8..14080cb17c 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -296,42 +296,4 @@ sub _extrude_path { return $gcode; } -# This method accepts $point in print coordinates. -sub travel_to { - my ($self, $point, $role, $comment) = @_; - - # Define the travel move as a line between current position and the taget point. - # This is expressed in print coordinates, so it will need to be translated by - # $self->origin in order to get G-code coordinates. - my $travel = Slic3r::Polyline->new($self->last_pos, $point); - - # check whether a straight travel move would need retraction - my $needs_retraction = $self->needs_retraction($travel, $role // EXTR_ROLE_NONE); - - # if a retraction would be needed, try to use avoid_crossing_perimeters to plan a - # multi-hop travel path inside the configuration space - if ($needs_retraction - && $self->config->avoid_crossing_perimeters - && !$self->avoid_crossing_perimeters->disable_once) { - $travel = $self->avoid_crossing_perimeters->travel_to($self, $point); - - # check again whether the new travel path still needs a retraction - $needs_retraction = $self->needs_retraction($travel, $role // EXTR_ROLE_NONE); - } - - # Re-allow avoid_crossing_perimeters for the next travel moves - $self->avoid_crossing_perimeters->set_disable_once(0); - $self->avoid_crossing_perimeters->set_use_external_mp_once(0); - - # generate G-code for the travel move - my $gcode = ""; - $gcode .= $self->retract if $needs_retraction; - - # use G1 because we rely on paths being straight (G0 may make round paths) - $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($_->b), $comment) - for @{$travel->lines}; - - return $gcode; -} - 1; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index fe6e3089e1..1c3a6dce06 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -267,6 +267,47 @@ GCode::preamble() return gcode; } +// This method accepts &point in print coordinates. +std::string +GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) +{ + /* Define the travel move as a line between current position and the taget point. + This is expressed in print coordinates, so it will need to be translated by + $self->origin in order to get G-code coordinates. */ + Polyline travel; + travel.append(this->last_pos()); + travel.append(point); + + // check whether a straight travel move would need retraction + bool needs_retraction = this->needs_retraction(travel, role); + + // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a + // multi-hop travel path inside the configuration space + if (needs_retraction + && this->config.avoid_crossing_perimeters + && !this->avoid_crossing_perimeters.disable_once) { + travel = this->avoid_crossing_perimeters.travel_to(*this, point); + + // check again whether the new travel path still needs a retraction + needs_retraction = this->needs_retraction(travel, role); + } + + // Re-allow avoid_crossing_perimeters for the next travel moves + this->avoid_crossing_perimeters.disable_once = false; + this->avoid_crossing_perimeters.use_external_mp_once = false; + + // generate G-code for the travel move + std::string gcode; + if (needs_retraction) gcode += this->retract(); + + // use G1 because we rely on paths being straight (G0 may make round paths) + Lines lines = travel.lines(); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) + gcode += this->writer.travel_to_xy(this->point_to_gcode(line->b), comment); + + return gcode; +} + bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) { diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index ee6f78c574..d71069c92f 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -92,6 +92,7 @@ class GCode { void apply_print_config(const PrintConfig &print_config); void set_origin(const Pointf &pointf); std::string preamble(); + std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); std::string retract(bool toolchange = false); std::string unretract(); diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 3465c3c2c8..7595e829ae 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -157,6 +157,8 @@ void set_origin(Pointf* pointf) %code{% THIS->set_origin(*pointf); %}; std::string preamble(); + std::string travel_to(Point* point, ExtrusionRole role, std::string comment) + %code{% RETVAL = THIS->travel_to(*point, role, comment); %}; bool needs_retraction(Polyline* travel, ExtrusionRole role = erNone) %code{% RETVAL = THIS->needs_retraction(*travel, role); %}; std::string retract(bool toolchange = false); From fbd640fdc5579f7755867b7979e01d9c98c89941 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 18:57:40 +0200 Subject: [PATCH 68/83] Ported GCode::extrude_path() to XS (speed boost!) --- lib/Slic3r/GCode.pm | 113 +--------------------------------- lib/Slic3r/Print/GCode.pm | 3 +- xs/src/libslic3r/GCode.cpp | 116 ++++++++++++++++++++++++++++++++++- xs/src/libslic3r/GCode.hpp | 2 + xs/src/libslic3r/libslic3r.h | 2 + xs/xsp/GCode.xsp | 4 ++ 6 files changed, 126 insertions(+), 114 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 14080cb17c..2b50f8f9ce 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -151,7 +151,7 @@ sub extrude_loop { } # extrude along the path - my $gcode = join '', map $self->_extrude_path($_, $description, $speed), @paths; + my $gcode = join '', map $self->_extrude_path($_, $description // "", $speed // -1), @paths; # reset acceleration $gcode .= $self->writer->set_acceleration($self->config->default_acceleration); @@ -185,115 +185,4 @@ sub extrude_loop { return $gcode; } -sub extrude_path { - my ($self, $path, $description, $speed) = @_; - - my $gcode = $self->_extrude_path($path, $description, $speed); - - # reset acceleration - $gcode .= $self->writer->set_acceleration($self->config->default_acceleration); - - return $gcode; -} - -sub _extrude_path { - my ($self, $path, $description, $speed) = @_; - - $path->simplify(&Slic3r::SCALED_RESOLUTION); - - # go to first point of extrusion path - my $gcode = ""; - { - my $first_point = $path->first_point; - $gcode .= $self->travel_to($first_point, $path->role, "move to first $description point") - if !$self->last_pos_defined || !$self->last_pos->coincides_with($first_point); - } - - # compensate retraction - $gcode .= $self->unretract; - - # adjust acceleration - { - my $acceleration; - if ($self->config->first_layer_acceleration && $self->first_layer) { - $acceleration = $self->config->first_layer_acceleration; - } elsif ($self->config->perimeter_acceleration && $path->is_perimeter) { - $acceleration = $self->config->perimeter_acceleration; - } elsif ($self->config->bridge_acceleration && $path->is_bridge) { - $acceleration = $self->config->bridge_acceleration; - } elsif ($self->config->infill_acceleration && $path->is_infill) { - $acceleration = $self->config->infill_acceleration; - } else { - $acceleration = $self->config->default_acceleration; - } - $gcode .= $self->writer->set_acceleration($acceleration); - } - - # calculate extrusion length per distance unit - my $e_per_mm = $self->writer->extruder->e_per_mm3 * $path->mm3_per_mm; - $e_per_mm = 0 if !$self->writer->extrusion_axis; - - # set speed - $speed //= -1; - if ($speed == -1) { - if ($path->role == EXTR_ROLE_PERIMETER) { - $speed = $self->config->get_abs_value('perimeter_speed'); - } elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) { - $speed = $self->config->get_abs_value('external_perimeter_speed'); - } elsif ($path->role == EXTR_ROLE_OVERHANG_PERIMETER || $path->role == EXTR_ROLE_BRIDGE) { - $speed = $self->config->get_abs_value('bridge_speed'); - } elsif ($path->role == EXTR_ROLE_FILL) { - $speed = $self->config->get_abs_value('infill_speed'); - } elsif ($path->role == EXTR_ROLE_SOLIDFILL) { - $speed = $self->config->get_abs_value('solid_infill_speed'); - } elsif ($path->role == EXTR_ROLE_TOPSOLIDFILL) { - $speed = $self->config->get_abs_value('top_solid_infill_speed'); - } elsif ($path->role == EXTR_ROLE_GAPFILL) { - $speed = $self->config->get_abs_value('gap_fill_speed'); - } else { - die "Invalid speed"; - } - } - if ($self->first_layer) { - $speed = $self->config->get_abs_value_over('first_layer_speed', $speed); - } - if ($self->volumetric_speed != 0) { - $speed ||= $self->volumetric_speed / $path->mm3_per_mm; - } - if ($self->config->max_volumetric_speed > 0) { - # Cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - $speed = min( - $speed, - $self->config->max_volumetric_speed / $path->mm3_per_mm, - ); - } - my $F = $speed * 60; # convert mm/sec to mm/min - - # extrude arc or line - $gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge && $self->enable_cooling_markers; - my $path_length = unscale $path->length; - { - my $extruder_offset = $self->config->get_at('extruder_offset', $self->writer->extruder->id); - $gcode .= $path->gcode($self->writer->extruder, $e_per_mm, $F, - $self->origin->x - $extruder_offset->x, - $self->origin->y - $extruder_offset->y, #- - $self->writer->extrusion_axis, - $self->config->gcode_comments ? " ; $description" : ""); - - if ($self->wipe->enable) { - $self->wipe->set_path($path->polyline->clone); - $self->wipe->path->reverse; - } - } - $gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge && $self->enable_cooling_markers; - $self->set_last_pos($path->last_point); - - if ($self->config->cooling) { - my $path_time = $path_length / $F * 60; - $self->set_elapsed_time($self->elapsed_time + $path_time); - } - - return $gcode; -} - 1; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 4323e0965b..c5d0f5facd 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -16,6 +16,7 @@ has '_second_layer_things_done' => (is => 'rw'); has '_last_obj_copy' => (is => 'rw'); use List::Util qw(first sum min max); +use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull); use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset); @@ -251,7 +252,7 @@ sub export { print $fh $gcodegen->retract; print $fh $gcodegen->travel_to( Slic3r::Point->new(0,0), - undef, + EXTR_ROLE_NONE, 'move to origin position for next object', ); $gcodegen->set_enable_cooling_markers(1); diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 1c3a6dce06..5b5d26acdc 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -1,5 +1,6 @@ #include "GCode.hpp" #include "ExtrusionEntity.hpp" +#include namespace Slic3r { @@ -267,13 +268,126 @@ GCode::preamble() return gcode; } +std::string +GCode::extrude_path(const ExtrusionPath &path, std::string description, double speed) +{ + std::string gcode = this->_extrude_path(path, description, speed); + + // reset acceleration + gcode += this->writer.set_acceleration(this->config.default_acceleration.value); + + return gcode; +} + +std::string +GCode::_extrude_path(ExtrusionPath path, std::string description, double speed) +{ + path.simplify(SCALED_RESOLUTION); + + std::string gcode; + + // go to first point of extrusion path + if (!this->_last_pos_defined || !this->_last_pos.coincides_with(path.first_point())) { + gcode += this->travel_to( + path.first_point(), + path.role, + "move to first " + description + " point" + ); + } + + // compensate retraction + gcode += this->unretract(); + + // adjust acceleration + { + double acceleration; + if (this->config.first_layer_acceleration.value > 0 && this->first_layer) { + acceleration = this->config.first_layer_acceleration.value; + } else if (this->config.perimeter_acceleration.value > 0 && path.is_perimeter()) { + acceleration = this->config.perimeter_acceleration.value; + } else if (this->config.bridge_acceleration.value > 0 && path.is_bridge()) { + acceleration = this->config.bridge_acceleration.value; + } else if (this->config.infill_acceleration.value > 0 && path.is_infill()) { + acceleration = this->config.infill_acceleration.value; + } else { + acceleration = this->config.default_acceleration.value; + } + gcode += this->writer.set_acceleration(acceleration); + } + + // calculate extrusion length per distance unit + double e_per_mm = this->writer.extruder()->e_per_mm3 * path.mm3_per_mm; + if (this->writer.extrusion_axis().empty()) e_per_mm = 0; + + // set speed + if (speed == -1) { + if (path.role == erPerimeter) { + speed = this->config.get_abs_value("perimeter_speed"); + } else if (path.role == erExternalPerimeter) { + speed = this->config.get_abs_value("external_perimeter_speed"); + } else if (path.role == erOverhangPerimeter || path.role == erBridgeInfill) { + speed = this->config.get_abs_value("bridge_speed"); + } else if (path.role == erInternalInfill) { + speed = this->config.get_abs_value("infill_speed"); + } else if (path.role == erSolidInfill) { + speed = this->config.get_abs_value("solid_infill_speed"); + } else if (path.role == erTopSolidInfill) { + speed = this->config.get_abs_value("top_solid_infill_speed"); + } else if (path.role == erGapFill) { + speed = this->config.get_abs_value("gap_fill_speed"); + } else { + CONFESS("Invalid speed"); + } + } + if (this->first_layer) { + speed = this->config.get_abs_value("first_layer_speed", speed); + } + if (this->volumetric_speed != 0 && speed == 0) { + speed = this->volumetric_speed / path.mm3_per_mm; + } + if (this->config.max_volumetric_speed.value > 0) { + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + speed = std::min( + speed, + this->config.max_volumetric_speed.value / path.mm3_per_mm + ); + } + double F = speed * 60; // convert mm/sec to mm/min + + // extrude arc or line + if (path.is_bridge() && this->enable_cooling_markers) + gcode += ";_BRIDGE_FAN_START\n"; + double path_length = unscale(path.length()); + { + Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); + gcode += path.gcode(this->writer.extruder(), e_per_mm, F, + this->origin.x - extruder_offset.x, + this->origin.y - extruder_offset.y, + this->writer.extrusion_axis(), + this->config.gcode_comments ? (" ; " + description) : ""); + + if (this->wipe.enable) { + this->wipe.path = path.polyline; + this->wipe.path.reverse(); + } + } + if (path.is_bridge() && this->enable_cooling_markers) + gcode += ";_BRIDGE_FAN_END\n"; + this->set_last_pos(path.last_point()); + + if (this->config.cooling) + this->elapsed_time += path_length / F * 60; + + return gcode; +} + // This method accepts &point in print coordinates. std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) { /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by - $self->origin in order to get G-code coordinates. */ + this->origin in order to get G-code coordinates. */ Polyline travel; travel.append(this->last_pos()); travel.append(point); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index d71069c92f..0af91b661f 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -92,6 +92,8 @@ class GCode { void apply_print_config(const PrintConfig &print_config); void set_origin(const Pointf &pointf); std::string preamble(); + std::string extrude_path(const ExtrusionPath &path, std::string description = "", double speed = -1); + std::string _extrude_path(ExtrusionPath path, std::string description = "", double speed = -1); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); std::string retract(bool toolchange = false); diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 965174959b..470dbe0152 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -10,6 +10,8 @@ #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 +#define RESOLUTION 0.0125 +#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) #define PI 3.141592653589793238 #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 7595e829ae..1aa1dfdad1 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -157,6 +157,10 @@ void set_origin(Pointf* pointf) %code{% THIS->set_origin(*pointf); %}; std::string preamble(); + std::string extrude_path(ExtrusionPath* path, std::string description = "", double speed = -1) + %code{% RETVAL = THIS->extrude_path(*path, description, speed); %}; + std::string _extrude_path(ExtrusionPath* path, std::string description = "", double speed = -1) + %code{% RETVAL = THIS->_extrude_path(*path, description, speed); %}; std::string travel_to(Point* point, ExtrusionRole role, std::string comment) %code{% RETVAL = THIS->travel_to(*point, role, comment); %}; bool needs_retraction(Polyline* travel, ExtrusionRole role = erNone) From 9a17efc48093d777ca983e3af59f983b8b0389bd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 19:14:55 +0200 Subject: [PATCH 69/83] Use GCodeWriter for path segments (refactoring) --- xs/src/libslic3r/ExtrusionEntity.cpp | 51 ---------------------------- xs/src/libslic3r/ExtrusionEntity.hpp | 3 -- xs/src/libslic3r/GCode.cpp | 29 ++++++++++------ xs/xsp/ExtrusionPath.xsp | 3 -- 4 files changed, 18 insertions(+), 68 deletions(-) diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index f6c3b80a8a..846269185e 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -114,57 +114,6 @@ ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCo REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); #endif -std::string -ExtrusionPath::gcode(Extruder* extruder, double e, double F, - double xofs, double yofs, std::string extrusion_axis, - std::string gcode_line_suffix) const -{ - dSP; - - std::stringstream stream; - stream.setf(std::ios::fixed); - - double local_F = F; - - Lines lines = this->polyline.lines(); - for (Lines::const_iterator line_it = lines.begin(); - line_it != lines.end(); ++line_it) - { - const double line_length = line_it->length() * SCALING_FACTOR; - - // calculate extrusion length for this line - double E = 0; - if (e > 0) { - extruder->extrude(e * line_length); - E = extruder->E; - } - - // compose G-code line - - Point point = line_it->b; - const double x = point.x * SCALING_FACTOR + xofs; - const double y = point.y * SCALING_FACTOR + yofs; - stream.precision(3); - stream << "G1 X" << x << " Y" << y; - - if (E != 0) { - stream.precision(5); - stream << " " << extrusion_axis << E; - } - - if (local_F != 0) { - stream.precision(3); - stream << " F" << local_F; - local_F = 0; - } - - stream << gcode_line_suffix; - stream << "\n"; - } - - return stream.str(); -} - Polygons ExtrusionPath::grow() const { diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 9be5b4125a..bcb50155a9 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -79,9 +79,6 @@ class ExtrusionPath : public ExtrusionEntity bool is_infill() const; bool is_solid_infill() const; bool is_bridge() const; - std::string gcode(Extruder* extruder, double e, double F, - double xofs, double yofs, std::string extrusion_axis, - std::string gcode_line_suffix) const; Polygons grow() const; double min_mm3_per_mm() const { return this->mm3_per_mm; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 5b5d26acdc..60145a6ab1 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -357,22 +357,29 @@ GCode::_extrude_path(ExtrusionPath path, std::string description, double speed) // extrude arc or line if (path.is_bridge() && this->enable_cooling_markers) gcode += ";_BRIDGE_FAN_START\n"; - double path_length = unscale(path.length()); + gcode += this->writer.set_speed(F); + double path_length = 0; { - Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); - gcode += path.gcode(this->writer.extruder(), e_per_mm, F, - this->origin.x - extruder_offset.x, - this->origin.y - extruder_offset.y, - this->writer.extrusion_axis(), - this->config.gcode_comments ? (" ; " + description) : ""); - - if (this->wipe.enable) { - this->wipe.path = path.polyline; - this->wipe.path.reverse(); + std::string comment = this->config.gcode_comments ? (" ; " + description) : ""; + Lines lines = path.polyline.lines(); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { + const double line_length = line->length() * SCALING_FACTOR; + path_length += line_length; + + gcode += this->writer.extrude_to_xy( + this->point_to_gcode(line->b), + e_per_mm * line_length, + comment + ); } } + if (this->wipe.enable) { + this->wipe.path = path.polyline; + this->wipe.path.reverse(); + } if (path.is_bridge() && this->enable_cooling_markers) gcode += ";_BRIDGE_FAN_END\n"; + this->set_last_pos(path.last_point()); if (this->config.cooling) diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp index 4918628404..ca1b465cb0 100644 --- a/xs/xsp/ExtrusionPath.xsp +++ b/xs/xsp/ExtrusionPath.xsp @@ -26,9 +26,6 @@ bool is_infill(); bool is_solid_infill(); bool is_bridge(); - std::string gcode(Extruder* extruder, double e, double F, - double xofs, double yofs, std::string extrusion_axis, - std::string gcode_line_suffix); Polygons grow(); %{ From 0ad4296aaffef41d14f074a1c2689fca9a43e565 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 19:33:08 +0200 Subject: [PATCH 70/83] Ported GCode::set_extruders() and GCode::change_layer() to XS --- lib/Slic3r/GCode.pm | 45 -------------------------------- xs/src/libslic3r/GCode.cpp | 53 +++++++++++++++++++++++++++++++++++++- xs/src/libslic3r/GCode.hpp | 4 ++- xs/src/perlglue.hpp | 1 + xs/xsp/GCode.xsp | 3 +++ 5 files changed, 59 insertions(+), 47 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 2b50f8f9ce..9e344fbd6e 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -8,51 +8,6 @@ use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(epsilon scale unscale PI X Y B); use Slic3r::Geometry::Clipper qw(union_ex); -sub set_extruders { - my ($self, $extruder_ids) = @_; - - $self->writer->set_extruders($extruder_ids); - - # enable wipe path generation if any extruder has wipe enabled - $self->wipe->set_enable(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids); -} - -sub change_layer { - my ($self, $layer) = @_; - - { - my $l = $layer->isa('Slic3r::Layer::Support') - ? $layer->as_layer - : $layer; - $self->set_layer($l); - } - $self->set_layer_index($self->layer_index + 1); - $self->set_first_layer($layer->id == 0); - - # avoid computing islands and overhangs if they're not needed - if ($self->config->avoid_crossing_perimeters) { - $self->avoid_crossing_perimeters->init_layer_mp( - union_ex([ map @$_, @{$layer->slices} ], 1), - ); - } - - my $gcode = ""; - if ($self->layer_count > 0) { - $gcode .= $self->writer->update_progress($self->layer_index, $self->layer_count); - } - - my $z = $layer->print_z + $self->config->z_offset; # in unscaled coordinates - if ($self->config->get_at('retract_layer_change', $self->writer->extruder->id) && $self->writer->will_move_z($z)) { - $gcode .= $self->retract; - } - $gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer_index . ')'); - - # forget last wiping path as wiping after raising Z is pointless - $self->wipe->reset_path; - - return $gcode; -} - sub extrude { my $self = shift; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 60145a6ab1..f153b96f7d 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -240,6 +240,22 @@ GCode::apply_print_config(const PrintConfig &print_config) this->config.apply(print_config); } +void +GCode::set_extruders(const std::vector &extruder_ids) +{ + this->writer.set_extruders(extruder_ids); + + // enable wipe path generation if any extruder has wipe enabled + this->wipe.enable = false; + for (std::vector::const_iterator it = extruder_ids.begin(); + it != extruder_ids.end(); ++it) { + if (this->config.wipe.get_at(*it)) { + this->wipe.enable = true; + break; + } + } +} + void GCode::set_origin(const Pointf &pointf) { @@ -268,6 +284,41 @@ GCode::preamble() return gcode; } +std::string +GCode::change_layer(const Layer &layer) +{ + this->layer = &layer; + this->layer_index++; + this->first_layer = (layer.id() == 0); + + // avoid computing islands and overhangs if they're not needed + if (this->config.avoid_crossing_perimeters) { + ExPolygons islands; + union_(layer.slices, &islands, true); + this->avoid_crossing_perimeters.init_layer_mp(islands); + } + + std::string gcode; + if (this->layer_count > 0) { + gcode += this->writer.update_progress(this->layer_index, this->layer_count); + } + + coordf_t z = layer.print_z + this->config.z_offset.value; // in unscaled coordinates + if (EXTRUDER_CONFIG(retract_layer_change) && this->writer.will_move_z(z)) { + gcode += this->retract(); + } + { + std::ostringstream comment; + comment << "move to next layer (" << this->layer_index << ")"; + gcode += this->writer.travel_to_z(z, comment.str()); + } + + // forget last wiping path as wiping after raising Z is pointless + this->wipe.reset_path(); + + return gcode; +} + std::string GCode::extrude_path(const ExtrusionPath &path, std::string description, double speed) { @@ -438,7 +489,7 @@ GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) } if (role == erSupportMaterial) { - SupportLayer* support_layer = dynamic_cast(this->layer); + const SupportLayer* support_layer = dynamic_cast(this->layer); if (support_layer != NULL && support_layer->support_islands.contains(travel)) { // skip retraction if this is a travel move inside a support material island return false; diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 0af91b661f..de391e491a 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -79,7 +79,7 @@ class GCode { bool enable_cooling_markers; size_t layer_count; int layer_index; // just a counter - Layer* layer; + const Layer* layer; std::map _seam_position; bool first_layer; // this flag triggers first layer speeds unsigned int elapsed_time; // seconds @@ -90,8 +90,10 @@ class GCode { void set_last_pos(const Point &pos); bool last_pos_defined() const; void apply_print_config(const PrintConfig &print_config); + void set_extruders(const std::vector &extruder_ids); void set_origin(const Pointf &pointf); std::string preamble(); + std::string change_layer(const Layer &layer); std::string extrude_path(const ExtrusionPath &path, std::string description = "", double speed = -1); std::string _extrude_path(ExtrusionPath path, std::string description = "", double speed = -1); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); diff --git a/xs/src/perlglue.hpp b/xs/src/perlglue.hpp index 84c9a7ae94..b10cbcf119 100644 --- a/xs/src/perlglue.hpp +++ b/xs/src/perlglue.hpp @@ -44,6 +44,7 @@ class Ref { public: Ref() : val(NULL) {} Ref(T* t) : val(t) {} + Ref(const T* t) : val(const_cast(t)) {} operator T*() const { return val; } static const char* CLASS() { return ClassTraits::name_ref; } }; diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 1aa1dfdad1..63cccfc225 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -154,9 +154,12 @@ void apply_print_config(PrintConfig* print_config) %code{% THIS->apply_print_config(*print_config); %}; + void set_extruders(std::vector extruder_ids); void set_origin(Pointf* pointf) %code{% THIS->set_origin(*pointf); %}; std::string preamble(); + std::string change_layer(Layer* layer) + %code{% RETVAL = THIS->change_layer(*layer); %}; std::string extrude_path(ExtrusionPath* path, std::string description = "", double speed = -1) %code{% RETVAL = THIS->extrude_path(*path, description, speed); %}; std::string _extrude_path(ExtrusionPath* path, std::string description = "", double speed = -1) From 3e739b87da9f49b61a6dfc9aa99a06ba28ddfe39 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 20:24:16 +0200 Subject: [PATCH 71/83] Finished porting Slic3r::GCode to XS (speed boost!) --- lib/Slic3r.pm | 2 - lib/Slic3r/GCode.pm | 143 --------------------- lib/Slic3r/GCode/PressureRegulator.pm | 2 +- lib/Slic3r/Print/GCode.pm | 8 +- xs/src/libslic3r/GCode.cpp | 171 +++++++++++++++++++++++++- xs/src/libslic3r/GCode.hpp | 8 +- xs/src/libslic3r/Layer.cpp | 6 + xs/src/libslic3r/Layer.hpp | 1 + xs/src/libslic3r/libslic3r.h | 2 + xs/xsp/GCode.xsp | 29 +++-- xs/xsp/Layer.xsp | 3 + 11 files changed, 208 insertions(+), 167 deletions(-) delete mode 100644 lib/Slic3r/GCode.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 54d1e524f5..e052240a16 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -44,7 +44,6 @@ use Slic3r::Flow; use Slic3r::Format::AMF; use Slic3r::Format::OBJ; use Slic3r::Format::STL; -use Slic3r::GCode; use Slic3r::GCode::ArcFitting; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; @@ -77,7 +76,6 @@ use Unicode::Normalize; use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; -use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.3; use constant EXTERNAL_INFILL_MARGIN => 3; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm deleted file mode 100644 index 9e344fbd6e..0000000000 --- a/lib/Slic3r/GCode.pm +++ /dev/null @@ -1,143 +0,0 @@ -package Slic3r::GCode; -use strict; -use warnings; - -use List::Util qw(min max first); -use Slic3r::ExtrusionLoop ':roles'; -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(epsilon scale unscale PI X Y B); -use Slic3r::Geometry::Clipper qw(union_ex); - -sub extrude { - my $self = shift; - - $_[0]->isa('Slic3r::ExtrusionLoop') - ? $self->extrude_loop(@_) - : $self->extrude_path(@_); -} - -sub extrude_loop { - my ($self, $loop, $description, $speed) = @_; - - # make a copy; don't modify the orientation of the original loop object otherwise - # next copies (if any) would not detect the correct orientation - $loop = $loop->clone; - - # extrude all loops ccw - my $was_clockwise = $loop->make_counter_clockwise; - - # find the point of the loop that is closest to the current extruder position - # or randomize if requested - my $last_pos = $self->last_pos; - if ($self->config->spiral_vase) { - $loop->split_at($last_pos); - } elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') { - # simplify polygon in order to skip false positives in concave/convex detection - # ($loop is always ccw as $polygon->simplify only works on ccw polygons) - my $polygon = $loop->polygon; - my @simplified = @{$polygon->simplify(scale $self->config->get_at('nozzle_diameter', $self->writer->extruder->id)/2)}; - - # restore original winding order so that concave and convex detection always happens - # on the right/outer side of the polygon - if ($was_clockwise) { - $_->reverse for @simplified; - } - - # concave vertices have priority - my @candidates = map @{$_->concave_points(PI*4/3)}, @simplified; - - # if no concave points were found, look for convex vertices - @candidates = map @{$_->convex_points(PI*2/3)}, @simplified if !@candidates; - - # retrieve the last start position for this object - if ($self->has_layer) { - if ($self->_has_seam_position($self->layer->object)) { - $last_pos = $self->_seam_position($self->layer->object); - } - } - - my $point; - if ($self->config->seam_position eq 'nearest') { - @candidates = @$polygon if !@candidates; - $point = $last_pos->nearest_point(\@candidates); - if (!$loop->split_at_vertex($point)) { - # On 32-bit Linux, Clipper will change some point coordinates by 1 unit - # while performing simplify_polygons(), thus split_at_vertex() won't - # find them anymore. - $loop->split_at($point); - } - } elsif (@candidates) { - my @non_overhang = grep !$loop->has_overhang_point($_), @candidates; - @candidates = @non_overhang if @non_overhang; - $point = $last_pos->nearest_point(\@candidates); - if (!$loop->split_at_vertex($point)) { - $loop->split_at($point); - } - } else { - $point = $last_pos->projection_onto_polygon($polygon); - $loop->split_at($point); - } - $self->_set_seam_position($self->layer->object, $point) - if $self->has_layer; - } elsif ($self->config->seam_position eq 'random') { - if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { - my $polygon = $loop->polygon; - my $centroid = $polygon->centroid; - $last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y); #)) - $last_pos->rotate(rand(2*PI), $centroid); - } - $loop->split_at($last_pos); - } - - # clip the path to avoid the extruder to get exactly on the first point of the loop; - # if polyline was shorter than the clipping distance we'd get a null polyline, so - # we discard it in that case - my $clip_length = $self->enable_loop_clipping - ? scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER - : 0; - - # get paths - my @paths = @{$loop->clip_end($clip_length)}; - return '' if !@paths; - - # apply the small perimeter speed - if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) { - $speed //= $self->config->get_abs_value('small_perimeter_speed'); - } - - # extrude along the path - my $gcode = join '', map $self->_extrude_path($_, $description // "", $speed // -1), @paths; - - # reset acceleration - $gcode .= $self->writer->set_acceleration($self->config->default_acceleration); - - $self->wipe->set_path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path - - # make a little move inwards before leaving loop - if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && $self->has_layer && $self->config->perimeters > 1) { - my $last_path_polyline = $paths[-1]->polyline; - # detect angle between last and first segment - # the side depends on the original winding order of the polygon (left for contours, right for holes) - my @points = ($paths[0][1], $paths[-1][-2]); - @points = reverse @points if $was_clockwise; - my $angle = $paths[0]->first_point->ccw_angle(@points) / 3; - - # turn left if contour, turn right if hole - $angle *= -1 if $was_clockwise; - - # create the destination point along the first segment and rotate it - # we make sure we don't exceed the segment length because we don't know - # the rotation of the second segment so we might cross the object boundary - my $first_segment = Slic3r::Line->new(@{$paths[0]->polyline}[0,1]); - my $distance = min(scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)), $first_segment->length); - my $point = $first_segment->point_at($distance); - $point->rotate($angle, $first_segment->a); - - # generate the travel move - $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), "move inwards before travel"); - } - - return $gcode; -} - -1; diff --git a/lib/Slic3r/GCode/PressureRegulator.pm b/lib/Slic3r/GCode/PressureRegulator.pm index 8d1cd65257..6074b94868 100644 --- a/lib/Slic3r/GCode/PressureRegulator.pm +++ b/lib/Slic3r/GCode/PressureRegulator.pm @@ -43,7 +43,7 @@ sub process { my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY}; # Then calculate absolute flow rate (mm/sec of feedstock) - my $flow_rate = $rel_flow_rate * $args->{F} / 60; + my $flow_rate = $rel_flow_rate * $F / 60; # And finally calculate advance by using the user-configured K factor. my $new_advance = $self->config->pressure_advance * ($flow_rate**2); diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index c5d0f5facd..db900d6fc4 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -384,7 +384,7 @@ sub process_layer { $pp->set('layer_z' => $layer->print_z); $gcode .= $pp->process($self->print->config->before_layer_gcode) . "\n"; } - $gcode .= $self->_gcodegen->change_layer($layer); # this will increase $self->_gcodegen->layer_index + $gcode .= $self->_gcodegen->change_layer($layer->as_layer); # this will increase $self->_gcodegen->layer_index if ($self->print->config->layer_gcode) { my $pp = $self->_gcodegen->placeholder_parser->clone; $pp->set('layer_num' => $self->_gcodegen->layer_index); @@ -592,7 +592,7 @@ sub _extrude_perimeters { my $gcode = ""; foreach my $region_id (sort keys %$entities_by_region) { $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config); - $gcode .= $self->_gcodegen->extrude($_, 'perimeter') + $gcode .= $self->_gcodegen->extrude($_, 'perimeter', -1) for @{ $entities_by_region->{$region_id} }; } return $gcode; @@ -608,10 +608,10 @@ sub _extrude_infill { my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} }); for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) { if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { - $gcode .= $self->_gcodegen->extrude($_, 'infill') + $gcode .= $self->_gcodegen->extrude($_, 'infill', -1) for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)}; } else { - $gcode .= $self->_gcodegen->extrude($fill, 'infill') ; + $gcode .= $self->_gcodegen->extrude($fill, 'infill', -1) ; } } } diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index f153b96f7d..51093667af 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -1,6 +1,7 @@ #include "GCode.hpp" #include "ExtrusionEntity.hpp" #include +#include namespace Slic3r { @@ -320,9 +321,173 @@ GCode::change_layer(const Layer &layer) } std::string -GCode::extrude_path(const ExtrusionPath &path, std::string description, double speed) +GCode::extrude(ExtrusionLoop loop, std::string description, double speed) { - std::string gcode = this->_extrude_path(path, description, speed); + // get a copy; don't modify the orientation of the original loop object otherwise + // next copies (if any) would not detect the correct orientation + + // extrude all loops ccw + bool was_clockwise = loop.make_counter_clockwise(); + + // find the point of the loop that is closest to the current extruder position + // or randomize if requested + Point last_pos = this->last_pos(); + if (this->config.spiral_vase) { + loop.split_at(last_pos); + } else if (this->config.seam_position == spNearest || this->config.seam_position == spAligned) { + Polygon polygon = loop.polygon(); + + // simplify polygon in order to skip false positives in concave/convex detection + // (loop is always ccw as polygon.simplify() only works on ccw polygons) + Polygons simplified = polygon.simplify(scale_(EXTRUDER_CONFIG(nozzle_diameter))/2); + + // restore original winding order so that concave and convex detection always happens + // on the right/outer side of the polygon + if (was_clockwise) { + for (Polygons::iterator p = simplified.begin(); p != simplified.end(); ++p) + p->reverse(); + } + + // concave vertices have priority + Points candidates; + for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) { + Points concave = p->concave_points(PI*4/3); + candidates.insert(candidates.end(), concave.begin(), concave.end()); + } + + // if no concave points were found, look for convex vertices + if (candidates.empty()) { + for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) { + Points convex = p->convex_points(PI*2/3); + candidates.insert(candidates.end(), convex.begin(), convex.end()); + } + } + + // retrieve the last start position for this object + if (this->layer != NULL && this->_seam_position.count(this->layer->object()) > 0) { + last_pos = this->_seam_position[this->layer->object()]; + } + + Point point; + if (this->config.seam_position == spNearest) { + if (candidates.empty()) candidates = polygon.points; + last_pos.nearest_point(candidates, &point); + + // On 32-bit Linux, Clipper will change some point coordinates by 1 unit + // while performing simplify_polygons(), thus split_at_vertex() won't + // find them anymore. + if (!loop.split_at_vertex(point)) loop.split_at(point); + } else if (!candidates.empty()) { + Points non_overhang; + for (Points::const_iterator p = candidates.begin(); p != candidates.end(); ++p) { + if (!loop.has_overhang_point(*p)) + non_overhang.push_back(*p); + } + + if (!non_overhang.empty()) + candidates = non_overhang; + + last_pos.nearest_point(candidates, &point); + if (!loop.split_at_vertex(point)) loop.split_at(point); // see note above + } else { + point = last_pos.projection_onto(polygon); + loop.split_at(point); + } + if (this->layer != NULL) + this->_seam_position[this->layer->object()] = point; + } else if (this->config.seam_position == spRandom) { + if (loop.role == elrContourInternalPerimeter) { + Polygon polygon = loop.polygon(); + Point centroid = polygon.centroid(); + last_pos = Point(polygon.bounding_box().max.x, centroid.y); + last_pos.rotate(rand() % 2*PI, centroid); + } + loop.split_at(last_pos); + } + + // clip the path to avoid the extruder to get exactly on the first point of the loop; + // if polyline was shorter than the clipping distance we'd get a null polyline, so + // we discard it in that case + double clip_length = this->enable_loop_clipping + ? scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER + : 0; + + // get paths + ExtrusionPaths paths; + loop.clip_end(clip_length, &paths); + if (paths.empty()) return ""; + + // apply the small perimeter speed + if (paths.front().is_perimeter() && loop.length() <= SMALL_PERIMETER_LENGTH) { + if (speed == -1) speed = this->config.get_abs_value("small_perimeter_speed"); + } + + // extrude along the path + std::string gcode; + for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path) + gcode += this->_extrude(*path, description, speed); + + // reset acceleration + gcode += this->writer.set_acceleration(this->config.default_acceleration.value); + + if (this->wipe.enable) + this->wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path + + // make a little move inwards before leaving loop + if (paths.back().role == erExternalPerimeter && this->layer != NULL && this->config.perimeters > 1) { + Polyline &last_path_polyline = paths.back().polyline; + // detect angle between last and first segment + // the side depends on the original winding order of the polygon (left for contours, right for holes) + Point a = paths.front().polyline.points[1]; // second point + Point b = *(paths.back().polyline.points.end()-3); // second to last point + if (was_clockwise) { + // swap points + Point c = a; a = b; b = c; + } + + double angle = paths.front().first_point().ccw_angle(a, b) / 3; + + // turn left if contour, turn right if hole + if (was_clockwise) angle *= -1; + + // create the destination point along the first segment and rotate it + // we make sure we don't exceed the segment length because we don't know + // the rotation of the second segment so we might cross the object boundary + Line first_segment( + paths.front().polyline.points[0], + paths.front().polyline.points[1] + ); + double distance = std::min( + scale_(EXTRUDER_CONFIG(nozzle_diameter)), + first_segment.length() + ); + Point point = first_segment.point_at(distance); + point.rotate(angle, first_segment.a); + + // generate the travel move + gcode += this->writer.travel_to_xy(this->point_to_gcode(point), "move inwards before travel"); + } + + return gcode; +} + +std::string +GCode::extrude(const ExtrusionEntity &entity, std::string description, double speed) +{ + if (const ExtrusionPath* path = dynamic_cast(&entity)) { + return this->extrude(*path, description, speed); + } else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) { + return this->extrude(*loop, description, speed); + } else { + CONFESS("Invalid argument supplied to extrude()"); + return ""; + } +} + +std::string +GCode::extrude(const ExtrusionPath &path, std::string description, double speed) +{ + std::string gcode = this->_extrude(path, description, speed); // reset acceleration gcode += this->writer.set_acceleration(this->config.default_acceleration.value); @@ -331,7 +496,7 @@ GCode::extrude_path(const ExtrusionPath &path, std::string description, double s } std::string -GCode::_extrude_path(ExtrusionPath path, std::string description, double speed) +GCode::_extrude(ExtrusionPath path, std::string description, double speed) { path.simplify(SCALED_RESOLUTION); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index de391e491a..f477c779e2 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -80,7 +80,7 @@ class GCode { size_t layer_count; int layer_index; // just a counter const Layer* layer; - std::map _seam_position; + std::map _seam_position; bool first_layer; // this flag triggers first layer speeds unsigned int elapsed_time; // seconds double volumetric_speed; @@ -94,8 +94,9 @@ class GCode { void set_origin(const Pointf &pointf); std::string preamble(); std::string change_layer(const Layer &layer); - std::string extrude_path(const ExtrusionPath &path, std::string description = "", double speed = -1); - std::string _extrude_path(ExtrusionPath path, std::string description = "", double speed = -1); + std::string extrude(const ExtrusionEntity &entity, std::string description = "", double speed = -1); + std::string extrude(ExtrusionLoop loop, std::string description = "", double speed = -1); + std::string extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); std::string retract(bool toolchange = false); @@ -106,6 +107,7 @@ class GCode { private: Point _last_pos; bool _last_pos_defined; + std::string _extrude(ExtrusionPath path, std::string description = "", double speed = -1); }; } diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 250572fb7d..4d4c288052 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -53,6 +53,12 @@ Layer::object() return this->_object; } +const PrintObject* +Layer::object() const +{ + return this->_object; +} + size_t Layer::region_count() diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index d34abec364..85f4844cb5 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -76,6 +76,7 @@ class Layer { size_t id() const; void set_id(size_t id); PrintObject* object(); + const PrintObject* object() const; Layer *upper_layer; Layer *lower_layer; diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 470dbe0152..fcf90f1330 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -13,6 +13,8 @@ #define RESOLUTION 0.0125 #define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) #define PI 3.141592653589793238 +#define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 +#define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 63cccfc225..4ab816bb87 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -124,13 +124,6 @@ void set_layer(Layer* ptr) %code{% THIS->layer = ptr; %}; - bool _has_seam_position(PrintObject* ptr) - %code{% RETVAL = THIS->_seam_position.count(ptr) > 0; %}; - Clone _seam_position(PrintObject* ptr) - %code{% RETVAL = THIS->_seam_position[ptr]; %}; - void _set_seam_position(PrintObject* ptr, Point* pos) - %code{% THIS->_seam_position[ptr] = *pos; %}; - bool first_layer() %code{% RETVAL = THIS->first_layer; %}; void set_first_layer(bool value) @@ -160,10 +153,10 @@ std::string preamble(); std::string change_layer(Layer* layer) %code{% RETVAL = THIS->change_layer(*layer); %}; - std::string extrude_path(ExtrusionPath* path, std::string description = "", double speed = -1) - %code{% RETVAL = THIS->extrude_path(*path, description, speed); %}; - std::string _extrude_path(ExtrusionPath* path, std::string description = "", double speed = -1) - %code{% RETVAL = THIS->_extrude_path(*path, description, speed); %}; + %name{extrude_loop} std::string extrude(ExtrusionLoop* loop, std::string description = "", double speed = -1) + %code{% RETVAL = THIS->extrude(*loop, description, speed); %}; + %name{extrude_path} std::string extrude(ExtrusionPath* path, std::string description = "", double speed = -1) + %code{% RETVAL = THIS->extrude(*path, description, speed); %}; std::string travel_to(Point* point, ExtrusionRole role, std::string comment) %code{% RETVAL = THIS->travel_to(*point, role, comment); %}; bool needs_retraction(Polyline* travel, ExtrusionRole role = erNone) @@ -173,4 +166,18 @@ std::string set_extruder(unsigned int extruder_id); Clone point_to_gcode(Point* point) %code{% RETVAL = THIS->point_to_gcode(*point); %}; + +%{ +std::string +GCode::extrude(entity, description, speed) + SV* entity + std::string description; + double speed; + CODE: + ExtrusionEntity* e = (ExtrusionEntity *)SvIV((SV*)SvRV( entity )); + RETVAL = THIS->extrude(*e, description, speed); + OUTPUT: + RETVAL +%} + }; diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index c7795b453e..d8444f0921 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -34,6 +34,9 @@ %name{Slic3r::Layer} class Layer { // owned by PrintObject, no constructor/destructor + + Ref as_layer() + %code%{ RETVAL = THIS; %}; int id(); void set_id(int id); From b8aecbd56c74aa658891cf78bed24f07605d30bb Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 3 Jul 2015 22:58:29 +0200 Subject: [PATCH 72/83] Initial work for porting PerimeterGenerator to XS --- lib/Slic3r.pm | 1 - lib/Slic3r/Layer/PerimeterGenerator.pm | 33 +------ xs/src/libslic3r/PerimeterGenerator.cpp | 120 ++++++++++++++++++++++++ xs/src/libslic3r/PerimeterGenerator.hpp | 64 +++++++++++++ xs/src/libslic3r/libslic3r.h | 1 + 5 files changed, 187 insertions(+), 32 deletions(-) create mode 100644 xs/src/libslic3r/PerimeterGenerator.cpp create mode 100644 xs/src/libslic3r/PerimeterGenerator.hpp diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index e052240a16..9bcebdb9bb 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -79,7 +79,6 @@ use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.3; use constant EXTERNAL_INFILL_MARGIN => 3; -use constant INSET_OVERLAP_TOLERANCE => 0.4; # keep track of threads we created my @my_threads = (); diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index daf2ba8f80..a4b0d4aec7 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -8,6 +8,8 @@ use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset of offset_ex offset2_ex intersection_ppl diff_ppl); use Slic3r::Surface ':types'; +use constant INSET_OVERLAP_TOLERANCE => 0.4; + has 'slices' => (is => 'ro', required => 1); # SurfaceCollection has 'lower_slices' => (is => 'ro', required => 0); has 'layer_height' => (is => 'ro', required => 1); @@ -20,7 +22,6 @@ has 'config' => (is => 'ro', default => sub { Slic3r::Config::Pri has 'object_config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new }); has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); has '_lower_slices_p' => (is => 'rw', default => sub { [] }); -has '_holes_pt' => (is => 'rw'); has '_ext_mm3_per_mm' => (is => 'rw'); has '_mm3_per_mm' => (is => 'rw'); has '_mm3_per_mm_overhang' => (is => 'rw'); @@ -500,34 +501,4 @@ sub _fill_gaps { } -package Slic3r::Layer::PerimeterGenerator::Loop; -use Moo; - -has 'polygon' => (is => 'ro', required => 1); -has 'is_contour' => (is => 'ro', required => 1); -has 'depth' => (is => 'ro', required => 1); -has 'children' => (is => 'ro', default => sub { [] }); - -use List::Util qw(first); - -sub add_child { - my ($self, $child) = @_; - push @{$self->children}, $child; -} - -sub is_external { - my ($self) = @_; - return $self->depth == 0; -} - -sub is_internal_contour { - my ($self) = @_; - - if ($self->is_contour) { - # an internal contour is a contour containing no other contours - return !defined first { $_->is_contour } @{$self->children}; - } - return 0; -} - 1; diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp new file mode 100644 index 0000000000..5c054aa6ad --- /dev/null +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -0,0 +1,120 @@ +#include "PerimeterGenerator.hpp" + +namespace Slic3r { + +void +PerimeterGenerator::process() +{ + +} + +ExtrusionEntityCollection +PerimeterGenerator::_traverse_loops(const std::vector &loops, + const Polylines &thin_walls) const +{ + +} + +ExtrusionEntityCollection +PerimeterGenerator::_fill_gaps(double min, double max, double w, + const Polygons &gaps) const +{ + ExtrusionEntityCollection coll; + + min *= (1 - INSET_OVERLAP_TOLERANCE); + + ExPolygon curr = diff( + offset2(gaps, -min/2, +min/2), + offset2(gaps, -max/2, +max/2), + true, + ); + + Polylines polylines; + for (ExPolygons::const_iterator ex = curr.begin(); ex != curr.end(); ++ex) + ex->medial_axis(max, min/2, &polylines); + if (polylines.empty()) + return coll; + + #ifdef SLIC3R_DEBUG + if (!curr.empty()) + printf(" %d gaps filled with extrusion width = %zu\n", curr.size(), w); + #endif + + //my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w); + Flow flow( + w, this->layer_height, this->solid_infill_flow.nozzle_diameter + ); + + double mm3_per_mm = flow.mm3_per_mm(); + + /* + my %path_args = ( + role => EXTR_ROLE_GAPFILL, + mm3_per_mm => $flow->mm3_per_mm, + width => $flow->width, + height => $self->layer_height, + ); + */ + + for (Polylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { + /* + #if ($polylines[$i]->isa('Slic3r::Polygon')) { + # my $loop = Slic3r::ExtrusionLoop->new; + # $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args)); + # $polylines[$i] = $loop; + */ + if (p->is_valid() && p->first_point().coincides_with(p->last_point())) { + // since medial_axis() now returns only Polyline objects, detect loops here + + + ExtrusionLoop loop; + loop.paths.push_back(); + } else { + + } + } + + foreach my $polyline (@polylines) { + #if ($polylines[$i]->isa('Slic3r::Polygon')) { + # my $loop = Slic3r::ExtrusionLoop->new; + # $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args)); + # $polylines[$i] = $loop; + if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) { + # since medial_axis() now returns only Polyline objects, detect loops here + push @entities, my $loop = Slic3r::ExtrusionLoop->new; + $loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args)); + } else { + push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args); + } + } + + return coll; +} + +#ifdef SLIC3RXS +REGISTER_CLASS(PerimeterGenerator, "Layer::PerimeterGenerator"); +#endif + +bool +PerimeterGeneratorLoop::is_external() const +{ + return this->depth == 0; +} + +bool +PerimeterGeneratorLoop::is_internal_contour() const +{ + if (this->is_contour) { + // an internal contour is a contour containing no other contours + for (std::vector::const_iterator loop = this->children.begin(); + loop != this->children.end(); ++loop) { + if (loop->is_contour) { + return false; + } + } + return true; + } + return false; +} + +} diff --git a/xs/src/libslic3r/PerimeterGenerator.hpp b/xs/src/libslic3r/PerimeterGenerator.hpp new file mode 100644 index 0000000000..d896cd8da7 --- /dev/null +++ b/xs/src/libslic3r/PerimeterGenerator.hpp @@ -0,0 +1,64 @@ +#ifndef slic3r_PerimeterGenerator_hpp_ +#define slic3r_PerimeterGenerator_hpp_ + +#include + +namespace Slic3r { + +class PerimeterGeneratorLoop; + +class PerimeterGeneratorLoop { + public: + Polygon polygon; + bool is_contour; + unsigned short depth; + std::vector children; + + PerimeterGeneratorLoop(Polygon polygon, unsigned short depth) + : polygon(polygon), depth(depth) + {}; + bool is_external() const; + bool is_internal_contour() const; +}; + +class PerimeterGenerator { + public: + SurfaceCollection* slices; + SurfaceCollection* lower_slices; + double layer_height; + int layer_id; + Flow perimeter_flow; + Flow ext_perimeter_flow; + Flow overhang_flow; + Flow solid_infill_flow; + PrintRegionConfig* config; + PrintObjectConfig* object_config; + PrintConfig* print_config; + double _ext_mm3_per_mm; + double _mm3_per_mm; + double _mm3_per_mm_overhang; + ExtrusionEntityCollection* loops; + ExtrusionEntityCollection* gap_fill; + SurfaceCollection* fill_surfaces; + + PerimeterGenerator(SurfaceCollection* slices, double layer_height, + ExtrusionEntityCollection* loops, ExtrusionEntityCollection* gap_fill, + SurfaceCollection* fill_surfaces) + : slices(slices), layer_height(layer_height), layer_id(-1), + _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1), + loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces) + {}; + void process(); + + private: + Polygons _lower_slices_p; + + ExtrusionEntityCollection _traverse_loops(const std::vector &loops, + const Polylines &thin_walls) const; + ExtrusionEntityCollection _fill_gaps(double min, double max, double w, + const Polygons &gaps) const; +}; + +} + +#endif diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index fcf90f1330..87d98caf1f 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -15,6 +15,7 @@ #define PI 3.141592653589793238 #define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 #define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI +#define INSET_OVERLAP_TOLERANCE 0.4 #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) From 440af2c81c0be4b26d015b8a7c8a701eaf804736 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 3 Jul 2015 23:38:41 +0200 Subject: [PATCH 73/83] Bugfix: bridge anchors were shortened under rare circumstances --- lib/Slic3r/Print/Object.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 1b5d875eb8..13f43df7ab 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -942,7 +942,12 @@ sub discover_horizontal_shells { # make sure our grown surfaces don't exceed the fill area my @grown = @{intersection( offset($too_narrow, +$margin), - [ map $_->p, @neighbor_fill_surfaces ], + # Discard bridges as they are grown for anchoring and we can't + # remove such anchors. (This may happen when a bridge is being + # anchored onto a wall where little space remains after the bridge + # is grown, and that little space is an internal solid shell so + # it triggers this too_narrow logic.) + [ map $_->p, grep { $_->is_internal && !$_->is_bridge } @neighbor_fill_surfaces ], )}; $new_internal_solid = $solid = [ @grown, @$new_internal_solid ]; } From e84ead529197e472f305d7c1d1a6a160fd9fc9a2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 4 Jul 2015 15:22:27 +0200 Subject: [PATCH 74/83] Bugfix: changing range-based layer heigths didn't trigger background processing. #2958 --- lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index ce38af97e2..c920b796a1 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -107,12 +107,17 @@ sub new { $grid->SetCellValue($event->GetRow, $event->GetCol, $value); # if there's no empty row, let's append one - for my $i (0 .. $grid->GetNumberRows-1) { + for my $i (0 .. $grid->GetNumberRows) { + if ($i == $grid->GetNumberRows) { + # if we're here then we found no empty row + $grid->AppendRows(1); + last; + } if (!grep $grid->GetCellValue($i, $_), 0..2) { - return; + # exit loop if this row is empty + last; } } - $grid->AppendRows(1); $self->{layers_changed} = 1; }); From c65c9d876eb29e4660deea0a51dd6a2a2a047ab5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 4 Jul 2015 15:25:11 +0200 Subject: [PATCH 75/83] Bugfix: zooming in empty layers preview (because of disabled background processing) crashed --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 7c1f487134..56912888d9 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -164,6 +164,8 @@ sub new { EVT_MOUSEWHEEL($self, sub { my ($self, $e) = @_; + return if !$self->GetParent->enabled; + my $old_zoom = $self->_zoom; # Calculate the zoom delta and apply it to the current zoom factor @@ -205,6 +207,8 @@ sub new { sub mouse_event { my ($self, $e) = @_; + return if !$self->GetParent->enabled; + my $pos = Slic3r::Pointf->new($e->GetPositionXY); if ($e->Entering && &Wx::wxMSW) { # wxMSW needs focus in order to catch mouse wheel events From 0e18b094d1edf32730a7555cf253cd9a90a7c404 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 7 Jul 2015 01:17:31 +0200 Subject: [PATCH 76/83] More work for porting PerimeterGenerator to XS --- lib/Slic3r/Layer/PerimeterGenerator.pm | 54 +- xs/src/libslic3r/ExPolygon.cpp | 7 + xs/src/libslic3r/ExPolygon.hpp | 1 + xs/src/libslic3r/ExtrusionEntity.hpp | 9 + .../libslic3r/ExtrusionEntityCollection.cpp | 61 ++- .../libslic3r/ExtrusionEntityCollection.hpp | 8 + xs/src/libslic3r/PerimeterGenerator.cpp | 471 ++++++++++++++++-- xs/src/libslic3r/PerimeterGenerator.hpp | 30 +- 8 files changed, 538 insertions(+), 103 deletions(-) diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index a4b0d4aec7..d8d5008b80 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -48,7 +48,7 @@ sub BUILDARGS { return { %args }; } -sub process { +sub __process { my ($self) = @_; # other perimeters @@ -335,7 +335,7 @@ sub process { } } -sub _traverse_loops { +sub ___traverse_loops { my ($self, $loops, $thin_walls) = @_; # loops is an arrayref of ::Loop objects @@ -451,54 +451,4 @@ sub _traverse_loops { return @entities; } -sub _fill_gaps { - my ($self, $min, $max, $w, $gaps) = @_; - - $min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - - my $this = diff_ex( - offset2($gaps, -$min/2, +$min/2), - offset2($gaps, -$max/2, +$max/2), - 1, - ); - - my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this; - return if !@polylines; - - Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w - if @$this; - - #my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w); - my $flow = Slic3r::Flow->new( - width => $w, - height => $self->layer_height, - nozzle_diameter => $self->solid_infill_flow->nozzle_diameter, - ); - - my %path_args = ( - role => EXTR_ROLE_GAPFILL, - mm3_per_mm => $flow->mm3_per_mm, - width => $flow->width, - height => $self->layer_height, - ); - - my @entities = (); - foreach my $polyline (@polylines) { - #if ($polylines[$i]->isa('Slic3r::Polygon')) { - # my $loop = Slic3r::ExtrusionLoop->new; - # $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args)); - # $polylines[$i] = $loop; - if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) { - # since medial_axis() now returns only Polyline objects, detect loops here - push @entities, my $loop = Slic3r::ExtrusionLoop->new; - $loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args)); - } else { - push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args); - } - } - - return @entities; -} - - 1; diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 53a1e9333a..83e1929aaa 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -122,6 +122,13 @@ ExPolygon::has_boundary_point(const Point &point) const return false; } +void +ExPolygon::simplify_p(double tolerance, Polygons* polygons) const +{ + Polygons pp = this->simplify_p(tolerance); + polygons->insert(polygons->end(), pp.begin(), pp.end()); +} + Polygons ExPolygon::simplify_p(double tolerance) const { diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index 3d7cd35409..7b7b0b760e 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -27,6 +27,7 @@ class ExPolygon bool contains(const Point &point) const; bool contains_b(const Point &point) const; bool has_boundary_point(const Point &point) const; + void simplify_p(double tolerance, Polygons* polygons) const; Polygons simplify_p(double tolerance) const; ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons &expolygons) const; diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index bcb50155a9..0cbabd1b88 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -52,6 +52,7 @@ class ExtrusionEntity virtual Point last_point() const = 0; virtual Polygons grow() const = 0; virtual double min_mm3_per_mm() const = 0; + virtual Polyline as_polyline() const = 0; }; typedef std::vector ExtrusionEntitiesPtr; @@ -83,6 +84,9 @@ class ExtrusionPath : public ExtrusionEntity double min_mm3_per_mm() const { return this->mm3_per_mm; }; + Polyline as_polyline() const { + return this->polyline; + }; private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; @@ -97,6 +101,8 @@ class ExtrusionLoop : public ExtrusionEntity ExtrusionLoopRole role; ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {}; + ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) + : paths(paths), role(role) {}; bool is_loop() const { return true; }; @@ -120,6 +126,9 @@ class ExtrusionLoop : public ExtrusionEntity bool is_solid_infill() const; Polygons grow() const; double min_mm3_per_mm() const; + Polyline as_polyline() const { + return this->polygon()->split_at_first_point(); + }; }; } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index 79736cbcb7..5904252ff8 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -8,9 +8,13 @@ namespace Slic3r { ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionEntityCollection& collection) : no_sort(collection.no_sort), orig_indices(collection.orig_indices) { - this->entities.reserve(collection.entities.size()); - for (ExtrusionEntitiesPtr::const_iterator it = collection.entities.begin(); it != collection.entities.end(); ++it) - this->entities.push_back((*it)->clone()); + this->append(collection); +} + +ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths) + : no_sort(false) +{ + this->append(paths); } ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const ExtrusionEntityCollection &other) @@ -28,6 +32,16 @@ ExtrusionEntityCollection::swap (ExtrusionEntityCollection &c) std::swap(this->no_sort, c.no_sort); } +ExtrusionEntityCollection::operator ExtrusionPaths() const +{ + ExtrusionPaths paths; + for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { + if (const ExtrusionPath* path = dynamic_cast(*it)) + paths.push_back(*path); + } + return paths; +} + ExtrusionEntityCollection* ExtrusionEntityCollection::clone() const { @@ -57,6 +71,33 @@ ExtrusionEntityCollection::last_point() const return this->entities.back()->last_point(); } +void +ExtrusionEntityCollection::append(const ExtrusionEntity &entity) +{ + this->entities.push_back(entity.clone()); +} + +void +ExtrusionEntityCollection::append(const ExtrusionEntityCollection &collection) +{ + this->entities.insert(this->entities.end(), collection.entities.begin(), collection.entities.end()); +} + +void +ExtrusionEntityCollection::append(const ExtrusionPaths &paths) +{ + for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path) + this->append(*path); +} + +ExtrusionEntityCollection +ExtrusionEntityCollection::chained_path(bool no_reverse, std::vector* orig_indices) const +{ + ExtrusionEntityCollection coll; + this->chained_path(&coll, no_reverse, orig_indices); + return coll; +} + void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, std::vector* orig_indices) const { @@ -145,15 +186,21 @@ ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if ((*it)->is_collection()) { ExtrusionEntityCollection* collection = dynamic_cast(*it); - ExtrusionEntityCollection contents; - collection->flatten(&contents); - retval->entities.insert(retval->entities.end(), contents.entities.begin(), contents.entities.end()); + retval->append(collection->flatten()); } else { - retval->entities.push_back((*it)->clone()); + retval->append(**it); } } } +ExtrusionEntityCollection +ExtrusionEntityCollection::flatten() const +{ + ExtrusionEntityCollection coll; + this->flatten(&coll); + return coll; +} + double ExtrusionEntityCollection::min_mm3_per_mm() const { diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 24016d6383..1cb288d0a6 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -15,7 +15,10 @@ class ExtrusionEntityCollection : public ExtrusionEntity bool no_sort; ExtrusionEntityCollection(): no_sort(false) {}; ExtrusionEntityCollection(const ExtrusionEntityCollection &collection); + ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other); + operator ExtrusionPaths() const; + bool is_collection() const { return true; }; @@ -23,6 +26,10 @@ class ExtrusionEntityCollection : public ExtrusionEntity return !this->no_sort; }; void swap (ExtrusionEntityCollection &c); + void append(const ExtrusionEntity &entity); + void append(const ExtrusionEntityCollection &collection); + void append(const ExtrusionPaths &paths); + ExtrusionEntityCollection chained_path(bool no_reverse = false, std::vector* orig_indices = NULL) const; void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector* orig_indices = NULL) const; void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector* orig_indices = NULL) const; void reverse(); @@ -31,6 +38,7 @@ class ExtrusionEntityCollection : public ExtrusionEntity Polygons grow() const; size_t items_count() const; void flatten(ExtrusionEntityCollection* retval) const; + ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; }; diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 5c054aa6ad..a03598db7b 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -5,14 +5,439 @@ namespace Slic3r { void PerimeterGenerator::process() { - + // other perimeters + this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); + coord_t pwidth = this->perimeter_flow.scaled_width(); + coord_t pspacing = this->perimeter_flow.scaled_spacing(); + + // external perimeters + this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); + coord_t = ext_pwidth = this->ext_perimeter_flow.scaled_width(); + coord_t = ext_pspacing = scale_(this->ext_perimeter_flow.spacing_to(this->perimeter_flow)); + + // overhang perimeters + this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); + + // solid infill + coord_t ispacing = this->solid_infill_flow->scaled_spacing; + coord_t gap_area_threshold = pwidth * pwidth; + + // Calculate the minimum required spacing between two adjacent traces. + // This should be equal to the nominal flow spacing but we experiment + // with some tolerance in order to avoid triggering medial axis when + // some squishing might work. Loops are still spaced by the entire + // flow spacing; this only applies to collapsing parts. + coord_t min_spacing = pspacing * (1 - INSET_OVERLAP_TOLERANCE); + coord_t ext_min_spacing = ext_pspacing * (1 - INSET_OVERLAP_TOLERANCE); + + // prepare grown lower layer slices for overhang detection + if (this->lower_slices != NULL && this->config->overhangs) { + // We consider overhang any part where the entire nozzle diameter is not supported by the + // lower layer, so we take lower slices and offset them by half the nozzle diameter used + // in the current layer + double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); + + this->_lower_slices_p = offset(this->lower_slices, scale_(+nozzle_diameter/2)); + } + + // we need to process each island separately because we might have different + // extra perimeters for each one + for (Surfaces::const_iterator surface = this->slices->surfaces.begin(); + surface != this->slices->surfaces.end(); ++surface) { + // detect how many perimeters must be generated for this island + unsigned short loop_number = this->config->perimeters + surface->extra_perimeters; + loop_number--; // 0-indexed loops + + Polygons gaps; + + Polygons last = surface->expolygon.simplify_p(SCALED_RESOLUTION); + if (loop_number >= 0) { // no loops = -1 + + std::vector contours(loop_number); // depth => loops + std::vector holes(loop_number); // depth => loops + Polylines thin_walls; + + // we loop one time more than needed in order to find gaps after the last perimeter was applied + for (unsigned short i = 0; i <= loop_number+1; ++i) { // outer loop is 0 + Polygons offsets; + if (i == 0) { + // the minimum thickness of a single loop is: + // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 + if (this->config->thin_walls) { + offsets = offset2( + \@last, + -(0.5*ext_pwidth + 0.5*ext_min_spacing - 1), + +(0.5*ext_min_spacing - 1) + ); + } else { + offsets = offset(last, -0.5*ext_pwidth); + } + + // look for thin walls + if (this->config->thin_walls) { + Polygons diff = diff( + last, + offset(offsets, +0.5*ext_pwidth), + true // medial axis requires non-overlapping geometry + ); + + // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width + // (actually, something larger than that still may exist due to mitering or other causes) + coord_t min_width = ext_pwidth / 2; + ExPolygons expp = offset2(diff, -min_width/2, +min_width/2)}; + + // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop + Polylines pp; + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) + ex->medial_axis(ext_pwidth + ext_pspacing, min_width, &pp); + + double threshold = ext_pwidth * ext_pwidth; + for (Polylines::const_iterator p = pp.begin(); p != pp.end(); ++p) { + if (p->length() > threshold) { + thin_walls.push_back(*p); + } + } + + #ifdef DEBUG + printf(" %zu thin walls detected\n", thin_walls.size()); + #endif + + /* + if (false) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "medial_axis.svg", + no_arrows => 1, + #expolygons => \@expp, + polylines => \@thin_walls, + ); + } + */ + } + } else { + coord_t distance = (i == 1) ? ext_pspacing : pspacing; + + if (this->config->thin_walls) { + offsets = offset2( + last, + -(distance + 0.5*min_spacing - 1), + +(0.5*min_spacing - 1), + ); + } else { + offsets = offset( + last, + -distance, + ); + } + + // look for gaps + if (this->config->gap_fill_speed > 0 && this->config->fill_density > 0) { + // not using safety offset here would "detect" very narrow gaps + // (but still long enough to escape the area threshold) that gap fill + // won't be able to fill but we'd still remove from infill area + ExPolygons diff = diff_ex( + offset(last, -0.5*distance), + offset(offsets, +0.5*distance + 10), // safety offset + ); + for (ExPolygons::const_iterator ex = diff.begin(); ex != diff.end(); ++ex) { + if (fabs(ex->area()) >= gap_area_threshold) + gaps.push_back(*ex); + } + } + } + + if (offsets.empty()) break; + if (i > loop_number) break; // we were only looking for gaps this time + + last = offsets; + + for (Polygons::const_iterator polygon = offsets.begin(); polygon != offsets.end(); ++polygon) { + PerimeterGeneratorLoop loop(*polygon, i); + loop.is_contour = polygon->is_counter_clockwise(); + if (loop.is_contour) { + contours[i].push_back(loop); + } else { + holes[i].push_back(loop); + } + } + } + + // nest loops: holes first + for (unsigned short d = 0; <= loop_number; ++d) { + PerimeterGeneratorLoops &holes_d = holes[d]; + + // loop through all holes having depth == d + for (unsigned short i = 0; i < holes_d.size(); ++i) { + const PerimeterGeneratorLoop &loop = holes_d[i]; + + // find the hole loop that contains this one, if any + for (unsigned short t = d+1; t <= loop_number; ++t) { + for (unsigned short j = 0; j < holes_d.size(); ++j) { + PerimeterGeneratorLoop &candidate_parent = holes[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.add_child(loop); + holes_d.erase(holes_d.begin() + i); + --i; + goto NEXT_HOLE; + } + } + } + + // if no hole contains this hole, find the contour loop that contains it + for (unsigned short t = loop_number; t >= 0; --t) { + for (unsigned short j = 0; j < contours[t].size(); ++j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.add_child(loop); + holes_d.erase(holes_d.begin() + i); + --i; + goto NEXT_HOLE; + } + } + } + } + NEXT_HOLE: + } + + // nest contour loops + for (unsigned short d = loop_number; d >= 1; --d) { + PerimeterGeneratorLoops &contours_d = contours[d]; + + // loop through all contours having depth == d + for (unsigned short i = 0; i < contours_d.size(); ++i) { + const PerimeterGeneratorLoop &loop = contours_d[i]; + + // find the contour loop that contains it + for (unsigned short t = d-1; t >= 0; --t) { + for (unsigned short j = 0; j < contours_d[t].size(); ++j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.add_child(loop); + contours_d.erase(contours_d.begin() + i); + --i; + goto NEXT_CONTOUR; + } + } + } + + NEXT_CONTOUR: + } + } + + // at this point, all loops should be in contours[0] + + ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls); + + // if brim will be printed, reverse the order of perimeters so that + // we continue inwards after having finished the brim + // TODO: add test for perimeter order + if (this->config->external_perimeters_first + || (this->layer_id == 0 && this->print_config->brim_width > 0)) + entities.reverse(); + + // append perimeters for this slice as a collection + if (!entities.empty()) + this->loops->append(entities); + } + + // fill gaps + if (!gaps.empty()) { + /* + if (false) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "gaps.svg", + expolygons => union_ex(\@gaps), + ); + } + */ + + // where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth + // where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth + std::vector gap_sizes; + gap_sizes.push_back(PerimeterGeneratorGapSize(pwidth, 2*pspacing, unscale(2*pwidth))); + gap_sizes.push_back(PerimeterGeneratorGapSize(0.1*pwidth, pwidth, unscale(1*pwidth))); + + for (std::vector::const_iterator gap_size = gap_sizes.begin(); + gap_size != gap_sizes.end(); ++gap_size) { + ExtrusionEntityCollection gap_fill = this->_fill_gaps(gap_size.min, gap_size.max, gap_size.width); + this->gap_fill->append(gap_fill); + + // Make sure we don't infill narrow parts that are already gap-filled + // (we only consider this surface's gaps to reduce the diff() complexity). + // Growing actual extrusions ensures that gaps not filled by medial axis + // are not subtracted from fill surfaces (they might be too short gaps + // that medial axis skips but infill might join with other infill regions + // and use zigzag). + double dist = scale_(gap_size->width/2); + Polygons filled; + for (ExtrusionEntitiesPtr::const_iterator it = gap_fill.entities.begin(); + it != gap_fill.entities.end(); ++it) + offset((*it)->as_polyline(), &filled, dist); + + last = diff(last, filled); + gaps = diff(gaps, filled); // prevent more gap fill here + } + } + + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = 0; + if (loop_number == 0) { + // one loop + inset += ext_pspacing/2; + } else if (loop_number > 0) { + // two or more loops + inset += pspacing/2; + } + + // only apply infill overlap if we actually have one perimeter + if (inset > 0) + inset -= this->config->get_abs_value("infill_overlap", inset + ispacing/2); + + { + ExPolygons expp = union_(last); + + // simplify infill contours according to resolution + Polygons pp; + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) + ex->simplify_p(SCALED_RESOLUTION, &pp); + + // collapse too narrow infill areas + coord_t min_perimeter_infill_spacing = ispacing * (1 - INSET_OVERLAP_TOLERANCE); + expp = offset2( + pp, + -inset -min_perimeter_infill_spacing/2, + +min_perimeter_infill_spacing/2, + ); + + // append infill areas to fill_surfaces + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) + this->fill_surfaces->surfaces.push_back(Surface(stInternal, *ex)); // use a bogus surface type + } + } } ExtrusionEntityCollection -PerimeterGenerator::_traverse_loops(const std::vector &loops, +PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, const Polylines &thin_walls) const { + // loops is an arrayref of ::Loop objects + // turn each one into an ExtrusionLoop object + ExtrusionEntityCollection coll; + for (PerimeterGeneratorLoops::const_iterator loop = loops.begin(); + loop != loops.end(); ++loop) { + bool is_external = loop->is_external(); + + ExtrusionRole role; + ExtrusionLoopRole loop_role; + role = is_external ? erExternalPerimeter : erPerimeter; + if (loop->is_internal_contour()) { + // Note that we set loop role to ContourInternalPerimeter + // also when loop is both internal and external (i.e. + // there's only one contour loop). + loop_role = elrContourInternalPerimeter; + } else { + loop_role = elrDefault; + } + + // detect overhanging/bridging perimeters + ExtrusionPaths paths; + if (this->config->overhangs && this->layer_id > 0 + && !(this->object_config->support_material && this->object_config->support_material_contact_distance == 0)) { + // get non-overhang paths by intersecting this loop with the grown lower slices + { + Polylines polylines; + intersection(loop->polygon(), this->_lower_slices_p, &polylines); + + for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { + ExtrusionPath path(role); + path.polyline = *polyline; + path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; + path.width = is_external ? this->ext_perimeter_flow->width : this->perimeter_flow.width; + path.height = this->layer_height; + paths.push_back(path); + } + } + + // get overhang paths by checking what parts of this loop fall + // outside the grown lower slices (thus where the distance between + // the loop centerline and original lower slices is >= half nozzle diameter + { + Polylines polylines; + diff(loop->polygon(), this->_lower_slices_p, &polylines); + + for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { + ExtrusionPath path(erOverhangPerimeter); + path.polyline = *polyline; + path.mm3_per_mm = this->_mm3_per_mm_overhang; + path.width = this->overhang_flow.width; + path.height = this->overhang_flow.height; + paths.push_back(path); + } + } + + // reapply the nearest point search for starting point + // We allow polyline reversal because Clipper may have randomly + // reversed polylines during clipping. + paths = ExtrusionEntityCollection(paths).chained_path(); + } else { + ExtrusionPath path(role); + path.polyline = loop.polygon().split_at_first_point(); + path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; + path.width = is_external ? this->ext_perimeter_flow->width : this->perimeter_flow.width; + path.height = this->layer_height; + } + + coll.append(ExtrusionLoop(paths, loop_role)); + } + // append thin walls to the nearest-neighbor search (only for first iteration) + if (!thin_walls.empty()) { + for (Polylines::const_iterator polyline = thin_walls.begin(); polyline != thin_walls.end(); ++polyline) { + ExtrusionPath path(erExternalPerimeter); + path.polyline = *polyline; + path.mm3_per_mm = this->_mm3_per_mm; + path.width = this->perimeter_flow.width; + path.height = this->layer_height; + coll.append(path); + } + + thin_walls.clear(); + } + + // sort entities + ExtrusionPathCollection sorted_coll; + coll.chained_path(&sorted_coll, false, &sorted_coll.orig_indices); + + // traverse children + ExtrusionPathCollection entities; + for (unsigned short i = 0; i < sorted_coll.orig_indices.size(); ++i) { + size_t idx = sorted_coll.orig_indices[i]; + if (idx >= loops.size()) { + // this is a thin wall + // let's get it from the sorted collection as it might have been reversed + entities.append(*sorted_coll.entities[i]); + } else { + PerimeterGeneratorLoop &loop = loops[i]; + ExtrusionLoop eloop = *coll.entities[idx]; + + ExtrusionEntityCollection children = this->_traverse_loops(loop->children, thin_walls); + if (loop->is_contour()) { + eloop.make_counter_clockwise(); + entities.append(children); + entities.append(elooop); + } else { + eloop.make_clockwise(); + push @entities, $eloop, @children; + entities.append(elooop); + entities.append(children); + } + } + } + return entities; } ExtrusionEntityCollection @@ -47,44 +472,20 @@ PerimeterGenerator::_fill_gaps(double min, double max, double w, double mm3_per_mm = flow.mm3_per_mm(); - /* - my %path_args = ( - role => EXTR_ROLE_GAPFILL, - mm3_per_mm => $flow->mm3_per_mm, - width => $flow->width, - height => $self->layer_height, - ); - */ - for (Polylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { - /* - #if ($polylines[$i]->isa('Slic3r::Polygon')) { - # my $loop = Slic3r::ExtrusionLoop->new; - # $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args)); - # $polylines[$i] = $loop; - */ + ExtrusionPath path(erGapFill); + path.polyline = *p; + path.mm3_per_mm = mm3_per_mm; + path.width = flow.width; + path.height = this->layer_height; + if (p->is_valid() && p->first_point().coincides_with(p->last_point())) { // since medial_axis() now returns only Polyline objects, detect loops here - - ExtrusionLoop loop; - loop.paths.push_back(); + loop.paths.push_back(path); + coll.append(loop); } else { - - } - } - - foreach my $polyline (@polylines) { - #if ($polylines[$i]->isa('Slic3r::Polygon')) { - # my $loop = Slic3r::ExtrusionLoop->new; - # $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args)); - # $polylines[$i] = $loop; - if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) { - # since medial_axis() now returns only Polyline objects, detect loops here - push @entities, my $loop = Slic3r::ExtrusionLoop->new; - $loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args)); - } else { - push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args); + coll.append(path); } } diff --git a/xs/src/libslic3r/PerimeterGenerator.hpp b/xs/src/libslic3r/PerimeterGenerator.hpp index d896cd8da7..f5a3a6dd91 100644 --- a/xs/src/libslic3r/PerimeterGenerator.hpp +++ b/xs/src/libslic3r/PerimeterGenerator.hpp @@ -6,6 +6,7 @@ namespace Slic3r { class PerimeterGeneratorLoop; +typedef std::vector PerimeterGeneratorLoops; class PerimeterGeneratorLoop { public: @@ -15,7 +16,7 @@ class PerimeterGeneratorLoop { std::vector children; PerimeterGeneratorLoop(Polygon polygon, unsigned short depth) - : polygon(polygon), depth(depth) + : polygon(polygon), depth(depth), is_contour(false) {}; bool is_external() const; bool is_internal_contour() const; @@ -34,31 +35,42 @@ class PerimeterGenerator { PrintRegionConfig* config; PrintObjectConfig* object_config; PrintConfig* print_config; - double _ext_mm3_per_mm; - double _mm3_per_mm; - double _mm3_per_mm_overhang; ExtrusionEntityCollection* loops; ExtrusionEntityCollection* gap_fill; SurfaceCollection* fill_surfaces; PerimeterGenerator(SurfaceCollection* slices, double layer_height, - ExtrusionEntityCollection* loops, ExtrusionEntityCollection* gap_fill, - SurfaceCollection* fill_surfaces) - : slices(slices), layer_height(layer_height), layer_id(-1), - _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1), + PrintRegionConfig* config, PrintObjectConfig* object_config, + PrintConfig* print_config, ExtrusionEntityCollection* loops, + ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces) + : slices(slices), lower_slices(NULL), layer_height(layer_height), layer_id(-1), + config(config), object_config(object_config), print_config(print_config), loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces) + _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1) {}; void process(); private: + double _ext_mm3_per_mm; + double _mm3_per_mm; + double _mm3_per_mm_overhang; Polygons _lower_slices_p; - ExtrusionEntityCollection _traverse_loops(const std::vector &loops, + ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, const Polylines &thin_walls) const; ExtrusionEntityCollection _fill_gaps(double min, double max, double w, const Polygons &gaps) const; }; +class PerimeterGeneratorGapSize { + public: + coord_t min; + coord_t max; + coord_t width; + PerimeterGeneratorGapSizes(coord_t min, coord_t max, coord_t width) + : min(min), max(max), width(width) {}; +}; + } #endif From 9ac4fc9034446b9aceaa29c7f9a7e356b0d46628 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 10 Jul 2015 16:01:45 +0200 Subject: [PATCH 77/83] Fix compilation on Windows due to lack of setenvt(). #2973 --- xs/src/libslic3r/Config.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index 07a8e86371..741151c380 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -1,6 +1,10 @@ #include "Config.hpp" #include // for setenv() +#ifdef _WIN32 +#define setenv(k, v, o) _putenv_s(k, v) +#endif + namespace Slic3r { bool From b4515cf695e0126191476e3363285af3be2efc11 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 23 Jul 2015 15:53:02 +0200 Subject: [PATCH 78/83] Finished porting PerimeterGenerator to C++ --- lib/Slic3r.pm | 2 +- lib/Slic3r/Layer/PerimeterGenerator.pm | 454 ------------------ lib/Slic3r/Layer/Region.pm | 30 +- t/perimeters.t | 190 ++++---- xs/MANIFEST | 3 + xs/src/libslic3r/ClipperUtils.cpp | 75 +++ xs/src/libslic3r/ClipperUtils.hpp | 18 + xs/src/libslic3r/ExPolygon.cpp | 17 +- xs/src/libslic3r/ExtrusionEntity.hpp | 2 +- .../libslic3r/ExtrusionEntityCollection.cpp | 21 +- .../libslic3r/ExtrusionEntityCollection.hpp | 12 +- xs/src/libslic3r/PerimeterGenerator.cpp | 114 ++--- xs/src/libslic3r/PerimeterGenerator.hpp | 20 +- xs/xsp/ExtrusionEntityCollection.xsp | 9 +- xs/xsp/PerimeterGenerator.xsp | 34 ++ xs/xsp/my.map | 4 + xs/xsp/typemap.xspt | 7 +- 17 files changed, 368 insertions(+), 644 deletions(-) delete mode 100644 lib/Slic3r/Layer/PerimeterGenerator.pm create mode 100644 xs/xsp/PerimeterGenerator.xsp diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9bcebdb9bb..bcd337dec7 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -54,7 +54,6 @@ use Slic3r::GCode::VibrationLimit; use Slic3r::Geometry qw(PI); use Slic3r::Geometry::Clipper; use Slic3r::Layer; -use Slic3r::Layer::PerimeterGenerator; use Slic3r::Layer::Region; use Slic3r::Line; use Slic3r::Model; @@ -200,6 +199,7 @@ sub thread_cleanup { *Slic3r::Geometry::BoundingBox::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {}; + *Slic3r::Layer::PerimeterGenerator::DESTROY = sub {}; *Slic3r::Line::DESTROY = sub {}; *Slic3r::Linef3::DESTROY = sub {}; *Slic3r::Model::DESTROY = sub {}; diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm deleted file mode 100644 index d8d5008b80..0000000000 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ /dev/null @@ -1,454 +0,0 @@ -package Slic3r::Layer::PerimeterGenerator; -use Moo; - -use Slic3r::ExtrusionLoop ':roles'; -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale chained_path); -use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset offset2 - offset_ex offset2_ex intersection_ppl diff_ppl); -use Slic3r::Surface ':types'; - -use constant INSET_OVERLAP_TOLERANCE => 0.4; - -has 'slices' => (is => 'ro', required => 1); # SurfaceCollection -has 'lower_slices' => (is => 'ro', required => 0); -has 'layer_height' => (is => 'ro', required => 1); -has 'layer_id' => (is => 'ro', required => 0, default => sub { -1 }); -has 'perimeter_flow' => (is => 'ro', required => 1); -has 'ext_perimeter_flow' => (is => 'ro', required => 1); -has 'overhang_flow' => (is => 'ro', required => 1); -has 'solid_infill_flow' => (is => 'ro', required => 1); -has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new }); -has 'object_config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new }); -has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); -has '_lower_slices_p' => (is => 'rw', default => sub { [] }); -has '_ext_mm3_per_mm' => (is => 'rw'); -has '_mm3_per_mm' => (is => 'rw'); -has '_mm3_per_mm_overhang' => (is => 'rw'); - -# generated loops will be put here -has 'loops' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new }); - -# generated gap fills will be put here -has 'gap_fill' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new }); - -# generated fill surfaces will be put here -has 'fill_surfaces' => (is => 'ro', default => sub { Slic3r::Surface::Collection->new }); - -sub BUILDARGS { - my ($class, %args) = @_; - - if (my $flow = delete $args{flow}) { - $args{perimeter_flow} //= $flow; - $args{ext_perimeter_flow} //= $flow; - $args{overhang_flow} //= $flow; - $args{solid_infill_flow} //= $flow; - } - - return { %args }; -} - -sub __process { - my ($self) = @_; - - # other perimeters - $self->_mm3_per_mm($self->perimeter_flow->mm3_per_mm); - my $pwidth = $self->perimeter_flow->scaled_width; - my $pspacing = $self->perimeter_flow->scaled_spacing; - - # external perimeters - $self->_ext_mm3_per_mm($self->ext_perimeter_flow->mm3_per_mm); - my $ext_pwidth = $self->ext_perimeter_flow->scaled_width; - my $ext_pspacing = scale($self->ext_perimeter_flow->spacing_to($self->perimeter_flow)); - - # overhang perimeters - $self->_mm3_per_mm_overhang($self->overhang_flow->mm3_per_mm); - - # solid infill - my $ispacing = $self->solid_infill_flow->scaled_spacing; - my $gap_area_threshold = $pwidth ** 2; - - # Calculate the minimum required spacing between two adjacent traces. - # This should be equal to the nominal flow spacing but we experiment - # with some tolerance in order to avoid triggering medial axis when - # some squishing might work. Loops are still spaced by the entire - # flow spacing; this only applies to collapsing parts. - my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - - # prepare grown lower layer slices for overhang detection - if ($self->lower_slices && $self->config->overhangs) { - # We consider overhang any part where the entire nozzle diameter is not supported by the - # lower layer, so we take lower slices and offset them by half the nozzle diameter used - # in the current layer - my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->config->perimeter_extruder-1); - - $self->_lower_slices_p( - offset([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2) - ); - } - - # we need to process each island separately because we might have different - # extra perimeters for each one - foreach my $surface (@{$self->slices}) { - # detect how many perimeters must be generated for this island - my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0); - $loop_number--; # 0-indexed loops - - my @gaps = (); # Polygons - - my @last = @{$surface->expolygon->simplify_p(&Slic3r::SCALED_RESOLUTION)}; - if ($loop_number >= 0) { # no loops = -1 - - my @contours = (); # depth => [ Polygon, Polygon ... ] - my @holes = (); # depth => [ Polygon, Polygon ... ] - my @thin_walls = (); # Polylines - - # we loop one time more than needed in order to find gaps after the last perimeter was applied - for my $i (0..($loop_number+1)) { # outer loop is 0 - my @offsets = (); - if ($i == 0) { - # the minimum thickness of a single loop is: - # ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - if ($self->config->thin_walls) { - @offsets = @{offset2( - \@last, - -(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1), - +(0.5*$ext_min_spacing - 1), - )}; - } else { - @offsets = @{offset( - \@last, - -0.5*$ext_pwidth, - )}; - } - - # look for thin walls - if ($self->config->thin_walls) { - my $diff = diff( - \@last, - offset(\@offsets, +0.5*$ext_pwidth), - 1, # medial axis requires non-overlapping geometry - ); - - # the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width - # (actually, something larger than that still may exist due to mitering or other causes) - my $min_width = $ext_pwidth / 2; - @thin_walls = @{offset2_ex($diff, -$min_width/2, +$min_width/2)}; - - # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - @thin_walls = grep $_->length > $ext_pwidth*2, - map @{$_->medial_axis($ext_pwidth + $ext_pspacing, $min_width)}, @thin_walls; - Slic3r::debugf " %d thin walls detected\n", scalar(@thin_walls) if $Slic3r::debug; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "medial_axis.svg", - no_arrows => 1, - #expolygons => \@expp, - polylines => \@thin_walls, - ); - } - } - } else { - my $distance = ($i == 1) ? $ext_pspacing : $pspacing; - - if ($self->config->thin_walls) { - @offsets = @{offset2( - \@last, - -($distance + 0.5*$min_spacing - 1), - +(0.5*$min_spacing - 1), - )}; - } else { - @offsets = @{offset( - \@last, - -$distance, - )}; - } - - # look for gaps - if ($self->config->gap_fill_speed > 0 && $self->config->fill_density > 0) { - # not using safety offset here would "detect" very narrow gaps - # (but still long enough to escape the area threshold) that gap fill - # won't be able to fill but we'd still remove from infill area - my $diff = diff_ex( - offset(\@last, -0.5*$distance), - offset(\@offsets, +0.5*$distance + 10), # safety offset - ); - push @gaps, map $_->clone, map @$_, grep abs($_->area) >= $gap_area_threshold, @$diff; - } - } - - last if !@offsets; - last if $i > $loop_number; # we were only looking for gaps this time - - @last = @offsets; - - $contours[$i] = []; - $holes[$i] = []; - foreach my $polygon (@offsets) { - my $loop = Slic3r::Layer::PerimeterGenerator::Loop->new( - polygon => $polygon, - is_contour => $polygon->is_counter_clockwise, - depth => $i, - ); - if ($loop->is_contour) { - push @{$contours[$i]}, $loop; - } else { - push @{$holes[$i]}, $loop; - } - } - } - - # nest loops: holes first - for my $d (0..$loop_number) { - # loop through all holes having depth $d - LOOP: for (my $i = 0; $i <= $#{$holes[$d]}; ++$i) { - my $loop = $holes[$d][$i]; - - # find the hole loop that contains this one, if any - for my $t (($d+1)..$loop_number) { - for (my $j = 0; $j <= $#{$holes[$t]}; ++$j) { - my $candidate_parent = $holes[$t][$j]; - if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { - $candidate_parent->add_child($loop); - splice @{$holes[$d]}, $i, 1; - --$i; - next LOOP; - } - } - } - - # if no hole contains this hole, find the contour loop that contains it - for my $t (reverse 0..$loop_number) { - for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) { - my $candidate_parent = $contours[$t][$j]; - if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { - $candidate_parent->add_child($loop); - splice @{$holes[$d]}, $i, 1; - --$i; - next LOOP; - } - } - } - } - } - - # nest contour loops - for my $d (reverse 1..$loop_number) { - # loop through all contours having depth $d - LOOP: for (my $i = 0; $i <= $#{$contours[$d]}; ++$i) { - my $loop = $contours[$d][$i]; - - # find the contour loop that contains it - for my $t (reverse 0..($d-1)) { - for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) { - my $candidate_parent = $contours[$t][$j]; - if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { - $candidate_parent->add_child($loop); - splice @{$contours[$d]}, $i, 1; - --$i; - next LOOP; - } - } - } - } - } - - # at this point, all loops should be in $contours[0] - my @entities = $self->_traverse_loops($contours[0], \@thin_walls); - - # if brim will be printed, reverse the order of perimeters so that - # we continue inwards after having finished the brim - # TODO: add test for perimeter order - @entities = reverse @entities - if $self->config->external_perimeters_first - || ($self->layer_id == 0 && $self->print_config->brim_width > 0); - - # append perimeters for this slice as a collection - $self->loops->append(Slic3r::ExtrusionPath::Collection->new(@entities)) - if @entities; - } - - # fill gaps - if (@gaps) { - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "gaps.svg", - expolygons => union_ex(\@gaps), - ); - } - - # where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth - # where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth - my @gap_sizes = ( - [ $pwidth, 2*$pspacing, unscale 2*$pwidth ], - [ 0.1*$pwidth, $pwidth, unscale 1*$pwidth ], - ); - foreach my $gap_size (@gap_sizes) { - my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps); - $self->gap_fill->append($_) for @gap_fill; - - # Make sure we don't infill narrow parts that are already gap-filled - # (we only consider this surface's gaps to reduce the diff() complexity). - # Growing actual extrusions ensures that gaps not filled by medial axis - # are not subtracted from fill surfaces (they might be too short gaps - # that medial axis skips but infill might join with other infill regions - # and use zigzag). - my $w = $gap_size->[2]; - my @filled = map { - @{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline) - ->grow(scale $w/2)}; - } @gap_fill; - @last = @{diff(\@last, \@filled)}; - @gaps = @{diff(\@gaps, \@filled)}; # prevent more gap fill here - } - } - - # create one more offset to be used as boundary for fill - # we offset by half the perimeter spacing (to get to the actual infill boundary) - # and then we offset back and forth by half the infill spacing to only consider the - # non-collapsing regions - my $inset = 0; - if ($loop_number == 0) { - # one loop - $inset += $ext_pspacing/2; - } elsif ($loop_number > 0) { - # two or more loops - $inset += $pspacing/2; - } - - # only apply infill overlap if we actually have one perimeter - $inset -= $self->config->get_abs_value_over('infill_overlap', $inset + $ispacing/2) - if $inset > 0; - - my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - $self->fill_surfaces->append($_) - for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type - @{offset2_ex( - [ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ], - -$inset -$min_perimeter_infill_spacing/2, - +$min_perimeter_infill_spacing/2, - )}; - } -} - -sub ___traverse_loops { - my ($self, $loops, $thin_walls) = @_; - - # loops is an arrayref of ::Loop objects - # turn each one into an ExtrusionLoop object - my $coll = Slic3r::ExtrusionPath::Collection->new; - foreach my $loop (@$loops) { - my $is_external = $loop->is_external; - - my ($role, $loop_role); - if ($is_external) { - $role = EXTR_ROLE_EXTERNAL_PERIMETER; - } else { - $role = EXTR_ROLE_PERIMETER; - } - if ($loop->is_internal_contour) { - # Note that we set loop role to ContourInternalPerimeter - # also when loop is both internal and external (i.e. - # there's only one contour loop). - $loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER; - } else { - $loop_role = EXTRL_ROLE_DEFAULT; - } - - # detect overhanging/bridging perimeters - my @paths = (); - if ($self->config->overhangs && $self->layer_id > 0 - && !($self->object_config->support_material && $self->object_config->support_material_contact_distance == 0)) { - # get non-overhang paths by intersecting this loop with the grown lower slices - foreach my $polyline (@{ intersection_ppl([ $loop->polygon ], $self->_lower_slices_p) }) { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => $role, - mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm), - width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width), - height => $self->layer_height, - ); - } - - # get overhang paths by checking what parts of this loop fall - # outside the grown lower slices (thus where the distance between - # the loop centerline and original lower slices is >= half nozzle diameter - foreach my $polyline (@{ diff_ppl([ $loop->polygon ], $self->_lower_slices_p) }) { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => EXTR_ROLE_OVERHANG_PERIMETER, - mm3_per_mm => $self->_mm3_per_mm_overhang, - width => $self->overhang_flow->width, - height => $self->overhang_flow->height, - ); - } - - # reapply the nearest point search for starting point - # (clone because the collection gets DESTROY'ed) - # We allow polyline reversal because Clipper may have randomly - # reversed polylines during clipping. - my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection - @paths = map $_->clone, @{$collection->chained_path(0)}; - } else { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $loop->polygon->split_at_first_point, - role => $role, - mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm), - width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width), - height => $self->layer_height, - ); - } - my $eloop = Slic3r::ExtrusionLoop->new_from_paths(@paths); - $eloop->role($loop_role); - $coll->append($eloop); - } - - # append thin walls to the nearest-neighbor search (only for first iteration) - if (@$thin_walls) { - foreach my $polyline (@$thin_walls) { - $coll->append(Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => $self->_mm3_per_mm, - width => $self->perimeter_flow->width, - height => $self->layer_height, - )); - } - - @$thin_walls = (); - } - - # sort entities - my $sorted_coll = $coll->chained_path_indices(0); - my @indices = @{$sorted_coll->orig_indices}; - - # traverse children - my @entities = (); - for my $i (0..$#indices) { - my $idx = $indices[$i]; - if ($idx > $#$loops) { - # this is a thin wall - # let's get it from the sorted collection as it might have been reversed - push @entities, $sorted_coll->[$i]->clone; - } else { - my $loop = $loops->[$idx]; - my $eloop = $coll->[$idx]->clone; - - my @children = $self->_traverse_loops($loop->children, $thin_walls); - if ($loop->is_contour) { - $eloop->make_counter_clockwise; - push @entities, @children, $eloop; - } else { - $eloop->make_clockwise; - push @entities, $eloop, @children; - } - } - } - return @entities; -} - -1; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index f65e90129f..51648c3ed2 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -33,23 +33,25 @@ sub make_perimeters { my $generator = Slic3r::Layer::PerimeterGenerator->new( # input: - config => $self->config, - object_config => $self->layer->object->config, - print_config => $self->layer->print->config, - layer_height => $self->height, - layer_id => $self->layer->id, - slices => $slices, - lower_slices => defined($self->layer->lower_layer) ? $self->layer->lower_layer->slices : undef, - perimeter_flow => $self->flow(FLOW_ROLE_PERIMETER), - ext_perimeter_flow => $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER), - overhang_flow => $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object), - solid_infill_flow => $self->flow(FLOW_ROLE_SOLID_INFILL), + $slices, + $self->height, + $self->flow(FLOW_ROLE_PERIMETER), + $self->config, + $self->layer->object->config, + $self->layer->print->config, # output: - loops => $self->perimeters, - gap_fill => $self->thin_fills, - fill_surfaces => $fill_surfaces, + $self->perimeters, + $self->thin_fills, + $fill_surfaces, ); + $generator->set_lower_slices($self->layer->lower_layer->slices) + if defined($self->layer->lower_layer); + $generator->set_layer_id($self->id); + $generator->set_ext_perimeter_flow($self->flow(FLOW_ROLE_EXTERNAL_PERIMETER)); + $generator->set_overhang_flow($self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object)); + $generator->set_solid_infill_flow($self->flow(FLOW_ROLE_SOLID_INFILL)); + $generator->process; } diff --git a/t/perimeters.t b/t/perimeters.t index d103eb3f27..1e47b51f09 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -17,6 +17,101 @@ use Slic3r::Geometry::Clipper qw(union_ex diff union offset); use Slic3r::Surface ':types'; use Slic3r::Test; +{ + my $flow = Slic3r::Flow->new( + width => 1, + height => 1, + nozzle_diameter => 1, + ); + + my $config = Slic3r::Config->new; + my $test = sub { + my ($expolygons, %expected) = @_; + + my $slices = Slic3r::Surface::Collection->new; + $slices->append(Slic3r::Surface->new( + surface_type => S_TYPE_INTERNAL, + expolygon => $_, + )) for @$expolygons; + + my ($region_config, $object_config, $print_config, $loops, $gap_fill, $fill_surfaces); + my $g = Slic3r::Layer::PerimeterGenerator->new( + # input: + $slices, + 1, # layer height + $flow, + ($region_config = Slic3r::Config::PrintRegion->new), + ($object_config = Slic3r::Config::PrintObject->new), + ($print_config = Slic3r::Config::Print->new), + + # output: + ($loops = Slic3r::ExtrusionPath::Collection->new), + ($gap_fill = Slic3r::ExtrusionPath::Collection->new), + ($fill_surfaces = Slic3r::Surface::Collection->new), + ); + $g->config->apply_dynamic($config); + $g->process; + + is scalar(@$loops), + scalar(@$expolygons), 'expected number of collections'; + ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @$loops), + 'everything is returned as collections'; + + my $flattened_loops = $loops->flatten; + my @loops = @$flattened_loops; + is scalar(@loops), + $expected{total}, 'expected number of loops'; + is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops), + $expected{external}, 'expected number of external loops'; + is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops), + $expected{cinternal}, 'expected number of internal contour loops'; + is scalar(grep $_->polygon->is_counter_clockwise, @loops), + $expected{ccw}, 'expected number of ccw loops'; + }; + + $config->set('perimeters', 3); + $test->( + [ + Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), + ), + ], + total => 3, + external => 1, + cinternal => 1, + ccw => 3, + ); + $test->( + [ + Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), + Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]), + ), + ], + total => 6, + external => 2, + cinternal => 1, + ccw => 3, + ); + $test->( + [ + Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]), + Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]), + ), + # nested: + Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]), + Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]), + ), + ], + total => 4*3, + external => 4, + cinternal => 2, + ccw => 2*3, + ); +} + { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); @@ -215,7 +310,7 @@ use Slic3r::Test; [ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ], ); - if (0) { + if (1) { printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered); require "Slic3r/SVG.pm"; Slic3r::SVG::output( @@ -223,9 +318,12 @@ use Slic3r::Test; expolygons => [ map $_->expolygon, @{$layerm->slices} ], red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]), green_expolygons => union_ex($non_covered), + no_arrows => 1, + polylines => [ + map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters}, + ], ); } - ok !(defined first { $_->area > ($iflow->scaled_width**2) } @$non_covered), 'no gap between perimeters and infill'; } @@ -299,92 +397,4 @@ use Slic3r::Test; $test->('small_dorito'); } -{ - my $flow = Slic3r::Flow->new( - width => 1, - height => 1, - nozzle_diameter => 1, - ); - - my $config = Slic3r::Config->new; - my $test = sub { - my ($expolygons, %expected) = @_; - - my $slices = Slic3r::Surface::Collection->new; - $slices->append(Slic3r::Surface->new( - surface_type => S_TYPE_INTERNAL, - expolygon => $_, - )) for @$expolygons; - - my $g = Slic3r::Layer::PerimeterGenerator->new( - # input: - layer_height => 1, - slices => $slices, - flow => $flow, - ); - $g->config->apply_dynamic($config); - $g->process; - - is scalar(@{$g->loops}), - scalar(@$expolygons), 'expected number of collections'; - ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @{$g->loops}), - 'everything is returned as collections'; - - my $flattened_loops = $g->loops->flatten; - my @loops = @$flattened_loops; - is scalar(@loops), - $expected{total}, 'expected number of loops'; - is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops), - $expected{external}, 'expected number of external loops'; - is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops), - $expected{cinternal}, 'expected number of internal contour loops'; - is scalar(grep $_->polygon->is_counter_clockwise, @loops), - $expected{ccw}, 'expected number of ccw loops'; - - return $g; - }; - - $config->set('perimeters', 3); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), - ), - ], - total => 3, - external => 1, - cinternal => 1, - ccw => 3, - ); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), - Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]), - ), - ], - total => 6, - external => 2, - cinternal => 1, - ccw => 3, - ); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]), - Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]), - ), - # nested: - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]), - Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]), - ), - ], - total => 4*3, - external => 4, - cinternal => 2, - ccw => 2*3, - ); -} - __END__ diff --git a/xs/MANIFEST b/xs/MANIFEST index 2011b9cf35..36ac111f6d 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1690,6 +1690,8 @@ src/libslic3r/MotionPlanner.cpp src/libslic3r/MotionPlanner.hpp src/libslic3r/MultiPoint.cpp src/libslic3r/MultiPoint.hpp +src/libslic3r/PerimeterGenerator.cpp +src/libslic3r/PerimeterGenerator.hpp src/libslic3r/PlaceholderParser.cpp src/libslic3r/PlaceholderParser.hpp src/libslic3r/Point.cpp @@ -1774,6 +1776,7 @@ xsp/Model.xsp xsp/MotionPlanner.xsp xsp/my.map xsp/mytype.map +xsp/PerimeterGenerator.xsp xsp/PlaceholderParser.xsp xsp/Point.xsp xsp/Polygon.xsp diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index ba243ee825..baddd8f456 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -136,6 +136,15 @@ offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float d ClipperPaths_to_Slic3rMultiPoints(output, retval); } +Slic3r::Polygons +offset(const Slic3r::Polygons &polygons, const float delta, + double scale, ClipperLib::JoinType joinType, double miterLimit) +{ + Slic3r::Polygons pp; + offset(polygons, &pp, delta, scale, joinType, miterLimit); + return pp; +} + void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) @@ -248,6 +257,15 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float ClipperPaths_to_Slic3rMultiPoints(output, retval); } +Slic3r::Polygons +offset2(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +{ + Slic3r::Polygons pp; + offset2(polygons, &pp, delta1, delta2, scale, joinType, miterLimit); + return pp; +} + void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) @@ -260,6 +278,15 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const floa ClipperPaths_to_Slic3rExPolygons(output, retval); } +Slic3r::ExPolygons +offset2_ex(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +{ + Slic3r::ExPolygons expp; + offset2(polygons, &expp, delta1, delta2, scale, joinType, miterLimit); + return expp; +} + template void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T* retval, const ClipperLib::PolyFillType fillType, const bool safety_offset_) @@ -437,6 +464,22 @@ void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType } template void diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); +Slic3r::Polygons +diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::Polygons pp; + diff(subject, clip, &pp, safety_offset_); + return pp; +} + +Slic3r::ExPolygons +diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::ExPolygons expp; + diff(subject, clip, &expp, safety_offset_); + return expp; +} + template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_) { @@ -448,6 +491,22 @@ template void intersection(const Slic3r::Po template void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); template void intersection(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); +Slic3r::Polygons +intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::Polygons pp; + intersection(subject, clip, &pp, safety_offset_); + return pp; +} + +Slic3r::Polylines +intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::Polylines pp; + intersection(subject, clip, &pp, safety_offset_); + return pp; +} + template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) { @@ -474,6 +533,22 @@ void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_) template void union_(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool safety_offset_); template void union_(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_); +Slic3r::Polygons +union_(const Slic3r::Polygons &subject, bool safety_offset) +{ + Polygons pp; + union_(subject, &pp, safety_offset); + return pp; +} + +Slic3r::ExPolygons +union_ex(const Slic3r::Polygons &subject, bool safety_offset) +{ + ExPolygons expp; + union_(subject, &expp, safety_offset); + return expp; +} + void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset) { Polygons pp = subject1; diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index ab144f202b..4a3ba2e5c4 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -40,6 +40,9 @@ void offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const f void offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, + double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double miterLimit = 3); // offset Polylines void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, @@ -62,9 +65,15 @@ void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const void offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1, const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double miterLimit = 3); void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double miterLimit = 3); template void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, @@ -86,9 +95,15 @@ void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* template void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false); +Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); + template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Polylines intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); + template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); @@ -98,6 +113,9 @@ void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r: template void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_ = false); +Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset = false); +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset = false); + void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset = false); void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_ = false); diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 83e1929aaa..ee0473656c 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -187,16 +187,17 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) } // compute the Voronoi diagram - ma.build(polylines); + Polylines pp; + ma.build(&pp); // clip segments to our expolygon area // (do this before extending endpoints as external segments coule be extended into // expolygon, this leaving wrong things inside) - intersection(*polylines, *this, polylines); + pp = intersection(pp, *this); // extend initial and final segments of each polyline (they will be clipped) // unless they represent closed loops - for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) { + for (Polylines::iterator polyline = pp.begin(); polyline != pp.end(); ++polyline) { if (polyline->points.front().distance_to(polyline->points.back()) < min_width) continue; // TODO: we should *not* extend endpoints where other polylines start/end // (such as T joints, which are returned as three polylines by MedialAxis) @@ -205,18 +206,20 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) } // clip again after extending endpoints to prevent them from exceeding the expolygon boundaries - intersection(*polylines, *this, polylines); + pp = intersection(pp, *this); // remove too short polylines // (we can't do this check before endpoints extension and clipping because we don't // know how long will the endpoints be extended since it depends on polygon thickness // which is variable - extension will be <= max_width/2 on each side) - for (size_t i = 0; i < polylines->size(); ++i) { - if ((*polylines)[i].length() < max_width) { - polylines->erase(polylines->begin() + i); + for (size_t i = 0; i < pp.size(); ++i) { + if (pp[i].length() < max_width) { + pp.erase(pp.begin() + i); --i; } } + + polylines->insert(polylines->end(), pp.begin(), pp.end()); } void diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 0cbabd1b88..c1eef6af34 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -127,7 +127,7 @@ class ExtrusionLoop : public ExtrusionEntity Polygons grow() const; double min_mm3_per_mm() const; Polyline as_polyline() const { - return this->polygon()->split_at_first_point(); + return this->polygon().split_at_first_point(); }; }; diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index 5904252ff8..eb77aaede3 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -8,7 +8,7 @@ namespace Slic3r { ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionEntityCollection& collection) : no_sort(collection.no_sort), orig_indices(collection.orig_indices) { - this->append(collection); + this->append(collection.entities); } ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths) @@ -32,6 +32,12 @@ ExtrusionEntityCollection::swap (ExtrusionEntityCollection &c) std::swap(this->no_sort, c.no_sort); } +ExtrusionEntityCollection::~ExtrusionEntityCollection() +{ + for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) + delete *it; +} + ExtrusionEntityCollection::operator ExtrusionPaths() const { ExtrusionPaths paths; @@ -45,7 +51,11 @@ ExtrusionEntityCollection::operator ExtrusionPaths() const ExtrusionEntityCollection* ExtrusionEntityCollection::clone() const { - return new ExtrusionEntityCollection(*this); + ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this); + for (size_t i = 0; i < coll->entities.size(); ++i) { + coll->entities[i] = this->entities[i]->clone(); + } + return coll; } void @@ -78,9 +88,10 @@ ExtrusionEntityCollection::append(const ExtrusionEntity &entity) } void -ExtrusionEntityCollection::append(const ExtrusionEntityCollection &collection) +ExtrusionEntityCollection::append(const ExtrusionEntitiesPtr &entities) { - this->entities.insert(this->entities.end(), collection.entities.begin(), collection.entities.end()); + for (ExtrusionEntitiesPtr::const_iterator ptr = entities.begin(); ptr != entities.end(); ++ptr) + this->append(**ptr); } void @@ -186,7 +197,7 @@ ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if ((*it)->is_collection()) { ExtrusionEntityCollection* collection = dynamic_cast(*it); - retval->append(collection->flatten()); + retval->append(collection->flatten().entities); } else { retval->append(**it); } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 1cb288d0a6..504c82ae19 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -10,13 +10,14 @@ class ExtrusionEntityCollection : public ExtrusionEntity { public: ExtrusionEntityCollection* clone() const; - ExtrusionEntitiesPtr entities; + ExtrusionEntitiesPtr entities; // we own these entities std::vector orig_indices; // handy for XS bool no_sort; ExtrusionEntityCollection(): no_sort(false) {}; ExtrusionEntityCollection(const ExtrusionEntityCollection &collection); ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other); + ~ExtrusionEntityCollection(); operator ExtrusionPaths() const; bool is_collection() const { @@ -25,9 +26,12 @@ class ExtrusionEntityCollection : public ExtrusionEntity bool can_reverse() const { return !this->no_sort; }; + bool empty() const { + return this->entities.empty(); + }; void swap (ExtrusionEntityCollection &c); void append(const ExtrusionEntity &entity); - void append(const ExtrusionEntityCollection &collection); + void append(const ExtrusionEntitiesPtr &entities); void append(const ExtrusionPaths &paths); ExtrusionEntityCollection chained_path(bool no_reverse = false, std::vector* orig_indices = NULL) const; void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector* orig_indices = NULL) const; @@ -40,6 +44,10 @@ class ExtrusionEntityCollection : public ExtrusionEntity void flatten(ExtrusionEntityCollection* retval) const; ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; + Polyline as_polyline() const { + CONFESS("Calling as_polyline() on a ExtrusionEntityCollection"); + return Polyline(); + }; }; } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index a03598db7b..f95930e2de 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -1,4 +1,6 @@ #include "PerimeterGenerator.hpp" +#include "ClipperUtils.hpp" +#include "ExtrusionEntityCollection.hpp" namespace Slic3r { @@ -12,14 +14,14 @@ PerimeterGenerator::process() // external perimeters this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); - coord_t = ext_pwidth = this->ext_perimeter_flow.scaled_width(); - coord_t = ext_pspacing = scale_(this->ext_perimeter_flow.spacing_to(this->perimeter_flow)); + coord_t ext_pwidth = this->ext_perimeter_flow.scaled_width(); + coord_t ext_pspacing = scale_(this->ext_perimeter_flow.spacing(this->perimeter_flow)); // overhang perimeters this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); // solid infill - coord_t ispacing = this->solid_infill_flow->scaled_spacing; + coord_t ispacing = this->solid_infill_flow.scaled_spacing(); coord_t gap_area_threshold = pwidth * pwidth; // Calculate the minimum required spacing between two adjacent traces. @@ -37,7 +39,7 @@ PerimeterGenerator::process() // in the current layer double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); - this->_lower_slices_p = offset(this->lower_slices, scale_(+nozzle_diameter/2)); + this->_lower_slices_p = offset(*this->lower_slices, scale_(+nozzle_diameter/2)); } // we need to process each island separately because we might have different @@ -53,8 +55,8 @@ PerimeterGenerator::process() Polygons last = surface->expolygon.simplify_p(SCALED_RESOLUTION); if (loop_number >= 0) { // no loops = -1 - std::vector contours(loop_number); // depth => loops - std::vector holes(loop_number); // depth => loops + std::vector contours(loop_number+1); // depth => loops + std::vector holes(loop_number+1); // depth => loops Polylines thin_walls; // we loop one time more than needed in order to find gaps after the last perimeter was applied @@ -65,7 +67,7 @@ PerimeterGenerator::process() // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 if (this->config->thin_walls) { offsets = offset2( - \@last, + last, -(0.5*ext_pwidth + 0.5*ext_min_spacing - 1), +(0.5*ext_min_spacing - 1) ); @@ -75,7 +77,7 @@ PerimeterGenerator::process() // look for thin walls if (this->config->thin_walls) { - Polygons diff = diff( + Polygons diffpp = diff( last, offset(offsets, +0.5*ext_pwidth), true // medial axis requires non-overlapping geometry @@ -84,7 +86,7 @@ PerimeterGenerator::process() // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = ext_pwidth / 2; - ExPolygons expp = offset2(diff, -min_width/2, +min_width/2)}; + ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop Polylines pp; @@ -121,27 +123,29 @@ PerimeterGenerator::process() offsets = offset2( last, -(distance + 0.5*min_spacing - 1), - +(0.5*min_spacing - 1), + +(0.5*min_spacing - 1) ); } else { offsets = offset( last, - -distance, + -distance ); } // look for gaps - if (this->config->gap_fill_speed > 0 && this->config->fill_density > 0) { + if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0) { // not using safety offset here would "detect" very narrow gaps // (but still long enough to escape the area threshold) that gap fill // won't be able to fill but we'd still remove from infill area - ExPolygons diff = diff_ex( + ExPolygons diff_expp = diff_ex( offset(last, -0.5*distance), - offset(offsets, +0.5*distance + 10), // safety offset + offset(offsets, +0.5*distance + 10) // safety offset ); - for (ExPolygons::const_iterator ex = diff.begin(); ex != diff.end(); ++ex) { - if (fabs(ex->area()) >= gap_area_threshold) - gaps.push_back(*ex); + for (ExPolygons::const_iterator ex = diff_expp.begin(); ex != diff_expp.end(); ++ex) { + if (fabs(ex->area()) >= gap_area_threshold) { + Polygons pp = *ex; + gaps.insert(gaps.end(), pp.begin(), pp.end()); + } } } } @@ -150,7 +154,6 @@ PerimeterGenerator::process() if (i > loop_number) break; // we were only looking for gaps this time last = offsets; - for (Polygons::const_iterator polygon = offsets.begin(); polygon != offsets.end(); ++polygon) { PerimeterGeneratorLoop loop(*polygon, i); loop.is_contour = polygon->is_counter_clockwise(); @@ -163,7 +166,7 @@ PerimeterGenerator::process() } // nest loops: holes first - for (unsigned short d = 0; <= loop_number; ++d) { + for (unsigned short d = 0; d <= loop_number; ++d) { PerimeterGeneratorLoops &holes_d = holes[d]; // loop through all holes having depth == d @@ -172,13 +175,13 @@ PerimeterGenerator::process() // find the hole loop that contains this one, if any for (unsigned short t = d+1; t <= loop_number; ++t) { - for (unsigned short j = 0; j < holes_d.size(); ++j) { + for (unsigned short j = 0; j < holes[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.add_child(loop); + candidate_parent.children.push_back(loop); holes_d.erase(holes_d.begin() + i); --i; - goto NEXT_HOLE; + goto NEXT_LOOP; } } } @@ -188,15 +191,15 @@ PerimeterGenerator::process() for (unsigned short j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.add_child(loop); + candidate_parent.children.push_back(loop); holes_d.erase(holes_d.begin() + i); --i; - goto NEXT_HOLE; + goto NEXT_LOOP; } } } + NEXT_LOOP: ; } - NEXT_HOLE: } // nest contour loops @@ -209,10 +212,10 @@ PerimeterGenerator::process() // find the contour loop that contains it for (unsigned short t = d-1; t >= 0; --t) { - for (unsigned short j = 0; j < contours_d[t].size(); ++j) { + for (unsigned short j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.add_child(loop); + candidate_parent.children.push_back(loop); contours_d.erase(contours_d.begin() + i); --i; goto NEXT_CONTOUR; @@ -220,7 +223,7 @@ PerimeterGenerator::process() } } - NEXT_CONTOUR: + NEXT_CONTOUR: ; } } @@ -232,7 +235,7 @@ PerimeterGenerator::process() // we continue inwards after having finished the brim // TODO: add test for perimeter order if (this->config->external_perimeters_first - || (this->layer_id == 0 && this->print_config->brim_width > 0)) + || (this->layer_id == 0 && this->print_config->brim_width.value > 0)) entities.reverse(); // append perimeters for this slice as a collection @@ -260,8 +263,9 @@ PerimeterGenerator::process() for (std::vector::const_iterator gap_size = gap_sizes.begin(); gap_size != gap_sizes.end(); ++gap_size) { - ExtrusionEntityCollection gap_fill = this->_fill_gaps(gap_size.min, gap_size.max, gap_size.width); - this->gap_fill->append(gap_fill); + ExtrusionEntityCollection gap_fill = this->_fill_gaps(gap_size->min, + gap_size->max, gap_size->width, gaps); + this->gap_fill->append(gap_fill.entities); // Make sure we don't infill narrow parts that are already gap-filled // (we only consider this surface's gaps to reduce the diff() complexity). @@ -298,7 +302,7 @@ PerimeterGenerator::process() inset -= this->config->get_abs_value("infill_overlap", inset + ispacing/2); { - ExPolygons expp = union_(last); + ExPolygons expp = union_ex(last); // simplify infill contours according to resolution Polygons pp; @@ -307,10 +311,10 @@ PerimeterGenerator::process() // collapse too narrow infill areas coord_t min_perimeter_infill_spacing = ispacing * (1 - INSET_OVERLAP_TOLERANCE); - expp = offset2( + expp = offset2_ex( pp, -inset -min_perimeter_infill_spacing/2, - +min_perimeter_infill_spacing/2, + +min_perimeter_infill_spacing/2 ); // append infill areas to fill_surfaces @@ -322,7 +326,7 @@ PerimeterGenerator::process() ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, - const Polylines &thin_walls) const + Polylines &thin_walls) const { // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object @@ -346,17 +350,17 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, // detect overhanging/bridging perimeters ExtrusionPaths paths; if (this->config->overhangs && this->layer_id > 0 - && !(this->object_config->support_material && this->object_config->support_material_contact_distance == 0)) { + && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) { // get non-overhang paths by intersecting this loop with the grown lower slices { Polylines polylines; - intersection(loop->polygon(), this->_lower_slices_p, &polylines); + intersection((Polygons)loop->polygon, this->_lower_slices_p, &polylines); for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { ExtrusionPath path(role); path.polyline = *polyline; path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; - path.width = is_external ? this->ext_perimeter_flow->width : this->perimeter_flow.width; + path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; path.height = this->layer_height; paths.push_back(path); } @@ -367,7 +371,7 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, // the loop centerline and original lower slices is >= half nozzle diameter { Polylines polylines; - diff(loop->polygon(), this->_lower_slices_p, &polylines); + diff((Polygons)loop->polygon, this->_lower_slices_p, &polylines); for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { ExtrusionPath path(erOverhangPerimeter); @@ -385,10 +389,11 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, paths = ExtrusionEntityCollection(paths).chained_path(); } else { ExtrusionPath path(role); - path.polyline = loop.polygon().split_at_first_point(); + path.polyline = loop->polygon.split_at_first_point(); path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; - path.width = is_external ? this->ext_perimeter_flow->width : this->perimeter_flow.width; + path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; path.height = this->layer_height; + paths.push_back(path); } coll.append(ExtrusionLoop(paths, loop_role)); @@ -409,11 +414,11 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, } // sort entities - ExtrusionPathCollection sorted_coll; + ExtrusionEntityCollection sorted_coll; coll.chained_path(&sorted_coll, false, &sorted_coll.orig_indices); // traverse children - ExtrusionPathCollection entities; + ExtrusionEntityCollection entities; for (unsigned short i = 0; i < sorted_coll.orig_indices.size(); ++i) { size_t idx = sorted_coll.orig_indices[i]; if (idx >= loops.size()) { @@ -421,19 +426,18 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, // let's get it from the sorted collection as it might have been reversed entities.append(*sorted_coll.entities[i]); } else { - PerimeterGeneratorLoop &loop = loops[i]; - ExtrusionLoop eloop = *coll.entities[idx]; + const PerimeterGeneratorLoop &loop = loops[i]; + ExtrusionLoop eloop = *dynamic_cast(coll.entities[idx]); - ExtrusionEntityCollection children = this->_traverse_loops(loop->children, thin_walls); - if (loop->is_contour()) { + ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls); + if (loop.is_contour) { eloop.make_counter_clockwise(); - entities.append(children); - entities.append(elooop); + entities.append(children.entities); + entities.append(eloop); } else { eloop.make_clockwise(); - push @entities, $eloop, @children; - entities.append(elooop); - entities.append(children); + entities.append(eloop); + entities.append(children.entities); } } } @@ -448,10 +452,10 @@ PerimeterGenerator::_fill_gaps(double min, double max, double w, min *= (1 - INSET_OVERLAP_TOLERANCE); - ExPolygon curr = diff( + ExPolygons curr = diff_ex( offset2(gaps, -min/2, +min/2), offset2(gaps, -max/2, +max/2), - true, + true ); Polylines polylines; @@ -462,7 +466,7 @@ PerimeterGenerator::_fill_gaps(double min, double max, double w, #ifdef SLIC3R_DEBUG if (!curr.empty()) - printf(" %d gaps filled with extrusion width = %zu\n", curr.size(), w); + printf(" %zu gaps filled with extrusion width = %f\n", curr.size(), w); #endif //my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w); diff --git a/xs/src/libslic3r/PerimeterGenerator.hpp b/xs/src/libslic3r/PerimeterGenerator.hpp index f5a3a6dd91..6465d63f9f 100644 --- a/xs/src/libslic3r/PerimeterGenerator.hpp +++ b/xs/src/libslic3r/PerimeterGenerator.hpp @@ -2,6 +2,12 @@ #define slic3r_PerimeterGenerator_hpp_ #include +#include +#include "ExPolygonCollection.hpp" +#include "Flow.hpp" +#include "Polygon.hpp" +#include "PrintConfig.hpp" +#include "SurfaceCollection.hpp" namespace Slic3r { @@ -25,7 +31,7 @@ class PerimeterGeneratorLoop { class PerimeterGenerator { public: SurfaceCollection* slices; - SurfaceCollection* lower_slices; + ExPolygonCollection* lower_slices; double layer_height; int layer_id; Flow perimeter_flow; @@ -39,13 +45,15 @@ class PerimeterGenerator { ExtrusionEntityCollection* gap_fill; SurfaceCollection* fill_surfaces; - PerimeterGenerator(SurfaceCollection* slices, double layer_height, + PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow flow, PrintRegionConfig* config, PrintObjectConfig* object_config, PrintConfig* print_config, ExtrusionEntityCollection* loops, ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces) - : slices(slices), lower_slices(NULL), layer_height(layer_height), layer_id(-1), + : slices(slices), lower_slices(NULL), layer_height(layer_height), + perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow), + solid_infill_flow(flow), layer_id(-1), config(config), object_config(object_config), print_config(print_config), - loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces) + loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1) {}; void process(); @@ -57,7 +65,7 @@ class PerimeterGenerator { Polygons _lower_slices_p; ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, - const Polylines &thin_walls) const; + Polylines &thin_walls) const; ExtrusionEntityCollection _fill_gaps(double min, double max, double w, const Polygons &gaps) const; }; @@ -67,7 +75,7 @@ class PerimeterGeneratorGapSize { coord_t min; coord_t max; coord_t width; - PerimeterGeneratorGapSizes(coord_t min, coord_t max, coord_t width) + PerimeterGeneratorGapSize(coord_t min, coord_t max, coord_t width) : min(min), max(max), width(width) {}; }; diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp index b7e439479b..7570d078ae 100644 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ b/xs/xsp/ExtrusionEntityCollection.xsp @@ -7,6 +7,7 @@ %name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { %name{_new} ExtrusionEntityCollection(); + ~ExtrusionEntityCollection(); Clone clone() %code{% RETVAL = THIS->clone(); %}; void reverse(); @@ -41,14 +42,6 @@ Polygons grow(); %{ -void -ExtrusionEntityCollection::DESTROY() - CODE: - for (ExtrusionEntitiesPtr::iterator it = THIS->entities.begin(); it != THIS->entities.end(); ++it) { - delete *it; - } - delete THIS; - SV* ExtrusionEntityCollection::arrayref() CODE: diff --git a/xs/xsp/PerimeterGenerator.xsp b/xs/xsp/PerimeterGenerator.xsp new file mode 100644 index 0000000000..351c6f4c0b --- /dev/null +++ b/xs/xsp/PerimeterGenerator.xsp @@ -0,0 +1,34 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/PerimeterGenerator.hpp" +%} + +%name{Slic3r::Layer::PerimeterGenerator} class PerimeterGenerator { + PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow* flow, + PrintRegionConfig* config, PrintObjectConfig* object_config, + PrintConfig* print_config, ExtrusionEntityCollection* loops, + ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces) + %code{% RETVAL = new PerimeterGenerator(slices, layer_height, *flow, + config, object_config, print_config, loops, gap_fill, fill_surfaces); %}; + ~PerimeterGenerator(); + + void set_lower_slices(ExPolygonCollection* lower_slices) + %code{% THIS->lower_slices = lower_slices; %}; + void set_layer_id(int layer_id) + %code{% THIS->layer_id = layer_id; %}; + void set_perimeter_flow(Flow* flow) + %code{% THIS->perimeter_flow = *flow; %}; + void set_ext_perimeter_flow(Flow* flow) + %code{% THIS->ext_perimeter_flow = *flow; %}; + void set_overhang_flow(Flow* flow) + %code{% THIS->overhang_flow = *flow; %}; + void set_solid_infill_flow(Flow* flow) + %code{% THIS->solid_infill_flow = *flow; %}; + + Ref config() + %code{% RETVAL = THIS->config; %}; + + void process(); +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index af6f9f0427..1f59b3fa07 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -198,6 +198,10 @@ BridgeDetector* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +PerimeterGenerator* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + GLVertexArray* O_OBJECT_SLIC3R Axis T_UV diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 8b4eb08507..6c7beb17fb 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -94,6 +94,12 @@ %typemap{BridgeDetector*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{SurfaceCollection*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; +%typemap{PerimeterGenerator*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; %typemap{Surface*}; %typemap{Ref}{simple}; @@ -153,7 +159,6 @@ %typemap{Polygons*}; %typemap{TriangleMesh*}; %typemap{TriangleMeshPtrs}; -%typemap{Ref}{simple}; %typemap{Extruder*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; From 6ac79e3ed6bf903edf45b52ee6fd68c1b0b54a9d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 23 Jul 2015 16:27:21 +0200 Subject: [PATCH 79/83] Ported make_perimeters() to C++ --- lib/Slic3r/Layer/Region.pm | 30 ----------------- t/perimeters.t | 2 +- .../libslic3r/ExtrusionEntityCollection.hpp | 3 ++ xs/src/libslic3r/Layer.hpp | 1 + xs/src/libslic3r/LayerRegion.cpp | 33 +++++++++++++++++++ xs/src/libslic3r/PerimeterGenerator.hpp | 6 ++-- xs/xsp/Layer.xsp | 2 ++ 7 files changed, 43 insertions(+), 34 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 51648c3ed2..09e4a9fc81 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -25,36 +25,6 @@ sub print { return $_[0]->layer->print; } sub config { return $_[0]->region->config; } -sub make_perimeters { - my ($self, $slices, $fill_surfaces) = @_; - - $self->perimeters->clear; - $self->thin_fills->clear; - - my $generator = Slic3r::Layer::PerimeterGenerator->new( - # input: - $slices, - $self->height, - $self->flow(FLOW_ROLE_PERIMETER), - $self->config, - $self->layer->object->config, - $self->layer->print->config, - - # output: - $self->perimeters, - $self->thin_fills, - $fill_surfaces, - ); - $generator->set_lower_slices($self->layer->lower_layer->slices) - if defined($self->layer->lower_layer); - $generator->set_layer_id($self->id); - $generator->set_ext_perimeter_flow($self->flow(FLOW_ROLE_EXTERNAL_PERIMETER)); - $generator->set_overhang_flow($self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object)); - $generator->set_solid_infill_flow($self->flow(FLOW_ROLE_SOLID_INFILL)); - - $generator->process; -} - sub process_external_surfaces { my ($self, $lower_layer) = @_; diff --git a/t/perimeters.t b/t/perimeters.t index 1e47b51f09..134c235362 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -310,7 +310,7 @@ use Slic3r::Test; [ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ], ); - if (1) { + if (0) { printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered); require "Slic3r/SVG.pm"; Slic3r::SVG::output( diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 504c82ae19..62909c55c7 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -29,6 +29,9 @@ class ExtrusionEntityCollection : public ExtrusionEntity bool empty() const { return this->entities.empty(); }; + void clear() { + this->entities.clear(); + }; void swap (ExtrusionEntityCollection &c); void append(const ExtrusionEntity &entity); void append(const ExtrusionEntitiesPtr &entities); diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 85f4844cb5..1c09b5f0f0 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -57,6 +57,7 @@ class LayerRegion Flow flow(FlowRole role, bool bridge = false, double width = -1) const; void merge_slices(); void prepare_fill_surfaces(); + void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); private: Layer *_layer; diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index d09ea19c73..ef6150015e 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -1,5 +1,6 @@ #include "Layer.hpp" #include "ClipperUtils.hpp" +#include "PerimeterGenerator.hpp" #include "Print.hpp" #include "Surface.hpp" @@ -53,6 +54,38 @@ LayerRegion::merge_slices() this->slices.surfaces.push_back(Surface(stInternal, *expoly)); } +void +LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) +{ + this->perimeters.clear(); + this->thin_fills.clear(); + + PerimeterGenerator g( + // input: + &slices, + this->layer()->height, + this->flow(frPerimeter), + &this->region()->config, + &this->layer()->object()->config, + &this->layer()->object()->print()->config, + + // output: + &this->perimeters, + &this->thin_fills, + fill_surfaces + ); + + if (this->layer()->lower_layer != NULL) + g.lower_slices = &this->layer()->lower_layer->slices; + + g.layer_id = this->layer()->id(); + g.ext_perimeter_flow = this->flow(frExternalPerimeter); + g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object()); + g.solid_infill_flow = this->flow(frSolidInfill); + + g.process(); +} + void LayerRegion::prepare_fill_surfaces() { diff --git a/xs/src/libslic3r/PerimeterGenerator.hpp b/xs/src/libslic3r/PerimeterGenerator.hpp index 6465d63f9f..8ce5f87d97 100644 --- a/xs/src/libslic3r/PerimeterGenerator.hpp +++ b/xs/src/libslic3r/PerimeterGenerator.hpp @@ -30,8 +30,8 @@ class PerimeterGeneratorLoop { class PerimeterGenerator { public: - SurfaceCollection* slices; - ExPolygonCollection* lower_slices; + const SurfaceCollection* slices; + const ExPolygonCollection* lower_slices; double layer_height; int layer_id; Flow perimeter_flow; @@ -45,7 +45,7 @@ class PerimeterGenerator { ExtrusionEntityCollection* gap_fill; SurfaceCollection* fill_surfaces; - PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow flow, + PerimeterGenerator(const SurfaceCollection* slices, double layer_height, Flow flow, PrintRegionConfig* config, PrintObjectConfig* object_config, PrintConfig* print_config, ExtrusionEntityCollection* loops, ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces) diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index d8444f0921..00913e615e 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -30,6 +30,8 @@ %code%{ RETVAL = THIS->flow(role, bridge, width); %}; void merge_slices(); void prepare_fill_surfaces(); + void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) + %code%{ THIS->make_perimeters(*slices, fill_surfaces); %}; }; %name{Slic3r::Layer} class Layer { From 5b8ed7367a2a9148c215eafa53c7e13b30cd0c36 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 6 Aug 2015 10:07:13 +0200 Subject: [PATCH 80/83] Fixed potential hang in PerimeterGenerator.cpp --- xs/src/libslic3r/PerimeterGenerator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index f95930e2de..a968485318 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -47,7 +47,7 @@ PerimeterGenerator::process() for (Surfaces::const_iterator surface = this->slices->surfaces.begin(); surface != this->slices->surfaces.end(); ++surface) { // detect how many perimeters must be generated for this island - unsigned short loop_number = this->config->perimeters + surface->extra_perimeters; + short loop_number = this->config->perimeters + surface->extra_perimeters; loop_number--; // 0-indexed loops Polygons gaps; @@ -187,7 +187,7 @@ PerimeterGenerator::process() } // if no hole contains this hole, find the contour loop that contains it - for (unsigned short t = loop_number; t >= 0; --t) { + for (short t = loop_number; t >= 0; --t) { for (unsigned short j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { @@ -203,7 +203,7 @@ PerimeterGenerator::process() } // nest contour loops - for (unsigned short d = loop_number; d >= 1; --d) { + for (short d = loop_number; d >= 1; --d) { PerimeterGeneratorLoops &contours_d = contours[d]; // loop through all contours having depth == d @@ -211,7 +211,7 @@ PerimeterGenerator::process() const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it - for (unsigned short t = d-1; t >= 0; --t) { + for (short t = d-1; t >= 0; --t) { for (unsigned short j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { From 9fcec107373b6846f3775fe353c7ae29653cf9ac Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 Oct 2015 23:23:03 +0100 Subject: [PATCH 81/83] Finished porting LayerRegion to C++ --- lib/Slic3r.pm | 2 - lib/Slic3r/Fill.pm | 30 ++--- lib/Slic3r/Layer.pm | 25 ++-- lib/Slic3r/Layer/Region.pm | 103 ---------------- lib/Slic3r/Print/GCode.pm | 2 +- lib/Slic3r/Print/Object.pm | 25 ++-- lib/Slic3r/Print/SupportMaterial.pm | 4 +- xs/src/libslic3r/BridgeDetector.cpp | 36 +++--- xs/src/libslic3r/BridgeDetector.hpp | 4 +- xs/src/libslic3r/ClipperUtils.cpp | 22 +++- xs/src/libslic3r/ClipperUtils.hpp | 8 +- xs/src/libslic3r/ExPolygonCollection.cpp | 6 + xs/src/libslic3r/ExPolygonCollection.hpp | 1 + xs/src/libslic3r/Layer.hpp | 5 +- xs/src/libslic3r/LayerRegion.cpp | 148 +++++++++++++++++++++++ xs/src/libslic3r/PerimeterGenerator.cpp | 24 ++-- xs/src/libslic3r/PolylineCollection.cpp | 6 + xs/src/libslic3r/PolylineCollection.hpp | 1 + xs/src/libslic3r/Print.hpp | 1 + xs/src/libslic3r/PrintObject.cpp | 34 ++++-- xs/src/libslic3r/Surface.cpp | 5 + xs/src/libslic3r/Surface.hpp | 1 + xs/src/libslic3r/SurfaceCollection.cpp | 6 + xs/src/libslic3r/SurfaceCollection.hpp | 4 + xs/src/libslic3r/libslic3r.h | 1 + xs/xsp/BridgeDetector.xsp | 12 +- xs/xsp/Layer.xsp | 5 +- xs/xsp/Print.xsp | 1 + 28 files changed, 319 insertions(+), 203 deletions(-) delete mode 100644 lib/Slic3r/Layer/Region.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index bcd337dec7..2668f7229d 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -54,7 +54,6 @@ use Slic3r::GCode::VibrationLimit; use Slic3r::Geometry qw(PI); use Slic3r::Geometry::Clipper; use Slic3r::Layer; -use Slic3r::Layer::Region; use Slic3r::Line; use Slic3r::Model; use Slic3r::Point; @@ -77,7 +76,6 @@ use constant RESOLUTION => 0.0125; use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.3; -use constant EXTERNAL_INFILL_MARGIN => 3; # keep track of threads we created my @my_threads = (); diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index a39b77fc4b..88672a055b 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -48,9 +48,9 @@ sub make_fill { my $self = shift; my ($layerm) = @_; - Slic3r::debugf "Filling layer %d:\n", $layerm->id; + Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id; - my $fill_density = $layerm->config->fill_density; + my $fill_density = $layerm->region->config->fill_density; my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL); my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL); my $top_solid_infill_flow = $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL); @@ -74,13 +74,13 @@ sub make_fill { for (my $i = 0; $i <= $#groups; $i++) { # we can only merge solid non-bridge surfaces, so discard # non-solid surfaces - if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) { + if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->layer->id == 0)) { $is_solid[$i] = 1; $fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP) ? $top_solid_infill_flow->width : $solid_infill_flow->width; $pattern[$i] = $groups[$i][0]->is_external - ? $layerm->config->external_fill_pattern + ? $layerm->region->config->external_fill_pattern : 'rectilinear'; } else { $is_solid[$i] = 0; @@ -179,19 +179,19 @@ sub make_fill { my @fills = (); SURFACE: foreach my $surface (@surfaces) { next if $surface->surface_type == S_TYPE_INTERNALVOID; - my $filler = $layerm->config->fill_pattern; + my $filler = $layerm->region->config->fill_pattern; my $density = $fill_density; my $role = ($surface->surface_type == S_TYPE_TOP) ? FLOW_ROLE_TOP_SOLID_INFILL : $surface->is_solid ? FLOW_ROLE_SOLID_INFILL : FLOW_ROLE_INFILL; - my $is_bridge = $layerm->id > 0 && $surface->is_bridge; + my $is_bridge = $layerm->layer->id > 0 && $surface->is_bridge; my $is_solid = $surface->is_solid; if ($surface->is_solid) { $density = 100; $filler = 'rectilinear'; if ($surface->is_external && !$is_bridge) { - $filler = $layerm->config->external_fill_pattern; + $filler = $layerm->region->config->external_fill_pattern; } } else { next SURFACE unless $density > 0; @@ -201,14 +201,14 @@ sub make_fill { my $f = $self->filler($filler); # calculate the actual flow we'll be using for this infill - my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness; + my $h = $surface->thickness == -1 ? $layerm->layer->height : $surface->thickness; my $flow = $layerm->region->flow( $role, $h, $is_bridge || $f->use_bridge_flow, - $layerm->id == 0, + $layerm->layer->id == 0, -1, - $layerm->object, + $layerm->layer->object, ); # calculate flow spacing for infill pattern generation @@ -220,11 +220,11 @@ sub make_fill { # layer height my $internal_flow = $layerm->region->flow( FLOW_ROLE_INFILL, - $layerm->object->config->layer_height, # TODO: handle infill_every_layers? + $layerm->layer->object->config->layer_height, # TODO: handle infill_every_layers? 0, # no bridge 0, # no first layer -1, # auto width - $layerm->object, + $layerm->layer->object, ); $f->spacing($internal_flow->spacing); $using_internal_flow = 1; @@ -232,9 +232,9 @@ sub make_fill { $f->spacing($flow->spacing); } - $f->layer_id($layerm->id); - $f->z($layerm->print_z); - $f->angle(deg2rad($layerm->config->fill_angle)); + $f->layer_id($layerm->layer->id); + $f->z($layerm->layer->print_z); + $f->angle(deg2rad($layerm->region->config->fill_angle)); $f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); # apply half spacing using this flow's own spacing and generate infill diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 2e65c147e9..b16c743798 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -41,24 +41,25 @@ sub make_perimeters { for my $region_id (0..$#{$self->regions}) { next if $done{$region_id}; - my $layerm = $self->regions->[$region_id]; + my $layerm = $self->get_region($region_id); + my $config = $layerm->region->config; $done{$region_id} = 1; # find compatible regions my @layerms = ($layerm); for my $i (($region_id+1)..$#{$self->regions}) { - my $config = $self->regions->[$i]->config; - my $layerm_config = $layerm->config; + my $other_layerm = $self->get_region($i); + my $other_config = $other_layerm->region->config; - if ($config->perimeter_extruder == $layerm_config->perimeter_extruder - && $config->perimeters == $layerm_config->perimeters - && $config->perimeter_speed == $layerm_config->perimeter_speed - && $config->gap_fill_speed == $layerm_config->gap_fill_speed - && $config->overhangs == $layerm_config->overhangs - && $config->perimeter_extrusion_width == $layerm_config->perimeter_extrusion_width - && $config->thin_walls == $layerm_config->thin_walls - && $config->external_perimeters_first == $layerm_config->external_perimeters_first) { - push @layerms, $self->regions->[$i]; + if ($config->perimeter_extruder == $other_config->perimeter_extruder + && $config->perimeters == $other_config->perimeters + && $config->perimeter_speed == $other_config->perimeter_speed + && $config->gap_fill_speed == $other_config->gap_fill_speed + && $config->overhangs == $other_config->overhangs + && $config->perimeter_extrusion_width == $other_config->perimeter_extrusion_width + && $config->thin_walls == $other_config->thin_walls + && $config->external_perimeters_first == $other_config->external_perimeters_first) { + push @layerms, $other_layerm; $done{$i} = 1; } } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm deleted file mode 100644 index 09e4a9fc81..0000000000 --- a/lib/Slic3r/Layer/Region.pm +++ /dev/null @@ -1,103 +0,0 @@ -package Slic3r::Layer::Region; -use strict; -use warnings; - -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(scale); -use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex - ); -use Slic3r::Surface ':types'; - - -# TODO: lazy -sub infill_area_threshold { - my $self = shift; - return $self->flow(FLOW_ROLE_SOLID_INFILL)->scaled_spacing ** 2; -} - -sub id { return $_[0]->layer->id; } -sub slice_z { return $_[0]->layer->slice_z; } -sub print_z { return $_[0]->layer->print_z; } -sub height { return $_[0]->layer->height; } -sub object { return $_[0]->layer->object; } -sub print { return $_[0]->layer->print; } - -sub config { return $_[0]->region->config; } - -sub process_external_surfaces { - my ($self, $lower_layer) = @_; - - my @surfaces = @{$self->fill_surfaces}; - my $margin = scale &Slic3r::EXTERNAL_INFILL_MARGIN; - - my @bottom = (); - foreach my $surface (grep $_->is_bottom, @surfaces) { - my $grown = $surface->expolygon->offset_ex(+$margin); - - # detect bridge direction before merging grown surfaces otherwise adjacent bridges - # would get merged into a single one while they need different directions - # also, supply the original expolygon instead of the grown one, because in case - # of very thin (but still working) anchors, the grown expolygon would go beyond them - my $angle; - if ($lower_layer) { - my $bridge_detector = Slic3r::BridgeDetector->new( - $surface->expolygon, - $lower_layer->slices, - $self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width, - ); - Slic3r::debugf "Processing bridge at layer %d:\n", $self->id; - $bridge_detector->detect_angle; - $angle = $bridge_detector->angle; - - if (defined $angle && $self->object->config->support_material) { - $self->bridged->append(Slic3r::ExPolygon->new($_)) - for @{ $bridge_detector->coverage_by_angle($angle) }; - $self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges }; - } - } - - push @bottom, map $surface->clone(expolygon => $_, bridge_angle => $angle), @$grown; - } - - my @top = (); - foreach my $surface (grep $_->surface_type == S_TYPE_TOP, @surfaces) { - # give priority to bottom surfaces - my $grown = diff_ex( - $surface->expolygon->offset(+$margin), - [ map $_->p, @bottom ], - ); - push @top, map $surface->clone(expolygon => $_), @$grown; - } - - # if we're slicing with no infill, we can't extend external surfaces - # over non-existent infill - my @fill_boundaries = $self->config->fill_density > 0 - ? @surfaces - : grep $_->surface_type != S_TYPE_INTERNAL, @surfaces; - - # intersect the grown surfaces with the actual fill boundaries - my @new_surfaces = (); - foreach my $group (@{Slic3r::Surface::Collection->new(@top, @bottom)->group}) { - push @new_surfaces, - map $group->[0]->clone(expolygon => $_), - @{intersection_ex( - [ map $_->p, @$group ], - [ map $_->p, @fill_boundaries ], - 1, # to ensure adjacent expolygons are unified - )}; - } - - # subtract the new top surfaces from the other non-top surfaces and re-add them - my @other = grep $_->surface_type != S_TYPE_TOP && !$_->is_bottom, @surfaces; - foreach my $group (@{Slic3r::Surface::Collection->new(@other)->group}) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex( - [ map $_->p, @$group ], - [ map $_->p, @new_surfaces ], - )}; - } - $self->fill_surfaces->clear; - $self->fill_surfaces->append($_) for @new_surfaces; -} - -1; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index db900d6fc4..8db7e2d334 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -357,7 +357,7 @@ sub process_layer { $self->_spiral_vase->enable( ($layer->id > 0 || $self->print->config->brim_width == 0) && ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt) - && !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions}) + && !defined(first { $_->region->config->bottom_solid_layers > $layer->id } @{$layer->regions}) && !defined(first { $_->perimeters->items_count > 1 } @{$layer->regions}) && !defined(first { $_->fills->items_count > 0 } @{$layer->regions}) ); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 13f43df7ab..15094cb934 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -830,17 +830,6 @@ sub clip_fill_surfaces { } } -sub process_external_surfaces { - my ($self) = @_; - - for my $region_id (0 .. ($self->print->region_count-1)) { - $self->get_layer(0)->regions->[$region_id]->process_external_surfaces(undef); - for my $i (1 .. ($self->layer_count - 1)) { - $self->get_layer($i)->regions->[$region_id]->process_external_surfaces($self->get_layer($i-1)); - } - } -} - sub discover_horizontal_shells { my $self = shift; @@ -850,8 +839,8 @@ sub discover_horizontal_shells { for (my $i = 0; $i < $self->layer_count; $i++) { my $layerm = $self->get_layer($i)->regions->[$region_id]; - if ($layerm->config->solid_infill_every_layers && $layerm->config->fill_density > 0 - && ($i % $layerm->config->solid_infill_every_layers) == 0) { + if ($layerm->region->config->solid_infill_every_layers && $layerm->region->config->fill_density > 0 + && ($i % $layerm->region->config->solid_infill_every_layers) == 0) { $_->surface_type(S_TYPE_INTERNALSOLID) for @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)}; } @@ -873,8 +862,8 @@ sub discover_horizontal_shells { Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom'; my $solid_layers = ($type == S_TYPE_TOP) - ? $layerm->config->top_solid_layers - : $layerm->config->bottom_solid_layers; + ? $layerm->region->config->top_solid_layers + : $layerm->region->config->bottom_solid_layers; NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1; abs($n - $i) <= $solid_layers-1; ($type == S_TYPE_TOP) ? $n-- : $n++) { @@ -902,7 +891,7 @@ sub discover_horizontal_shells { ); next EXTERNAL if !@$new_internal_solid; - if ($layerm->config->fill_density == 0) { + if ($layerm->region->config->fill_density == 0) { # if we're printing a hollow object we discard any solid shell thinner # than a perimeter width, since it's probably just crossing a sloping wall # and it's not wanted in a hollow print even if it would make sense when @@ -1100,12 +1089,12 @@ sub combine_infill { )}; # apply surfaces back with adjusted depth to the uppermost layer - if ($layerm->id == $self->get_layer($layer_idx)->id) { + if ($layerm->layer->id == $self->get_layer($layer_idx)->id) { push @new_this_type, map Slic3r::Surface->new( expolygon => $_, surface_type => $type, - thickness => sum(map $_->height, @layerms), + thickness => sum(map $_->layer->height, @layerms), thickness_layers => scalar(@layerms), ), @$intersection; diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 0a18ebea29..eea6397afa 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -235,7 +235,7 @@ sub contact_area { # just remove bridged areas $diff = diff( $diff, - [ map @$_, @{$layerm->bridged} ], + $layerm->bridged, 1, ); } @@ -267,7 +267,7 @@ sub contact_area { # get the average nozzle diameter used on this layer my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_), map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1, $_->config->solid_infill_extruder-1 } - @{$layer->regions}; + map $_->region, @{$layer->regions}; my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; my $contact_z = $layer->print_z - $self->contact_distance($layer->height, $nozzle_diameter); diff --git a/xs/src/libslic3r/BridgeDetector.cpp b/xs/src/libslic3r/BridgeDetector.cpp index da59a04d88..1dddf81ce0 100644 --- a/xs/src/libslic3r/BridgeDetector.cpp +++ b/xs/src/libslic3r/BridgeDetector.cpp @@ -185,16 +185,12 @@ BridgeDetector::detect_angle() return true; } -void -BridgeDetector::coverage(Polygons* coverage) const -{ - if (this->angle == -1) return; - return this->coverage(angle, coverage); -} - void BridgeDetector::coverage(double angle, Polygons* coverage) const { + if (angle == -1) angle = this->angle; + if (angle == -1) return; + // Clone our expolygon and rotate it so that we work with vertical lines. ExPolygon expolygon = this->expolygon; expolygon.rotate(PI/2.0 - angle, Point(0,0)); @@ -263,19 +259,23 @@ BridgeDetector::coverage(double angle, Polygons* coverage) const */ } +Polygons +BridgeDetector::coverage(double angle) const +{ + Polygons pp; + this->coverage(angle, &pp); + return pp; +} + /* This method returns the bridge edges (as polylines) that are not supported but would allow the entire bridge area to be bridged with detected angle if supported too */ -void -BridgeDetector::unsupported_edges(Polylines* unsupported) const -{ - if (this->angle == -1) return; - return this->unsupported_edges(this->angle, unsupported); -} - void BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const { + if (angle == -1) angle = this->angle; + if (angle == -1) return; + // get bridge edges (both contour and holes) Polylines bridge_edges; { @@ -319,6 +319,14 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const */ } +Polylines +BridgeDetector::unsupported_edges(double angle) const +{ + Polylines pp; + this->unsupported_edges(angle, &pp); + return pp; +} + #ifdef SLIC3RXS REGISTER_CLASS(BridgeDetector, "BridgeDetector"); #endif diff --git a/xs/src/libslic3r/BridgeDetector.hpp b/xs/src/libslic3r/BridgeDetector.hpp index ab25712895..c3af095429 100644 --- a/xs/src/libslic3r/BridgeDetector.hpp +++ b/xs/src/libslic3r/BridgeDetector.hpp @@ -18,10 +18,10 @@ class BridgeDetector { BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); bool detect_angle(); - void coverage(Polygons* coverage) const; void coverage(double angle, Polygons* coverage) const; - void unsupported_edges(Polylines* unsupported) const; + Polygons coverage(double angle = -1) const; void unsupported_edges(double angle, Polylines* unsupported) const; + Polylines unsupported_edges(double angle = -1) const; private: Polylines _edges; // representing the supporting edges diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index baddd8f456..3d07891b10 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -212,6 +212,15 @@ offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float ClipperPaths_to_Slic3rExPolygons(output, retval); } +Slic3r::ExPolygons +offset_ex(const Slic3r::Polygons &polygons, const float delta, + double scale, ClipperLib::JoinType joinType, double miterLimit) +{ + Slic3r::ExPolygons expp; + offset(polygons, &expp, delta, scale, joinType, miterLimit); + return expp; +} + void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1, const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) @@ -472,13 +481,16 @@ diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_ return pp; } +template Slic3r::ExPolygons -diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) +diff_ex(const SubjectType &subject, const ClipType &clip, bool safety_offset_) { Slic3r::ExPolygons expp; diff(subject, clip, &expp, safety_offset_); return expp; } +template Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); +template Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_); template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_) @@ -507,6 +519,14 @@ intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, boo return pp; } +Slic3r::ExPolygons +intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::ExPolygons expp; + intersection(subject, clip, &expp, safety_offset_); + return expp; +} + template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) { diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index 4a3ba2e5c4..2b4e8c0b34 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -58,6 +58,9 @@ void offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const floa void offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, + double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double miterLimit = 3); void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1, const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, @@ -96,13 +99,16 @@ template void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false); Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); -Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); + +template +Slic3r::ExPolygons diff_ex(const SubjectType &subject, const ClipType &clip, bool safety_offset_ = false); template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); Slic3r::Polylines intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); diff --git a/xs/src/libslic3r/ExPolygonCollection.cpp b/xs/src/libslic3r/ExPolygonCollection.cpp index 9ae7e89079..45f0df477d 100644 --- a/xs/src/libslic3r/ExPolygonCollection.cpp +++ b/xs/src/libslic3r/ExPolygonCollection.cpp @@ -122,6 +122,12 @@ ExPolygonCollection::contours() const return contours; } +void +ExPolygonCollection::append(const ExPolygons &expp) +{ + this->expolygons.insert(this->expolygons.end(), expp.begin(), expp.end()); +} + #ifdef SLIC3RXS REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection"); #endif diff --git a/xs/src/libslic3r/ExPolygonCollection.hpp b/xs/src/libslic3r/ExPolygonCollection.hpp index 4e6c366e29..ffb306c927 100644 --- a/xs/src/libslic3r/ExPolygonCollection.hpp +++ b/xs/src/libslic3r/ExPolygonCollection.hpp @@ -31,6 +31,7 @@ class ExPolygonCollection Polygon convex_hull() const; Lines lines() const; Polygons contours() const; + void append(const ExPolygons &expolygons); }; } diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 1c09b5f0f0..7adaa0fbb6 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -40,8 +40,7 @@ class LayerRegion // collection of expolygons representing the bridged areas (thus not // needing support material) - // (this could be just a Polygons object) - ExPolygonCollection bridged; + Polygons bridged; // collection of polylines representing the unsupported bridge edges PolylineCollection unsupported_bridge_edges; @@ -58,6 +57,8 @@ class LayerRegion void merge_slices(); void prepare_fill_surfaces(); void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); + void process_external_surfaces(const Layer* lower_layer); + double infill_area_threshold() const; private: Layer *_layer; diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index ef6150015e..4f874a2ead 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -1,4 +1,5 @@ #include "Layer.hpp" +#include "BridgeDetector.hpp" #include "ClipperUtils.hpp" #include "PerimeterGenerator.hpp" #include "Print.hpp" @@ -86,6 +87,146 @@ LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* g.process(); } +void +LayerRegion::process_external_surfaces(const Layer* lower_layer) +{ + const Surfaces &surfaces = this->fill_surfaces.surfaces; + const double margin = scale_(EXTERNAL_INFILL_MARGIN); + + SurfaceCollection bottom; + for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) { + if (!surface->is_bottom()) continue; + + ExPolygons grown = offset_ex(surface->expolygon, +margin); + + /* detect bridge direction before merging grown surfaces otherwise adjacent bridges + would get merged into a single one while they need different directions + also, supply the original expolygon instead of the grown one, because in case + of very thin (but still working) anchors, the grown expolygon would go beyond them */ + double angle = -1; + if (lower_layer != NULL) { + BridgeDetector bd( + surface->expolygon, + lower_layer->slices, + this->flow(frInfill, this->layer()->height, true).scaled_width() + ); + + #ifdef SLIC3R_DEBUG + printf("Processing bridge at layer %zu:\n", this->layer()->id(); + #endif + + if (bd.detect_angle() && this->layer()->object()->config.support_material) { + angle = bd.angle; + + Polygons coverage = bd.coverage(); + this->bridged.insert(this->bridged.end(), coverage.begin(), coverage.end()); + this->unsupported_bridge_edges.append(bd.unsupported_edges()); + } + } + + for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) { + Surface s = *surface; + s.expolygon = *it; + s.bridge_angle = angle; + bottom.surfaces.push_back(s); + } + } + + SurfaceCollection top; + for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) { + if (surface->surface_type != stTop) continue; + + // give priority to bottom surfaces + ExPolygons grown = diff_ex( + offset(surface->expolygon, +margin), + (Polygons)bottom + ); + for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) { + Surface s = *surface; + s.expolygon = *it; + top.surfaces.push_back(s); + } + } + + /* if we're slicing with no infill, we can't extend external surfaces + over non-existent infill */ + SurfaceCollection fill_boundaries; + if (this->region()->config.fill_density.value > 0) { + fill_boundaries = SurfaceCollection(surfaces); + } else { + for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) { + if (it->surface_type != stInternal) + fill_boundaries.surfaces.push_back(*it); + } + } + + // intersect the grown surfaces with the actual fill boundaries + SurfaceCollection new_surfaces; + { + // merge top and bottom in a single collection + SurfaceCollection tb = top; + tb.surfaces.insert(tb.surfaces.end(), bottom.surfaces.begin(), bottom.surfaces.end()); + + // group surfaces + std::vector groups; + tb.group(&groups); + + for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { + Polygons subject; + for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) { + Polygons pp = **s; + subject.insert(subject.end(), pp.begin(), pp.end()); + } + + ExPolygons expp = intersection_ex( + subject, + (Polygons)fill_boundaries, + true // to ensure adjacent expolygons are unified + ); + + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { + Surface s = *g->front(); + s.expolygon = *ex; + new_surfaces.surfaces.push_back(s); + } + } + } + + /* subtract the new top surfaces from the other non-top surfaces and re-add them */ + { + SurfaceCollection other; + for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) { + if (s->surface_type != stTop && !s->is_bottom()) + other.surfaces.push_back(*s); + } + + // group surfaces + std::vector groups; + other.group(&groups); + + for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { + Polygons subject; + for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) { + Polygons pp = **s; + subject.insert(subject.end(), pp.begin(), pp.end()); + } + + ExPolygons expp = diff_ex( + subject, + (Polygons)new_surfaces + ); + + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { + Surface s = *g->front(); + s.expolygon = *ex; + new_surfaces.surfaces.push_back(s); + } + } + } + + this->fill_surfaces = new_surfaces; +} + void LayerRegion::prepare_fill_surfaces() { @@ -123,6 +264,13 @@ LayerRegion::prepare_fill_surfaces() } } +double +LayerRegion::infill_area_threshold() const +{ + double ss = this->flow(frSolidInfill).scaled_spacing(); + return ss*ss; +} + #ifdef SLIC3RXS REGISTER_CLASS(LayerRegion, "Layer::Region"); #endif diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index a968485318..32662f59c3 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -47,7 +47,7 @@ PerimeterGenerator::process() for (Surfaces::const_iterator surface = this->slices->surfaces.begin(); surface != this->slices->surfaces.end(); ++surface) { // detect how many perimeters must be generated for this island - short loop_number = this->config->perimeters + surface->extra_perimeters; + signed short loop_number = this->config->perimeters + surface->extra_perimeters; loop_number--; // 0-indexed loops Polygons gaps; @@ -60,7 +60,7 @@ PerimeterGenerator::process() Polylines thin_walls; // we loop one time more than needed in order to find gaps after the last perimeter was applied - for (unsigned short i = 0; i <= loop_number+1; ++i) { // outer loop is 0 + for (signed short i = 0; i <= loop_number+1; ++i) { // outer loop is 0 Polygons offsets; if (i == 0) { // the minimum thickness of a single loop is: @@ -166,16 +166,16 @@ PerimeterGenerator::process() } // nest loops: holes first - for (unsigned short d = 0; d <= loop_number; ++d) { + for (signed short d = 0; d <= loop_number; ++d) { PerimeterGeneratorLoops &holes_d = holes[d]; // loop through all holes having depth == d - for (unsigned short i = 0; i < holes_d.size(); ++i) { + for (signed short i = 0; i < holes_d.size(); ++i) { const PerimeterGeneratorLoop &loop = holes_d[i]; // find the hole loop that contains this one, if any - for (unsigned short t = d+1; t <= loop_number; ++t) { - for (unsigned short j = 0; j < holes[t].size(); ++j) { + for (signed short t = d+1; t <= loop_number; ++t) { + for (signed short j = 0; j < holes[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -187,8 +187,8 @@ PerimeterGenerator::process() } // if no hole contains this hole, find the contour loop that contains it - for (short t = loop_number; t >= 0; --t) { - for (unsigned short j = 0; j < contours[t].size(); ++j) { + for (signed short t = loop_number; t >= 0; --t) { + for (signed short j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -203,16 +203,16 @@ PerimeterGenerator::process() } // nest contour loops - for (short d = loop_number; d >= 1; --d) { + for (signed short d = loop_number; d >= 1; --d) { PerimeterGeneratorLoops &contours_d = contours[d]; // loop through all contours having depth == d - for (unsigned short i = 0; i < contours_d.size(); ++i) { + for (signed short i = 0; i < contours_d.size(); ++i) { const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it - for (short t = d-1; t >= 0; --t) { - for (unsigned short j = 0; j < contours[t].size(); ++j) { + for (signed short t = d-1; t >= 0; --t) { + for (signed short j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); diff --git a/xs/src/libslic3r/PolylineCollection.cpp b/xs/src/libslic3r/PolylineCollection.cpp index c2a142cf31..b7e34b6705 100644 --- a/xs/src/libslic3r/PolylineCollection.cpp +++ b/xs/src/libslic3r/PolylineCollection.cpp @@ -50,6 +50,12 @@ PolylineCollection::leftmost_point() const return p; } +void +PolylineCollection::append(const Polylines &pp) +{ + this->polylines.insert(this->polylines.end(), pp.begin(), pp.end()); +} + #ifdef SLIC3RXS REGISTER_CLASS(PolylineCollection, "Polyline::Collection"); #endif diff --git a/xs/src/libslic3r/PolylineCollection.hpp b/xs/src/libslic3r/PolylineCollection.hpp index ace03ad378..d903b35c58 100644 --- a/xs/src/libslic3r/PolylineCollection.hpp +++ b/xs/src/libslic3r/PolylineCollection.hpp @@ -13,6 +13,7 @@ class PolylineCollection void chained_path(PolylineCollection* retval, bool no_reverse = false) const; void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const; Point leftmost_point() const; + void append(const Polylines &polylines); }; } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 9c8c1738f0..c88dbbda9f 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -135,6 +135,7 @@ class PrintObject bool invalidate_all_steps(); bool has_support_material() const; + void process_external_surfaces(); void bridge_over_infill(); private: diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 7c184c2580..a405e320d0 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -340,14 +340,32 @@ PrintObject::has_support_material() const || this->config.support_material_enforce_layers > 0; } +void +PrintObject::process_external_surfaces() +{ + FOREACH_REGION(this->_print, region) { + size_t region_id = region - this->_print->regions.begin(); + + FOREACH_LAYER(this, layer_it) { + const Layer* lower_layer = (layer_it == this->layers.begin()) + ? NULL + : *(layer_it-1); + + (*layer_it)->get_region(region_id)->process_external_surfaces(lower_layer); + } + } +} + +/* This method applies bridge flow to the first internal solid layer above + sparse infill */ void PrintObject::bridge_over_infill() { FOREACH_REGION(this->_print, region) { size_t region_id = region - this->_print->regions.begin(); - double fill_density = (*region)->config.fill_density.value; - if (fill_density == 100) continue; + // skip bridging in case there are no voids + if ((*region)->config.fill_density.value == 100) continue; // get bridge flow Flow bridge_flow = (*region)->flow( @@ -360,6 +378,7 @@ PrintObject::bridge_over_infill() ); FOREACH_LAYER(this, layer_it) { + // skip first layer if (layer_it == this->layers.begin()) continue; Layer* layer = *layer_it; @@ -379,7 +398,7 @@ PrintObject::bridge_over_infill() // iterate through lower layers spanned by bridge_flow double bottom_z = layer->print_z - bridge_flow.height; for (int i = (layer_it - this->layers.begin()) - 1; i >= 0; --i) { - Layer* lower_layer = this->layers[i]; + const Layer* lower_layer = this->layers[i]; // stop iterating if layer is lower than bottom_z if (lower_layer->print_z < bottom_z) break; @@ -390,19 +409,19 @@ PrintObject::bridge_over_infill() (*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal); // intersect such lower internal surfaces with the candidate solid surfaces - intersection(to_bridge_pp, lower_internal, &to_bridge_pp); + to_bridge_pp = intersection(to_bridge_pp, lower_internal); } // there's no point in bridging too thin/short regions { double min_width = bridge_flow.scaled_width() * 3; - offset2(to_bridge_pp, &to_bridge_pp, -min_width, +min_width); + to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); } if (to_bridge_pp.empty()) continue; // convert into ExPolygons - union_(to_bridge_pp, &to_bridge); + to_bridge = union_ex(to_bridge_pp); } #ifdef SLIC3R_DEBUG @@ -410,8 +429,7 @@ PrintObject::bridge_over_infill() #endif // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge; - diff(internal_solid, to_bridge, ¬_to_bridge, true); + ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, true); // build the new collection of fill_surfaces { diff --git a/xs/src/libslic3r/Surface.cpp b/xs/src/libslic3r/Surface.cpp index e4625f7998..e7d66c17b9 100644 --- a/xs/src/libslic3r/Surface.cpp +++ b/xs/src/libslic3r/Surface.cpp @@ -2,6 +2,11 @@ namespace Slic3r { +Surface::operator Polygons() const +{ + return this->expolygon; +} + double Surface::area() const { diff --git a/xs/src/libslic3r/Surface.hpp b/xs/src/libslic3r/Surface.hpp index 28a90799a2..a4a28a2c39 100644 --- a/xs/src/libslic3r/Surface.hpp +++ b/xs/src/libslic3r/Surface.hpp @@ -21,6 +21,7 @@ class Surface : surface_type(_surface_type), expolygon(_expolygon), thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0) {}; + operator Polygons() const; double area() const; bool is_solid() const; bool is_external() const; diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index 38c77ffd6e..2e96d4b1e1 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -111,6 +111,12 @@ SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) } } +void +SurfaceCollection::append(const SurfaceCollection &coll) +{ + this->surfaces.insert(this->surfaces.end(), coll.surfaces.begin(), coll.surfaces.end()); +} + #ifdef SLIC3RXS REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); #endif diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index e2ced7f0e9..40e36c5f03 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -11,6 +11,9 @@ class SurfaceCollection public: Surfaces surfaces; + SurfaceCollection() {}; + SurfaceCollection(const Surfaces &_surfaces) + : surfaces(_surfaces) {}; operator Polygons() const; operator ExPolygons() const; void simplify(double tolerance); @@ -19,6 +22,7 @@ class SurfaceCollection template bool any_bottom_contains(const T &item) const; SurfacesPtr filter_by_type(SurfaceType type); void filter_by_type(SurfaceType type, Polygons* polygons); + void append(const SurfaceCollection &coll); }; } diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 87d98caf1f..67ff6979c7 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -16,6 +16,7 @@ #define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 #define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI #define INSET_OVERLAP_TOLERANCE 0.4 +#define EXTERNAL_INFILL_MARGIN 3 #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) diff --git a/xs/xsp/BridgeDetector.xsp b/xs/xsp/BridgeDetector.xsp index e2318bcbf9..0350dae871 100644 --- a/xs/xsp/BridgeDetector.xsp +++ b/xs/xsp/BridgeDetector.xsp @@ -9,14 +9,10 @@ ~BridgeDetector(); bool detect_angle(); - Polygons coverage() - %code{% THIS->coverage(&RETVAL); %}; - Polygons coverage_by_angle(double angle) - %code{% THIS->coverage(angle, &RETVAL); %}; - Polylines unsupported_edges() - %code{% THIS->unsupported_edges(&RETVAL); %}; - Polylines unsupported_edges_by_angle(double angle) - %code{% THIS->unsupported_edges(angle, &RETVAL); %}; + Polygons coverage(); + %name{coverage_by_angle} Polygons coverage(double angle); + Polylines unsupported_edges(); + %name{unsupported_edges_by_angle} Polylines unsupported_edges(double angle); double angle() %code{% RETVAL = THIS->angle; %}; double resolution() diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index 00913e615e..42338fb160 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -17,8 +17,8 @@ %code%{ RETVAL = &THIS->thin_fills; %}; Ref fill_surfaces() %code%{ RETVAL = &THIS->fill_surfaces; %}; - Ref bridged() - %code%{ RETVAL = &THIS->bridged; %}; + Polygons bridged() + %code%{ RETVAL = THIS->bridged; %}; Ref unsupported_bridge_edges() %code%{ RETVAL = &THIS->unsupported_bridge_edges; %}; Ref perimeters() @@ -32,6 +32,7 @@ void prepare_fill_surfaces(); void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) %code%{ THIS->make_perimeters(*slices, fill_surfaces); %}; + double infill_area_threshold(); }; %name{Slic3r::Layer} class Layer { diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 635b858311..d7631cdf96 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -107,6 +107,7 @@ _constant() void set_step_started(PrintObjectStep step) %code%{ THIS->state.set_started(step); %}; + void process_external_surfaces(); void bridge_over_infill(); int ptr() From 889a54e946697599a78340ab5e9efc22263b2fb9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 Oct 2015 23:25:26 +0100 Subject: [PATCH 82/83] Bump version number --- xs/src/libslic3r/libslic3r.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 67ff6979c7..d1366eef26 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -6,7 +6,7 @@ #include #include -#define SLIC3R_VERSION "1.2.9" +#define SLIC3R_VERSION "1.2.10-dev" #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 From 2811af349ae17230a525ddcf819b1ddffaa250c9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 1 Nov 2015 19:03:11 +0100 Subject: [PATCH 83/83] Added a new grid infill pattern --- lib/Slic3r/Fill.pm | 1 + lib/Slic3r/Fill/Rectilinear.pm | 25 ++++++++++++++++++++++--- lib/Slic3r/Print/Object.pm | 4 ++-- xs/src/libslic3r/PrintConfig.cpp | 2 ++ xs/src/libslic3r/PrintConfig.hpp | 3 ++- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 88672a055b..8c63fde596 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -21,6 +21,7 @@ has 'fillers' => (is => 'rw', default => sub { {} }); our %FillTypes = ( archimedeanchords => 'Slic3r::Fill::ArchimedeanChords', rectilinear => 'Slic3r::Fill::Rectilinear', + grid => 'Slic3r::Fill::Grid', flowsnake => 'Slic3r::Fill::Flowsnake', octagramspiral => 'Slic3r::Fill::OctagramSpiral', hilbertcurve => 'Slic3r::Fill::HilbertCurve', diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index b16b0bb0cb..0922ff7714 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -12,6 +12,8 @@ has '_line_oscillation' => (is => 'rw'); use Slic3r::Geometry qw(scale unscale scaled_epsilon); use Slic3r::Geometry::Clipper qw(intersection_pl); +sub horizontal_lines { 0 } + sub fill_surface { my $self = shift; my ($surface, %params) = @_; @@ -44,9 +46,18 @@ sub fill_surface { # generate the basic pattern my $x_max = $bounding_box->x_max + scaled_epsilon; - my @vertical_lines = (); + my @lines = (); for (my $x = $bounding_box->x_min; $x <= $x_max; $x += $self->_line_spacing) { - push @vertical_lines, $self->_line($#vertical_lines, $x, $bounding_box->y_min, $bounding_box->y_max); + push @lines, $self->_line($#lines, $x, $bounding_box->y_min, $bounding_box->y_max); + } + if ($self->horizontal_lines) { + my $y_max = $bounding_box->y_max + scaled_epsilon; + for (my $y = $bounding_box->y_min; $y <= $y_max; $y += $self->_line_spacing) { + push @lines, Slic3r::Polyline->new( + [$bounding_box->x_min, $y], + [$bounding_box->x_max, $y], + ); + } } # clip paths against a slightly larger expolygon, so that the first and last paths @@ -54,7 +65,7 @@ sub fill_surface { # the minimum offset for preventing edge lines from being clipped is scaled_epsilon; # however we use a larger offset to support expolygons with slightly skewed sides and # not perfectly straight - my @polylines = @{intersection_pl(\@vertical_lines, $expolygon->offset(+scale 0.02))}; + my @polylines = @{intersection_pl(\@lines, $expolygon->offset(+scale 0.02))}; my $extra = $self->_min_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING; foreach my $polyline (@polylines) { @@ -146,4 +157,12 @@ sub _can_connect { && $dist_Y <= $self->_diagonal_distance; } + +package Slic3r::Fill::Grid; +use Moo; +extends 'Slic3r::Fill::Rectilinear'; + +sub angles () { [0] } +sub horizontal_lines { 1 } + 1; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 15094cb934..765fb386f4 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -437,7 +437,7 @@ sub make_perimeters { $slice->extra_perimeters($slice->extra_perimeters + 1); } Slic3r::debugf " adding %d more perimeter(s) at layer %d\n", - $slice->extra_perimeters, $layerm->id + $slice->extra_perimeters, $layerm->layer->id if $slice->extra_perimeters > 0; } } @@ -1072,7 +1072,7 @@ sub combine_infill { + $layerms[-1]->flow(FLOW_ROLE_PERIMETER)->scaled_width / 2 # Because fill areas for rectilinear and honeycomb are grown # later to overlap perimeters, we need to counteract that too. - + (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|honeycomb)/) + + (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|grid|line|honeycomb)/) ? $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width : 0) )}, @$intersection; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 6284cd0279..22f2b70f9e 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -301,6 +301,7 @@ PrintConfigDef::build_def() { Options["fill_pattern"].cli = "fill-pattern=s"; Options["fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["fill_pattern"].enum_values.push_back("rectilinear"); + Options["fill_pattern"].enum_values.push_back("grid"); Options["fill_pattern"].enum_values.push_back("line"); Options["fill_pattern"].enum_values.push_back("concentric"); Options["fill_pattern"].enum_values.push_back("honeycomb"); @@ -309,6 +310,7 @@ PrintConfigDef::build_def() { Options["fill_pattern"].enum_values.push_back("archimedeanchords"); Options["fill_pattern"].enum_values.push_back("octagramspiral"); Options["fill_pattern"].enum_labels.push_back("Rectilinear"); + Options["fill_pattern"].enum_labels.push_back("Grid"); Options["fill_pattern"].enum_labels.push_back("Line"); Options["fill_pattern"].enum_labels.push_back("Concentric"); Options["fill_pattern"].enum_labels.push_back("Honeycomb"); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 2bef5695f9..012135e015 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -12,7 +12,7 @@ enum GCodeFlavor { }; enum InfillPattern { - ipRectilinear, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, + ipRectilinear, ipGrid, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, }; @@ -39,6 +39,7 @@ template<> inline t_config_enum_values ConfigOptionEnum::get_enum_v template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["rectilinear"] = ipRectilinear; + keys_map["grid"] = ipGrid; keys_map["line"] = ipLine; keys_map["concentric"] = ipConcentric; keys_map["honeycomb"] = ipHoneycomb;