From 6776d6bc00101de798df0eb5ec1f453162b3ac94 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 5 Jan 2015 15:51:57 +0100 Subject: [PATCH 01/34] Bugfix: a bug in Polyline::split_at() caused random loss of perimeter segments. #2495 --- lib/Slic3r/Point.pm | 5 +++++ xs/src/libslic3r/ExtrusionEntity.cpp | 2 +- xs/src/libslic3r/Polyline.cpp | 2 +- xs/t/08_extrusionloop.t | 15 ++++++++++++++- xs/t/09_polyline.t | 11 ++++++++++- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/Point.pm b/lib/Slic3r/Point.pm index abb7a5edf..6d9e0891a 100644 --- a/lib/Slic3r/Point.pm +++ b/lib/Slic3r/Point.pm @@ -7,6 +7,11 @@ sub new_scale { return $class->new(map Slic3r::Geometry::scale($_), @_); } +sub dump_perl { + my $self = shift; + return sprintf "[%s,%s]", @$self; +} + package Slic3r::Pointf; use strict; use warnings; diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index ee49e3357..843dfda29 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -278,7 +278,7 @@ ExtrusionLoop::split_at(const Point &point) { if (this->paths.empty()) return; - // find the closest path and closest point + // find the closest path and closest point belonging to that path size_t path_idx = 0; Point p = this->paths.front().first_point(); double min = point.distance_to(p); diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index 1ae5055ba..7f27adf58 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -159,7 +159,7 @@ Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const p2->points.clear(); p2->points.push_back(point); for (Lines::const_iterator line = lines.begin() + line_idx; line != lines.end(); ++line) { - if (!line->b.coincides_with(p)) p2->points.push_back(line->b); + p2->points.push_back(line->b); } } diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t index 0657766c8..92d720dc3 100644 --- a/xs/t/08_extrusionloop.t +++ b/xs/t/08_extrusionloop.t @@ -5,7 +5,7 @@ use warnings; use List::Util qw(sum); use Slic3r::XS; -use Test::More tests => 46; +use Test::More tests => 48; { my $square = [ @@ -133,8 +133,10 @@ use Test::More tests => 46; Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), ); + my $len = $loop->length; my $point = Slic3r::Point->new(4821067,9321068); $loop->split_at_vertex($point) or $loop->split_at($point); + is $loop->length, $len, 'total length is preserved after splitting'; is_deeply [ map $_->role, @$loop ], [ Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, @@ -144,4 +146,15 @@ use Test::More tests => 46; ], 'order is correctly preserved after splitting'; } +{ + my $loop = Slic3r::ExtrusionLoop->new; + $loop->append(Slic3r::ExtrusionPath->new( + polyline => Slic3r::Polyline->new([15896783,15868739],[24842049,12117558],[33853238,15801279],[37591780,24780128],[37591780,24844970],[33853231,33825297],[24842049,37509013],[15896798,33757841],[12211841,24812544],[15896783,15868739]), + role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1 + )); + my $len = $loop->length; + $loop->split_at(Slic3r::Point->new(15896783,15868739)); + is $loop->length, $len, 'split_at() preserves total length'; +} + __END__ diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t index 99077aad3..824f049e9 100644 --- a/xs/t/09_polyline.t +++ b/xs/t/09_polyline.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 16; +use Test::More tests => 18; my $points = [ [100, 100], @@ -79,4 +79,13 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; ok $p2->first_point->coincides_with($point), 'split_at'; } +{ + my $polyline = Slic3r::Polyline->new(@$points[0,1,2,0]); + my $p1 = Slic3r::Polyline->new; + my $p2 = Slic3r::Polyline->new; + $polyline->split_at($polyline->first_point, $p1, $p2); + is scalar(@$p1), 1, 'split_at'; + is scalar(@$p2), 4, 'split_at'; +} + __END__ From 47e4e8bb66740af791e56b36f0ecb67143a94b3b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 5 Jan 2015 19:39:10 +0100 Subject: [PATCH 02/34] Option to use volumetric E values. #1746 --- README.md | 1 + lib/Slic3r/GUI/Tab.pm | 4 +++- slic3r.pl | 1 + xs/src/libslic3r/Extruder.cpp | 18 ++++++++++++++++-- xs/src/libslic3r/GCodeWriter.cpp | 32 +++++++++++++++++++++++++------- xs/src/libslic3r/PrintConfig.cpp | 5 +++++ xs/src/libslic3r/PrintConfig.hpp | 27 +++++++++++++++------------ 7 files changed, 66 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 653e40246..70ec89d1d 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ The author of the Silk icon set is Mark James. default: reprap) --use-relative-e-distances Enable this to get relative E values (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) + --use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no) --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported by all firmwares) --gcode-comments Make G-code verbose by adding comments (default: no) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index d3e7ccd35..7a21b3fc0 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -927,6 +927,7 @@ sub build { gcode_flavor use_relative_e_distances octoprint_host octoprint_apikey use_firmware_retraction pressure_advance vibration_limit + use_volumetric_e start_gcode end_gcode layer_gcode toolchange_gcode nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe @@ -1036,11 +1037,12 @@ sub build { { my $optgroup = $page->new_optgroup('Firmware'); $optgroup->append_single_option_line('gcode_flavor'); - $optgroup->append_single_option_line('use_relative_e_distances'); } { my $optgroup = $page->new_optgroup('Advanced'); + $optgroup->append_single_option_line('use_relative_e_distances'); $optgroup->append_single_option_line('use_firmware_retraction'); + $optgroup->append_single_option_line('use_volumetric_e'); $optgroup->append_single_option_line('pressure_advance'); $optgroup->append_single_option_line('vibration_limit'); } diff --git a/slic3r.pl b/slic3r.pl index debc5ff79..bea11ce1b 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -286,6 +286,7 @@ $j default: $config->{gcode_flavor}) --use-relative-e-distances Enable this to get relative E values (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) + --use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no) --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported by all firmwares) --gcode-comments Make G-code verbose by adding comments (default: no) diff --git a/xs/src/libslic3r/Extruder.cpp b/xs/src/libslic3r/Extruder.cpp index d0934851e..8cbd00b73 100644 --- a/xs/src/libslic3r/Extruder.cpp +++ b/xs/src/libslic3r/Extruder.cpp @@ -9,8 +9,12 @@ Extruder::Extruder(int id, GCodeConfig *config) reset(); // cache values that are going to be called often - this->e_per_mm3 = this->extrusion_multiplier() - * (4 / ((this->filament_diameter() * this->filament_diameter()) * PI)); + if (config->use_volumetric_e) { + this->e_per_mm3 = this->extrusion_multiplier(); + } else { + this->e_per_mm3 = this->extrusion_multiplier() + * (4 / ((this->filament_diameter() * this->filament_diameter()) * PI)); + } this->retract_speed_mm_min = this->retract_speed() * 60; } @@ -80,12 +84,22 @@ Extruder::e_per_mm(double mm3_per_mm) const double Extruder::extruded_volume() const { + if (this->config->use_volumetric_e) { + // Any current amount of retraction should not affect used filament, since + // it represents empty volume in the nozzle. We add it back to E. + return this->absolute_E + this->retracted; + } + return this->used_filament() * (this->filament_diameter() * this->filament_diameter()) * PI/4; } double Extruder::used_filament() const { + if (this->config->use_volumetric_e) { + return this->extruded_volume() / (this->filament_diameter() * this->filament_diameter() * PI/4); + } + // Any current amount of retraction should not affect used filament, since // it represents empty volume in the nozzle. We add it back to E. return this->absolute_E + this->retracted; diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp index bb8035ac3..0fe15809e 100644 --- a/xs/src/libslic3r/GCodeWriter.cpp +++ b/xs/src/libslic3r/GCodeWriter.cpp @@ -48,22 +48,32 @@ GCodeWriter::set_extruders(const std::vector &extruder_ids) std::string GCodeWriter::preamble() { - std::string gcode; + std::ostringstream gcode; if (FLAVOR_IS_NOT(gcfMakerWare)) { - gcode += "G21 ; set units to millimeters\n"; - gcode += "G90 ; use absolute coordinates\n"; + gcode << "G21 ; set units to millimeters\n"; + gcode << "G90 ; use absolute coordinates\n"; } if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfTeacup)) { if (this->config.use_relative_e_distances) { - gcode += "M83 ; use relative distances for extrusion\n"; + gcode << "M83 ; use relative distances for extrusion\n"; } else { - gcode += "M82 ; use absolute distances for extrusion\n"; + gcode << "M82 ; use absolute distances for extrusion\n"; } - gcode += this->reset_e(true); + if (this->config.use_volumetric_e && this->config.start_gcode.value.find("M200") == std::string::npos) { + for (std::map::const_iterator it = this->extruders.begin(); it != this->extruders.end(); ++it) { + unsigned int extruder_id = it->first; + gcode << "M200 D" << E_NUM(this->config.filament_diameter.get_at(extruder_id)); + if (this->multiple_extruders || extruder_id != 0) { + gcode << " T" << extruder_id; + } + gcode << " ; set filament diameter\n"; + } + } + gcode << this->reset_e(true); } - return gcode; + return gcode.str(); } std::string @@ -423,6 +433,14 @@ GCodeWriter::_retract(double length, double restart_extra, const std::string &co might be 0, in which case the retraction logic gets skipped. */ if (this->config.use_firmware_retraction) length = 1; + // If we use volumetric E values we turn lengths into volumes */ + if (this->config.use_volumetric_e) { + double d = this->_extruder->filament_diameter(); + double area = d * d * PI/4; + length = length * area; + restart_extra = restart_extra * area; + } + double dE = this->_extruder->retract(length, restart_extra); if (dE != 0) { if (this->config.use_firmware_retraction) { diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index e4847a570..e18179cd1 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -945,6 +945,11 @@ PrintConfigDef::build_def() { Options["use_relative_e_distances"].tooltip = "If your firmware requires relative E values, check this, otherwise leave it unchecked. Most firmwares use absolute values."; Options["use_relative_e_distances"].cli = "use-relative-e-distances!"; + Options["use_volumetric_e"].type = coBool; + Options["use_volumetric_e"].label = "Use volumetric E"; + Options["use_volumetric_e"].tooltip = "This experimental setting uses outputs the E values in cubic millimeters instead of linear millimeters. The M200 command is prepended to the generated G-code, unless it is found in the configured start G-code. This is only supported in recent Marlin."; + Options["use_volumetric_e"].cli = "use-volumetric-e!"; + Options["vibration_limit"].type = coFloat; Options["vibration_limit"].label = "Vibration limit (deprecated)"; Options["vibration_limit"].tooltip = "This experimental option will slow down those moves hitting the configured frequency limit. The purpose of limiting vibrations is to avoid mechanical resonance. Set zero to disable."; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index b647acfb8..500babb88 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -321,11 +321,13 @@ class PrintRegionConfig : public virtual StaticPrintConfig class GCodeConfig : public virtual StaticPrintConfig { public: + ConfigOptionString end_gcode; ConfigOptionString extrusion_axis; ConfigOptionFloats extrusion_multiplier; ConfigOptionFloats filament_diameter; ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; + ConfigOptionString layer_gcode; ConfigOptionFloat pressure_advance; ConfigOptionFloats retract_length; ConfigOptionFloats retract_length_toolchange; @@ -333,11 +335,15 @@ class GCodeConfig : public virtual StaticPrintConfig ConfigOptionFloats retract_restart_extra; ConfigOptionFloats retract_restart_extra_toolchange; ConfigOptionInts retract_speed; + ConfigOptionString start_gcode; + ConfigOptionString toolchange_gcode; ConfigOptionFloat travel_speed; ConfigOptionBool use_firmware_retraction; ConfigOptionBool use_relative_e_distances; + ConfigOptionBool use_volumetric_e; GCodeConfig() : StaticPrintConfig() { + this->end_gcode.value = "M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n"; this->extrusion_axis.value = "E"; this->extrusion_multiplier.values.resize(1); this->extrusion_multiplier.values[0] = 1; @@ -345,6 +351,7 @@ class GCodeConfig : public virtual StaticPrintConfig this->filament_diameter.values[0] = 3; this->gcode_comments.value = false; this->gcode_flavor.value = gcfRepRap; + this->layer_gcode.value = ""; this->pressure_advance.value = 0; this->retract_length.values.resize(1); this->retract_length.values[0] = 1; @@ -358,17 +365,22 @@ class GCodeConfig : public virtual StaticPrintConfig this->retract_restart_extra_toolchange.values[0] = 0; this->retract_speed.values.resize(1); this->retract_speed.values[0] = 30; + this->start_gcode.value = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n"; + this->toolchange_gcode.value = ""; this->travel_speed.value = 130; this->use_firmware_retraction.value = false; this->use_relative_e_distances.value = false; + this->use_volumetric_e.value = false; }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { + 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 == "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; @@ -376,9 +388,12 @@ class GCodeConfig : public virtual StaticPrintConfig 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; return NULL; }; @@ -409,7 +424,6 @@ class PrintConfig : public GCodeConfig ConfigOptionFloat default_acceleration; ConfigOptionInt disable_fan_first_layers; ConfigOptionFloat duplicate_distance; - ConfigOptionString end_gcode; ConfigOptionFloat extruder_clearance_height; ConfigOptionFloat extruder_clearance_radius; ConfigOptionPoints extruder_offset; @@ -423,7 +437,6 @@ class PrintConfig : public GCodeConfig ConfigOptionBool gcode_arcs; ConfigOptionFloat infill_acceleration; ConfigOptionBool infill_first; - ConfigOptionString layer_gcode; ConfigOptionInt max_fan_speed; ConfigOptionInt min_fan_speed; ConfigOptionInt min_print_speed; @@ -444,10 +457,8 @@ class PrintConfig : public GCodeConfig ConfigOptionInt slowdown_below_layer_time; ConfigOptionBool spiral_vase; ConfigOptionInt standby_temperature_delta; - ConfigOptionString start_gcode; ConfigOptionInts temperature; ConfigOptionInt threads; - ConfigOptionString toolchange_gcode; ConfigOptionFloat vibration_limit; ConfigOptionBools wipe; ConfigOptionFloat z_offset; @@ -467,7 +478,6 @@ class PrintConfig : public GCodeConfig this->default_acceleration.value = 0; this->disable_fan_first_layers.value = 1; this->duplicate_distance.value = 6; - this->end_gcode.value = "M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n"; this->extruder_clearance_height.value = 20; this->extruder_clearance_radius.value = 20; this->extruder_offset.values.resize(1); @@ -485,7 +495,6 @@ class PrintConfig : public GCodeConfig this->gcode_arcs.value = false; this->infill_acceleration.value = 0; this->infill_first.value = false; - this->layer_gcode.value = ""; this->max_fan_speed.value = 100; this->min_fan_speed.value = 35; this->min_print_speed.value = 10; @@ -508,11 +517,9 @@ class PrintConfig : public GCodeConfig this->slowdown_below_layer_time.value = 30; this->spiral_vase.value = false; this->standby_temperature_delta.value = -5; - this->start_gcode.value = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n"; this->temperature.values.resize(1); this->temperature.values[0] = 200; this->threads.value = 2; - this->toolchange_gcode.value = ""; this->vibration_limit.value = 0; this->wipe.values.resize(1); this->wipe.values[0] = false; @@ -531,7 +538,6 @@ class PrintConfig : public GCodeConfig 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 == "end_gcode") return &this->end_gcode; 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; @@ -545,7 +551,6 @@ class PrintConfig : public GCodeConfig 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 == "layer_gcode") return &this->layer_gcode; 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; @@ -566,10 +571,8 @@ class PrintConfig : public GCodeConfig 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 == "start_gcode") return &this->start_gcode; if (opt_key == "temperature") return &this->temperature; if (opt_key == "threads") return &this->threads; - if (opt_key == "toolchange_gcode") return &this->toolchange_gcode; 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; From 3332282767248b6ceecc21d05cd35ccef7b09c8c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 5 Jan 2015 20:07:47 +0100 Subject: [PATCH 03/34] Unit test for pressure advance. #2470 --- t/pressure.t | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 t/pressure.t diff --git a/t/pressure.t b/t/pressure.t new file mode 100644 index 000000000..fe4f05fb7 --- /dev/null +++ b/t/pressure.t @@ -0,0 +1,39 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use List::Util qw(); +use Slic3r; +use Slic3r::Geometry qw(); +use Slic3r::Test; + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('pressure_advance', 10); + $config->set('retract_length', [1]); + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); + my $retracted = 0; + my $extruding_before_full_unretract = 0; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($info->{extruding} && $info->{dist_XY}) { + $extruding_before_full_unretract if $retracted != 0; + } elsif ($info->{extruding}) { + $retracted += -$info->{dist_E}; + } elsif ($info->{retracting}) { + $retracted += -$info->{dist_E}; + } + }); + + ok !$extruding_before_full_unretract, 'not extruding before complete unretract'; +} + + +__END__ From 9fd063799057fd5404dbcbf188b916487eedc339 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 5 Jan 2015 21:00:50 +0100 Subject: [PATCH 04/34] Bugfix: artifacts were introduced when perimeters were recalculated through incremental slicing. #2494 --- xs/src/libslic3r/LayerRegion.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index f068cf24f..7b8e77f5a 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -44,7 +44,8 @@ void LayerRegion::merge_slices() { ExPolygons expp; - union_(this->slices, &expp); + // without safety offset, artifacts are generated (GH #2494) + union_(this->slices, &expp, true); this->slices.surfaces.clear(); this->slices.surfaces.reserve(expp.size()); From 0f7933c4f9e29ada1b468956b16894fdfe9cfefc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 00:35:39 +0100 Subject: [PATCH 05/34] Bugfix: pressure regulation accumulated too much retraction and didn't discharge at the end of print. Includes regression test. #2470 --- lib/Slic3r/GCode/PressureRegulator.pm | 29 +++++++++++++++++++-------- lib/Slic3r/Print/GCode.pm | 21 ++++++++++--------- t/pressure.t | 15 ++++++-------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/lib/Slic3r/GCode/PressureRegulator.pm b/lib/Slic3r/GCode/PressureRegulator.pm index 6acbf0e09..8d1cd6525 100644 --- a/lib/Slic3r/GCode/PressureRegulator.pm +++ b/lib/Slic3r/GCode/PressureRegulator.pm @@ -24,7 +24,7 @@ sub BUILD { sub process { my $self = shift; - my ($gcode) = @_; + my ($gcode, $flush) = @_; my $new_gcode = ""; @@ -51,7 +51,7 @@ sub process { if (abs($new_advance - $self->_advance) > 1E-5) { my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) + ($new_advance - $self->_advance); $new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure advance\n", - $self->_extrusion_axis, $new_E, $self->unretract_speed; + $self->_extrusion_axis, $new_E, $self->_unretract_speed; $new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E if !$self->config->use_relative_e_distances; $self->_advance($new_advance); @@ -61,20 +61,33 @@ sub process { } } elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) { # We need to bring pressure to zero when retracting. - my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) - $self->_advance; - $new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure discharge\n", - $self->_extrusion_axis, $new_E, $args->{F} // $self->unretract_speed; - $new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E - if !$self->config->use_relative_e_distances; + $new_gcode .= $self->_discharge($args->{F}); } $new_gcode .= "$info->{raw}\n"; }); + if ($flush) { + $new_gcode .= $self->_discharge; + } + return $new_gcode; } -sub unretract_speed { +sub _discharge { + my ($self, $F) = @_; + + my $new_E = ($self->config->use_relative_e_distances ? 0 : $self->reader->E) - $self->_advance; + my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n", + $self->_extrusion_axis, $new_E, $F // $self->_unretract_speed; + $gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E + if !$self->config->use_relative_e_distances; + $self->_advance(0); + + return $gcode; +} + +sub _unretract_speed { my ($self) = @_; return $self->config->get_at('retract_speed', $self->_tool) * 60; } diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index a40a64e28..762978655 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -54,7 +54,7 @@ sub BUILD { if $self->config->spiral_vase; $self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config)) - if $self->config->vibration_limit > 0; + if $self->config->vibration_limit != 0; $self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config)) if $self->config->gcode_arcs; @@ -208,7 +208,7 @@ sub export { } $self->process_layer($layer, [$copy]); } - $self->flush_cooling_buffer; + $self->flush_filters; $finished_objects++; } } @@ -234,7 +234,7 @@ sub export { } } } - $self->flush_cooling_buffer; + $self->flush_filters; } # write end commands to file @@ -529,28 +529,29 @@ sub _extrude_infill { return $gcode; } -sub flush_cooling_buffer { +sub flush_filters { my ($self) = @_; - print {$self->fh} $self->filter($self->_cooling_buffer->flush); + + print {$self->fh} $self->filter($self->_cooling_buffer->flush, 1); } sub filter { - my ($self, $gcode) = @_; + my ($self, $gcode, $flush) = @_; # apply vibration limit if enabled; # this injects pauses according to time (thus depends on actual speeds) $gcode = $self->_vibration_limit->process($gcode) - if $self->print->config->vibration_limit != 0; + if defined $self->_vibration_limit; # apply pressure regulation if enabled; # this depends on actual speeds - $gcode = $self->_pressure_regulator->process($gcode) - if $self->print->config->pressure_advance > 0; + $gcode = $self->_pressure_regulator->process($gcode, $flush) + if defined $self->_pressure_regulator; # apply arc fitting if enabled; # this does not depend on speeds but changes G1 XY commands into G2/G2 IJ $gcode = $self->_arc_fitting->process($gcode) - if $self->print->config->gcode_arcs; + if defined $self->_arc_fitting; return $gcode; } diff --git a/t/pressure.t b/t/pressure.t index fe4f05fb7..fd9045c82 100644 --- a/t/pressure.t +++ b/t/pressure.t @@ -9,7 +9,7 @@ BEGIN { use List::Util qw(); use Slic3r; -use Slic3r::Geometry qw(); +use Slic3r::Geometry qw(epsilon); use Slic3r::Test; { @@ -18,21 +18,18 @@ use Slic3r::Test; $config->set('retract_length', [1]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); - my $retracted = 0; - my $extruding_before_full_unretract = 0; + my $retracted = $config->retract_length->[0]; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; - if ($info->{extruding} && $info->{dist_XY}) { - $extruding_before_full_unretract if $retracted != 0; - } elsif ($info->{extruding}) { - $retracted += -$info->{dist_E}; + if ($info->{extruding} && !$info->{dist_XY}) { + $retracted += $info->{dist_E}; } elsif ($info->{retracting}) { - $retracted += -$info->{dist_E}; + $retracted += $info->{dist_E}; } }); - ok !$extruding_before_full_unretract, 'not extruding before complete unretract'; + ok abs($retracted) < epsilon, 'all retractions are compensated'; } From 7e82159620f99f132534b658dc8e0525fd829983 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 11:29:34 +0100 Subject: [PATCH 06/34] Fixed one more case where only_retract_when_crossing_perimeters didn't apply. #2498 --- lib/Slic3r/GCode.pm | 3 ++- xs/src/libslic3r/Layer.cpp | 11 +++++++++++ xs/src/libslic3r/Layer.hpp | 1 + xs/src/libslic3r/SurfaceCollection.cpp | 11 +++++++++++ xs/src/libslic3r/SurfaceCollection.hpp | 1 + xs/xsp/Layer.xsp | 4 ++++ 6 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 0a8f3951c..d4f019755 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -352,7 +352,8 @@ sub travel_to { && $self->config->fill_density > 0 && defined($self->layer) && ($self->layer->any_internal_region_slice_contains_line($travel) - || $self->layer->any_internal_region_fill_surface_contains_line($travel))) + || ($self->layer->any_bottom_region_slice_contains_line($travel) + && (!defined $self->layer->upper_layer || $self->layer->upper_layer->slices->contains_line($travel))))) || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel)) ) { # Just perform a straight travel move without any retraction. diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 47bef8101..d46665d88 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -131,6 +131,17 @@ Layer::any_internal_region_slice_contains(const T &item) const } template bool Layer::any_internal_region_slice_contains(const Line &item) const; +template +bool +Layer::any_bottom_region_slice_contains(const T &item) const +{ + FOREACH_LAYERREGION(this, layerm) { + if ((*layerm)->slices.any_bottom_contains(item)) return true; + } + return false; +} +template bool Layer::any_bottom_region_slice_contains(const Line &item) const; + template bool Layer::any_internal_region_fill_surface_contains(const T &item) const diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 6a4e905c3..6630e2cdc 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -94,6 +94,7 @@ class Layer { void make_slices(); template bool any_internal_region_slice_contains(const T &item) const; + template bool any_bottom_region_slice_contains(const T &item) const; template bool any_internal_region_fill_surface_contains(const T &item) const; protected: diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index ccf1eaa4e..c3b5f6866 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -80,6 +80,17 @@ SurfaceCollection::any_internal_contains(const T &item) const template bool SurfaceCollection::any_internal_contains(const Line &item) const; template bool SurfaceCollection::any_internal_contains(const Polyline &item) const; +template +bool +SurfaceCollection::any_bottom_contains(const T &item) const +{ + for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { + if (surface->is_bottom() && surface->expolygon.contains(item)) return true; + } + return false; +} +template bool SurfaceCollection::any_bottom_contains(const Line &item) const; + SurfacesPtr SurfaceCollection::filter_by_type(SurfaceType type) { diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index 09a46449e..e2ced7f0e 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -16,6 +16,7 @@ class SurfaceCollection void simplify(double tolerance); void group(std::vector *retval); template bool any_internal_contains(const T &item) const; + template bool any_bottom_contains(const T &item) const; SurfacesPtr filter_by_type(SurfaceType type); void filter_by_type(SurfaceType type, Polygons* polygons); }; diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index cc38b0847..8f512c4e9 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -71,6 +71,8 @@ void make_slices(); bool any_internal_region_slice_contains_line(Line* line) %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; + bool any_bottom_region_slice_contains_line(Line* line) + %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*line); %}; bool any_internal_region_fill_surface_contains_line(Line* line) %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %}; bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline) @@ -123,6 +125,8 @@ bool any_internal_region_slice_contains_line(Line* line) %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; + bool any_bottom_region_slice_contains_line(Line* line) + %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*line); %}; bool any_internal_region_fill_surface_contains_line(Line* line) %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %}; bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline) From 25620702320390a26263516b1652bf22a0d29a37 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 14:47:53 +0100 Subject: [PATCH 07/34] Refactored the travel/retract/avoid_crossing_perimeters logic. Several edge cases are now handled correctly. #2498 --- lib/Slic3r/GCode.pm | 145 +++++++++++++------------ lib/Slic3r/Print/GCode.pm | 9 +- xs/src/libslic3r/Layer.cpp | 17 +-- xs/src/libslic3r/Layer.hpp | 3 +- xs/src/libslic3r/SurfaceCollection.cpp | 3 +- xs/xsp/Layer.xsp | 24 ++-- 6 files changed, 98 insertions(+), 103 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index d4f019755..70d0330c2 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -332,49 +332,75 @@ sub _extrude_path { sub travel_to { my ($self, $point, $role, $comment) = @_; - my $gcode = ""; - # 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::Line->new($self->last_pos, $point); + my $travel = Slic3r::Polyline->new($self->last_pos, $point); - # Skip retraction at all in the following cases: - # - travel length is shorter than the configured threshold - # - user has enabled "Only retract when crossing perimeters" and the travel move is - # contained in a single internal fill_surface (this includes the bottom layer when - # bottom_solid_layers == 0) or in a single internal slice (this would exclude such - # bottom layer but preserve perimeter-to-infill moves in all the other layers) - # - the path that will be extruded after this travel move is a support material - # extrusion and the travel move is contained in a single support material island - if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id) - || ($self->config->only_retract_when_crossing_perimeters - && $self->config->fill_density > 0 - && defined($self->layer) - && ($self->layer->any_internal_region_slice_contains_line($travel) - || ($self->layer->any_bottom_region_slice_contains_line($travel) - && (!defined $self->layer->upper_layer || $self->layer->upper_layer->slices->contains_line($travel))))) - || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel)) - ) { - # Just perform a straight travel move without any retraction. - $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || ''); - } elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) { - # If avoid_crossing_perimeters is enabled and the disable_once flag is not set - # we need to plan a multi-segment travel move inside the configuration space. - $gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment || ''); - } else { - # If avoid_crossing_perimeters is disabled or the disable_once flag is set, - # perform a straight move with a retraction. - $gcode .= $self->retract; - $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || ''); + # check whether a straight travel move would need retraction + my $needs_retraction = $self->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 + && $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); } # 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); + + # 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; } +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->support_islands->contains_line($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->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 retract { my ($self, $toolchange) = @_; @@ -569,9 +595,10 @@ 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 -has 'disable_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move -use Slic3r::Geometry qw(scale); +# 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) = @_; @@ -584,47 +611,31 @@ sub init_layer_mp { } sub travel_to { - my ($self, $gcodegen, $point, $comment) = @_; - - my $gcode = ""; + my ($self, $gcodegen, $point) = @_; if ($self->use_external_mp || $self->use_external_mp_once) { - $self->use_external_mp_once(0); + # 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 $point in G-code coordinates + # represent last_pos in absolute G-code coordinates + my $last_pos = $gcodegen->last_pos->clone; + $last_pos->translate(@{$gcodegen->origin}); + + # represent $point in absolute G-code coordinates $point = $point->clone; - my $origin = $gcodegen->origin; - $point->translate(map scale $_, @$origin); + $point->translate(@$scaled_origin); - # calculate path (external_mp uses G-code coordinates so we set a temporary null origin) - $gcodegen->set_origin(Slic3r::Pointf->new(0,0)); - $gcode .= $self->_plan($gcodegen, $self->_external_mp, $point, $comment); - $gcodegen->set_origin($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 { - $gcode .= $self->_plan($gcodegen, $self->_layer_mp, $point, $comment); + return $self->_layer_mp->shortest_path($gcodegen->last_pos, $point); } - - return $gcode; -} - -sub _plan { - my ($self, $gcodegen, $mp, $point, $comment) = @_; - - my $gcode = ""; - my $travel = $mp->shortest_path($gcodegen->last_pos, $point); - - # if the path is not contained in a single island we need to retract - $gcode .= $gcodegen->retract - if !$gcodegen->config->only_retract_when_crossing_perimeters - || !$gcodegen->layer->any_internal_region_fill_surface_contains_polyline($travel); - - # append the actual path and return - # use G1 because we rely on paths being straight (G0 may make round paths) - $gcode .= join '', - map $gcodegen->writer->travel_to_xy($gcodegen->point_to_gcode($_->b), $comment), - @{$travel->lines}; - - return $gcode; } 1; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 762978655..517c453cd 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -354,7 +354,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->disable_once(1); + + # 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); + } } # extrude brim @@ -366,6 +371,8 @@ sub process_layer { for @{$self->print->brim}; $self->_brim_done(1); $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0); + + # allow a straight travel move to the first object point $self->_gcodegen->avoid_crossing_perimeters->disable_once(1); } diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index d46665d88..38935fced 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -129,7 +129,7 @@ Layer::any_internal_region_slice_contains(const T &item) const } return false; } -template bool Layer::any_internal_region_slice_contains(const Line &item) const; +template bool Layer::any_internal_region_slice_contains(const Polyline &item) const; template bool @@ -140,20 +140,7 @@ Layer::any_bottom_region_slice_contains(const T &item) const } return false; } -template bool Layer::any_bottom_region_slice_contains(const Line &item) const; - -template -bool -Layer::any_internal_region_fill_surface_contains(const T &item) const -{ - FOREACH_LAYERREGION(this, layerm) { - if ((*layerm)->fill_surfaces.any_internal_contains(item)) return true; - } - return false; -} -template bool Layer::any_internal_region_fill_surface_contains(const Line &item) const; -template bool Layer::any_internal_region_fill_surface_contains(const Polyline &item) const; - +template bool Layer::any_bottom_region_slice_contains(const Polyline &item) const; #ifdef SLIC3RXS REGISTER_CLASS(Layer, "Layer"); diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 6630e2cdc..5f6a6baa0 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -95,8 +95,7 @@ class Layer { void make_slices(); template bool any_internal_region_slice_contains(const T &item) const; template bool any_bottom_region_slice_contains(const T &item) const; - template bool any_internal_region_fill_surface_contains(const T &item) const; - + protected: int _id; // sequential number of layer, 0-based PrintObject *_object; diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index c3b5f6866..38c77ffd6 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -77,7 +77,6 @@ SurfaceCollection::any_internal_contains(const T &item) const } return false; } -template bool SurfaceCollection::any_internal_contains(const Line &item) const; template bool SurfaceCollection::any_internal_contains(const Polyline &item) const; template @@ -89,7 +88,7 @@ SurfaceCollection::any_bottom_contains(const T &item) const } return false; } -template bool SurfaceCollection::any_bottom_contains(const Line &item) const; +template bool SurfaceCollection::any_bottom_contains(const Polyline &item) const; SurfacesPtr SurfaceCollection::filter_by_type(SurfaceType type) diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index 8f512c4e9..ffc9f9af8 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -69,14 +69,10 @@ %code%{ RETVAL = (int)(intptr_t)THIS; %}; void make_slices(); - bool any_internal_region_slice_contains_line(Line* line) - %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; - bool any_bottom_region_slice_contains_line(Line* line) - %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*line); %}; - bool any_internal_region_fill_surface_contains_line(Line* line) - %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %}; - bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline) - %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %}; + bool any_internal_region_slice_contains_polyline(Polyline* polyline) + %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; + bool any_bottom_region_slice_contains_polyline(Polyline* polyline) + %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %}; }; %name{Slic3r::Layer::Support} class SupportLayer { @@ -123,12 +119,8 @@ Ref slices() %code%{ RETVAL = &THIS->slices; %}; - bool any_internal_region_slice_contains_line(Line* line) - %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; - bool any_bottom_region_slice_contains_line(Line* line) - %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*line); %}; - bool any_internal_region_fill_surface_contains_line(Line* line) - %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %}; - bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline) - %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %}; + bool any_internal_region_slice_contains_polyline(Polyline* polyline) + %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; + bool any_bottom_region_slice_contains_polyline(Polyline* polyline) + %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %}; }; From f0de57cbe41489836e194a82ce5e1bea5482fc96 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 15:04:09 +0100 Subject: [PATCH 08/34] Minor cleanup of the init_external_mp() call --- lib/Slic3r/Print/GCode.pm | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 517c453cd..da4d51416 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -121,23 +121,30 @@ sub export { # initialize a motion planner for object-to-object travel moves if ($self->config->avoid_crossing_perimeters) { - my $distance_from_objects = 1; + my $distance_from_objects = scale 1; + # compute the offsetted convex hull for each object and repeat it for each copy. - my @islands = (); - foreach my $obj_idx (0 .. ($self->print->object_count - 1)) { + my @islands_p = (); + foreach my $object (@{$self->objects}) { + # compute the convex hull of the entire object my $convex_hull = convex_hull([ - map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers}, + map @{$_->contour}, map @{$_->slices}, @{$object->layers}, ]); - # discard layers only containing thin walls (offset would fail on an empty polygon) - if (@$convex_hull) { - my $expolygon = Slic3r::ExPolygon->new($convex_hull); - my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)}; - foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) { - push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island; - } + + # discard objects only containing thin walls (offset would fail on an empty polygon) + next if !@$convex_hull; + + # grow convex hull by the wanted clearance + my @obj_islands_p = @{offset([$convex_hull], $distance_from_objects, 1, JT_SQUARE)}; + + # translate convex hull for each object copy and append it to the islands array + foreach my $copy (@{ $object->_shifted_copies }) { + my @copy_islands_p = map $_->clone, @obj_islands_p; + $_->translate(@$copy) for @copy_islands_p; + push @islands_p, @copy_islands_p; } } - $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ])); + $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex(\@islands_p)); } # calculate wiping points if needed From 713fcb5e8eabe21646b25febe43ac09f422370a0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 16:26:15 +0100 Subject: [PATCH 09/34] New methods in Slic3r::SVG C++ class --- xs/src/libslic3r/SVG.cpp | 64 ++++++++++++++++++++++++++++++++++------ xs/src/libslic3r/SVG.hpp | 16 ++++++++-- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/xs/src/libslic3r/SVG.cpp b/xs/src/libslic3r/SVG.cpp index db5ec7293..5374ed40b 100644 --- a/xs/src/libslic3r/SVG.cpp +++ b/xs/src/libslic3r/SVG.cpp @@ -1,8 +1,12 @@ #include "SVG.hpp" +#include + +#define COORD(x) ((float)unscale(x)*10) namespace Slic3r { SVG::SVG(const char* filename) + : arrows(true), filename(filename), fill("grey"), stroke("black") { this->f = fopen(filename, "w"); fprintf(this->f, @@ -13,13 +17,6 @@ SVG::SVG(const char* filename) " \n" " \n" ); - this->arrows = true; -} - -float -SVG::coordinate(long c) -{ - return (float)unscale(c)*10; } void @@ -27,7 +24,7 @@ SVG::AddLine(const Line &line) { fprintf(this->f, " coordinate(line.a.x), this->coordinate(line.a.y), this->coordinate(line.b.x), this->coordinate(line.b.y) + COORD(line.a.x), COORD(line.a.y), COORD(line.b.x), COORD(line.b.y) ); if (this->arrows) fprintf(this->f, " marker-end=\"url(#endArrow)\""); @@ -40,12 +37,61 @@ SVG::AddLine(const IntersectionLine &line) this->AddLine(Line(line.a, line.b)); } +void +SVG::draw(const ExPolygon &expolygon) +{ + std::string d; + Polygons pp = expolygon; + for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { + d += this->get_path_d(*p) + " "; + } + this->path(d, true); +} + +void +SVG::draw(const Polygon &polygon) +{ + this->path(this->get_path_d(polygon), true); +} + +void +SVG::draw(const Polyline &polyline) +{ + this->path(this->get_path_d(polyline), false); +} + +void +SVG::path(const std::string &d, bool fill) +{ + fprintf( + this->f, + " \n", + d.c_str(), + fill ? this->fill.c_str() : "none", + this->stroke.c_str(), + fill ? "0" : "2" + ); +} + +std::string +SVG::get_path_d(const MultiPoint &mp) const +{ + std::ostringstream d; + d << "M"; + for (Points::const_iterator p = mp.points.begin(); p != mp.points.end(); ++p) { + d << " " << COORD(p->x); + d << " " << COORD(p->y); + } + d << " z"; + return d.str(); +} + void SVG::Close() { fprintf(this->f, "\n"); fclose(this->f); - printf("SVG file written.\n"); + printf("SVG written to %s\n", this->filename.c_str()); } } diff --git a/xs/src/libslic3r/SVG.hpp b/xs/src/libslic3r/SVG.hpp index 5d4cfd56e..f312f4a3b 100644 --- a/xs/src/libslic3r/SVG.hpp +++ b/xs/src/libslic3r/SVG.hpp @@ -2,6 +2,7 @@ #define slic3r_SVG_hpp_ #include +#include "ExPolygon.hpp" #include "Line.hpp" #include "TriangleMesh.hpp" @@ -9,15 +10,24 @@ namespace Slic3r { class SVG { - private: - FILE* f; - float coordinate(long c); public: bool arrows; + std::string fill, stroke; + SVG(const char* filename); void AddLine(const Line &line); void AddLine(const IntersectionLine &line); + void draw(const ExPolygon &expolygon); + void draw(const Polygon &polygon); + void draw(const Polyline &polyline); void Close(); + + private: + std::string filename; + FILE* f; + + void path(const std::string &d, bool fill); + std::string get_path_d(const MultiPoint &polygon) const; }; } From 5e100abe2545ba5f61edce541c91b63aa3e14347 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 20:51:48 +0100 Subject: [PATCH 10/34] Added several drawing methods to Slic3r::SVG --- xs/src/libslic3r/SVG.cpp | 50 ++++++++++++++++++++++++++-------------- xs/src/libslic3r/SVG.hpp | 11 +++++---- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/xs/src/libslic3r/SVG.cpp b/xs/src/libslic3r/SVG.cpp index 5374ed40b..328d5421a 100644 --- a/xs/src/libslic3r/SVG.cpp +++ b/xs/src/libslic3r/SVG.cpp @@ -20,11 +20,11 @@ SVG::SVG(const char* filename) } void -SVG::AddLine(const Line &line) +SVG::draw(const Line &line, std::string stroke) { fprintf(this->f, - " arrows) fprintf(this->f, " marker-end=\"url(#endArrow)\""); @@ -34,30 +34,45 @@ SVG::AddLine(const Line &line) void SVG::AddLine(const IntersectionLine &line) { - this->AddLine(Line(line.a, line.b)); + this->draw(Line(line.a, line.b)); } void -SVG::draw(const ExPolygon &expolygon) +SVG::draw(const ExPolygon &expolygon, std::string fill) { + this->fill = fill; + std::string d; Polygons pp = expolygon; for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { - d += this->get_path_d(*p) + " "; + d += this->get_path_d(*p, true) + " "; } this->path(d, true); } void -SVG::draw(const Polygon &polygon) +SVG::draw(const Polygon &polygon, std::string fill) { - this->path(this->get_path_d(polygon), true); + this->fill = fill; + this->path(this->get_path_d(polygon, true), true); } void -SVG::draw(const Polyline &polyline) +SVG::draw(const Polyline &polyline, std::string stroke) { - this->path(this->get_path_d(polyline), false); + this->stroke = stroke; + this->path(this->get_path_d(polyline, false), false); +} + +void +SVG::draw(const Point &point, std::string fill, unsigned int radius) +{ + std::ostringstream svg; + svg << " "; + + fprintf(this->f, "%s\n", svg.str().c_str()); } void @@ -65,24 +80,25 @@ SVG::path(const std::string &d, bool fill) { fprintf( this->f, - " \n", + " \n", d.c_str(), fill ? this->fill.c_str() : "none", this->stroke.c_str(), - fill ? "0" : "2" + fill ? "0" : "2", + (this->arrows && !fill) ? " marker-end=\"url(#endArrow)\"" : "" ); } std::string -SVG::get_path_d(const MultiPoint &mp) const +SVG::get_path_d(const MultiPoint &mp, bool closed) const { std::ostringstream d; - d << "M"; + d << "M "; for (Points::const_iterator p = mp.points.begin(); p != mp.points.end(); ++p) { - d << " " << COORD(p->x); - d << " " << COORD(p->y); + d << COORD(p->x) << " "; + d << COORD(p->y) << " "; } - d << " z"; + if (closed) d << "z"; return d.str(); } diff --git a/xs/src/libslic3r/SVG.hpp b/xs/src/libslic3r/SVG.hpp index f312f4a3b..bb0d5adcf 100644 --- a/xs/src/libslic3r/SVG.hpp +++ b/xs/src/libslic3r/SVG.hpp @@ -15,11 +15,12 @@ class SVG std::string fill, stroke; SVG(const char* filename); - void AddLine(const Line &line); void AddLine(const IntersectionLine &line); - void draw(const ExPolygon &expolygon); - void draw(const Polygon &polygon); - void draw(const Polyline &polyline); + void draw(const Line &line, std::string stroke = "black"); + void draw(const ExPolygon &expolygon, std::string fill = "grey"); + void draw(const Polygon &polygon, std::string fill = "grey"); + void draw(const Polyline &polyline, std::string stroke = "black"); + void draw(const Point &point, std::string fill = "black", unsigned int radius = 3); void Close(); private: @@ -27,7 +28,7 @@ class SVG FILE* f; void path(const std::string &d, bool fill); - std::string get_path_d(const MultiPoint &polygon) const; + std::string get_path_d(const MultiPoint &mp, bool closed = false) const; }; } From 8f4cbefd0dd71cd6ae8dc149bd2e349905cd1ee8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 20:52:36 +0100 Subject: [PATCH 11/34] Lots of improvements to MotionPlanner/avoid_crossing_perimeters. Smoother paths and several edge cases now handled better --- lib/Slic3r/GCode.pm | 3 +- xs/src/libslic3r/ExPolygon.cpp | 27 ++ xs/src/libslic3r/ExPolygon.hpp | 3 + xs/src/libslic3r/ExPolygonCollection.cpp | 25 ++ xs/src/libslic3r/ExPolygonCollection.hpp | 3 + xs/src/libslic3r/Line.cpp | 7 + xs/src/libslic3r/Line.hpp | 4 +- xs/src/libslic3r/MotionPlanner.cpp | 304 +++++++++++++++-------- xs/src/libslic3r/MotionPlanner.hpp | 4 + xs/src/libslic3r/MultiPoint.cpp | 7 + xs/src/libslic3r/MultiPoint.hpp | 1 + xs/src/libslic3r/Point.cpp | 35 +++ xs/src/libslic3r/Point.hpp | 2 + xs/src/libslic3r/Polyline.cpp | 32 +++ xs/src/libslic3r/Polyline.hpp | 2 + xs/t/09_polyline.t | 38 ++- xs/xsp/Polyline.xsp | 2 + 17 files changed, 386 insertions(+), 113 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 70d0330c2..f39ded634 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -620,12 +620,11 @@ sub travel_to { # represent last_pos in absolute G-code coordinates my $last_pos = $gcodegen->last_pos->clone; - $last_pos->translate(@{$gcodegen->origin}); + $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); diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 34b75940b..2c5246aad 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -105,6 +105,23 @@ ExPolygon::contains(const Point &point) const return true; } +// inclusive version of contains() that also checks whether point is on boundaries +bool +ExPolygon::contains_b(const Point &point) const +{ + return this->contains(point) || this->has_boundary_point(point); +} + +bool +ExPolygon::has_boundary_point(const Point &point) const +{ + if (this->contour.has_boundary_point(point)) return true; + for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { + if (h->has_boundary_point(point)) return true; + } + return false; +} + Polygons ExPolygon::simplify_p(double tolerance) const { @@ -364,6 +381,16 @@ ExPolygon::triangulate_p2t(Polygons* polygons) const } } +Lines +ExPolygon::lines() const +{ + Lines lines; + this->contour.lines(&lines); + for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) + h->lines(&lines); + return lines; +} + #ifdef SLIC3RXS REGISTER_CLASS(ExPolygon, "ExPolygon"); diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index 9d65fcb2a..3d7cd3540 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -25,6 +25,8 @@ class ExPolygon bool contains(const Line &line) const; bool contains(const Polyline &polyline) const; bool contains(const Point &point) const; + bool contains_b(const Point &point) const; + bool has_boundary_point(const Point &point) const; Polygons simplify_p(double tolerance) const; ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons &expolygons) const; @@ -36,6 +38,7 @@ class ExPolygon void triangulate(Polygons* polygons) const; void triangulate_pp(Polygons* polygons) const; void triangulate_p2t(Polygons* polygons) const; + Lines lines() const; #ifdef SLIC3RXS void from_SV(SV* poly_sv); diff --git a/xs/src/libslic3r/ExPolygonCollection.cpp b/xs/src/libslic3r/ExPolygonCollection.cpp index 6f29dbb0d..aaa747d8d 100644 --- a/xs/src/libslic3r/ExPolygonCollection.cpp +++ b/xs/src/libslic3r/ExPolygonCollection.cpp @@ -3,6 +3,11 @@ namespace Slic3r { +ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon) +{ + this->expolygons.push_back(expolygon); +} + ExPolygonCollection::operator Points() const { Points points; @@ -68,6 +73,15 @@ template bool ExPolygonCollection::contains(const Point &item) const; template bool ExPolygonCollection::contains(const Line &item) const; template bool ExPolygonCollection::contains(const Polyline &item) const; +bool +ExPolygonCollection::contains_b(const Point &point) const +{ + for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { + if (it->contains_b(point)) return true; + } + return false; +} + void ExPolygonCollection::simplify(double tolerance) { @@ -87,6 +101,17 @@ ExPolygonCollection::convex_hull(Polygon* hull) const Slic3r::Geometry::convex_hull(pp, hull); } +Lines +ExPolygonCollection::lines() const +{ + Lines lines; + for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { + Lines ex_lines = it->lines(); + lines.insert(lines.end(), ex_lines.begin(), ex_lines.end()); + } + return lines; +} + #ifdef SLIC3RXS REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection"); #endif diff --git a/xs/src/libslic3r/ExPolygonCollection.hpp b/xs/src/libslic3r/ExPolygonCollection.hpp index 6aa5b0a56..cca6064d5 100644 --- a/xs/src/libslic3r/ExPolygonCollection.hpp +++ b/xs/src/libslic3r/ExPolygonCollection.hpp @@ -17,6 +17,7 @@ class ExPolygonCollection ExPolygons expolygons; ExPolygonCollection() {}; + ExPolygonCollection(const ExPolygon &expolygon); ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}; operator Points() const; operator Polygons() const; @@ -25,8 +26,10 @@ class ExPolygonCollection void translate(double x, double y); void rotate(double angle, const Point ¢er); template bool contains(const T &item) const; + bool contains_b(const Point &point) const; void simplify(double tolerance); void convex_hull(Polygon* hull) const; + Lines lines() const; }; } diff --git a/xs/src/libslic3r/Line.cpp b/xs/src/libslic3r/Line.cpp index ef8598ada..3cce6c971 100644 --- a/xs/src/libslic3r/Line.cpp +++ b/xs/src/libslic3r/Line.cpp @@ -16,6 +16,13 @@ Line::wkt() const return ss.str(); } +Line::operator Lines() const +{ + Lines lines; + lines.push_back(*this); + return lines; +} + Line::operator Polyline() const { Polyline pl; diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp index 52f3ef77f..76c385ce6 100644 --- a/xs/src/libslic3r/Line.hpp +++ b/xs/src/libslic3r/Line.hpp @@ -9,6 +9,7 @@ namespace Slic3r { class Line; class Linef3; class Polyline; +typedef std::vector Lines; class Line { @@ -18,6 +19,7 @@ class Line Line() {}; explicit Line(Point _a, Point _b): a(_a), b(_b) {}; std::string wkt() const; + operator Lines() const; operator Polyline() const; void scale(double factor); void translate(double x, double y); @@ -45,8 +47,6 @@ class Line #endif }; -typedef std::vector Lines; - class Linef3 { public: diff --git a/xs/src/libslic3r/MotionPlanner.cpp b/xs/src/libslic3r/MotionPlanner.cpp index 88b69ef82..9c76a3cd5 100644 --- a/xs/src/libslic3r/MotionPlanner.cpp +++ b/xs/src/libslic3r/MotionPlanner.cpp @@ -72,11 +72,23 @@ MotionPlanner::initialize() this->initialized = true; } +ExPolygonCollection +MotionPlanner::get_env(size_t island_idx) const +{ + if (island_idx == -1) { + return ExPolygonCollection(this->outer); + } else { + return this->inner[island_idx]; + } +} + void MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyline) { + // lazy generation of configuration space if (!this->initialized) this->initialize(); + // if we have an empty configuration space, return a straight move if (this->islands.empty()) { polyline->points.push_back(from); polyline->points.push_back(to); @@ -103,111 +115,163 @@ MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyl Point inner_from = from; Point inner_to = to; bool from_is_inside, to_is_inside; - if (island_idx == -1) { - if (!(from_is_inside = this->outer.contains(from))) { - // Find the closest inner point to start from. - from.nearest_point(this->outer, &inner_from); - } - if (!(to_is_inside = this->outer.contains(to))) { - // Find the closest inner point to start from. - to.nearest_point(this->outer, &inner_to); - } - } else { - if (!(from_is_inside = this->inner[island_idx].contains(from))) { - // Find the closest inner point to start from. - from.nearest_point(this->inner[island_idx], &inner_from); - } - if (!(to_is_inside = this->inner[island_idx].contains(to))) { - // Find the closest inner point to start from. - to.nearest_point(this->inner[island_idx], &inner_to); - } + + ExPolygonCollection env = this->get_env(island_idx); + if (!(from_is_inside = env.contains(from))) { + // Find the closest inner point to start from. + inner_from = this->nearest_env_point(env, from, to); + } + if (!(to_is_inside = env.contains(to))) { + // Find the closest inner point to start from. + inner_to = this->nearest_env_point(env, to, inner_from); } // perform actual path search MotionPlannerGraph* graph = this->init_graph(island_idx); graph->shortest_path(graph->find_node(inner_from), graph->find_node(inner_to), polyline); - polyline->points.insert(polyline->points.begin(), from); - polyline->points.push_back(to); + if (!from_is_inside) + polyline->points.insert(polyline->points.begin(), from); + + if (!to_is_inside) + polyline->points.push_back(to); + + { + // grow our environment slightly in order for simplify_by_visibility() + // to work best by considering moves on boundaries valid as well + ExPolygonCollection grown_env; + offset(env, &grown_env.expolygons, +SCALED_EPSILON); + + // remove unnecessary vertices + polyline->simplify_by_visibility(grown_env); + } + + /* + SVG svg("shortest_path.svg"); + svg.draw(this->outer); + svg.arrows = false; + for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) { + Point a = graph->nodes[it - graph->adjacency_list.begin()]; + for (std::vector::const_iterator n = it->begin(); n != it->end(); ++n) { + Point b = graph->nodes[n->target]; + svg.draw(Line(a, b)); + } + } + svg.arrows = true; + svg.draw(from); + svg.draw(inner_from, "red"); + svg.draw(to); + svg.draw(inner_to, "red"); + svg.draw(*polyline, "red"); + svg.Close(); + */ +} + +Point +MotionPlanner::nearest_env_point(const ExPolygonCollection &env, const Point &from, const Point &to) const +{ + /* In order to ensure that the move between 'from' and the initial env point does + not violate any of the configuration space boundaries, we limit our search to + the points that satisfy this condition. */ + + /* Assume that this method is never called when 'env' contains 'from'; + so 'from' is either inside a hole or outside all contours */ + + // get the points of the hole containing 'from', if any + Points pp; + for (ExPolygons::const_iterator ex = env.expolygons.begin(); ex != env.expolygons.end(); ++ex) { + for (Polygons::const_iterator h = ex->holes.begin(); h != ex->holes.end(); ++h) { + if (h->contains(from)) { + pp = *h; + } + } + if (!pp.empty()) break; + } + + /* If 'from' is not inside a hole, it's outside of all contours, so take all + contours' points */ + if (pp.empty()) { + for (ExPolygons::const_iterator ex = env.expolygons.begin(); ex != env.expolygons.end(); ++ex) { + Points contour_pp = ex->contour; + pp.insert(pp.end(), contour_pp.begin(), contour_pp.end()); + } + } + + /* Find the candidate result and check that it doesn't cross any boundary. + (We could skip all of the above polygon finding logic and directly test all points + in env, but this way we probably reduce complexity). */ + Polygons env_pp = env; + while (pp.size() >= 2) { + // find the point in pp that is closest to both 'from' and 'to' + size_t result = from.nearest_waypoint_index(pp, to); + + if (intersects((Lines)Line(from, pp[result]), env_pp)) { + // discard result + pp.erase(pp.begin() + result); + } else { + return pp[result]; + } + } + + // if we're here, return last point (better than nothing) + return pp.front(); } MotionPlannerGraph* MotionPlanner::init_graph(int island_idx) { if (this->graphs[island_idx + 1] == NULL) { - Polygons pp; - if (island_idx == -1) { - pp = this->outer; - } else { - pp = this->inner[island_idx]; - } - + // if this graph doesn't exist, initialize it MotionPlannerGraph* graph = this->graphs[island_idx + 1] = new MotionPlannerGraph(); - // add polygon boundaries as edges - size_t node_idx = 0; - Lines lines; - for (Polygons::const_iterator polygon = pp.begin(); polygon != pp.end(); ++polygon) { - graph->nodes.push_back(polygon->points.back()); - node_idx++; - for (Points::const_iterator p = polygon->points.begin(); p != polygon->points.end(); ++p) { - graph->nodes.push_back(*p); - double dist = graph->nodes[node_idx-1].distance_to(*p); - graph->add_edge(node_idx-1, node_idx, dist); - graph->add_edge(node_idx, node_idx-1, dist); - node_idx++; - } - polygon->lines(&lines); - } + /* We don't add polygon boundaries as graph edges, because we'd need to connect + them to the Voronoi-generated edges by recognizing coinciding nodes. */ - // add Voronoi edges as internal edges - { - typedef voronoi_diagram VD; - typedef std::map t_vd_vertices; - VD vd; - t_vd_vertices vd_vertices; + typedef voronoi_diagram VD; + VD vd; + + // mapping between Voronoi vertices and graph nodes + typedef std::map t_vd_vertices; + t_vd_vertices vd_vertices; + + // get boundaries as lines + ExPolygonCollection env = this->get_env(island_idx); + Lines lines = env.lines(); + boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd); + + // traverse the Voronoi diagram and generate graph nodes and edges + for (VD::const_edge_iterator edge = vd.edges().begin(); edge != vd.edges().end(); ++edge) { + if (edge->is_infinite()) continue; - boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd); - for (VD::const_edge_iterator edge = vd.edges().begin(); edge != vd.edges().end(); ++edge) { - if (edge->is_infinite()) continue; - - const VD::vertex_type* v0 = edge->vertex0(); - const VD::vertex_type* v1 = edge->vertex1(); - Point p0 = Point(v0->x(), v0->y()); - Point p1 = Point(v1->x(), v1->y()); - // contains() should probably be faster than contains(), - // and should it fail on any boundary points it's not a big problem - if (island_idx == -1) { - if (!this->outer.contains(p0) || !this->outer.contains(p1)) continue; - } else { - if (!this->inner[island_idx].contains(p0) || !this->inner[island_idx].contains(p1)) continue; - } - - t_vd_vertices::const_iterator i_v0 = vd_vertices.find(v0); - size_t v0_idx; - if (i_v0 == vd_vertices.end()) { - graph->nodes.push_back(p0); - v0_idx = node_idx; - vd_vertices[v0] = node_idx; - node_idx++; - } else { - v0_idx = i_v0->second; - } - - t_vd_vertices::const_iterator i_v1 = vd_vertices.find(v1); - size_t v1_idx; - if (i_v1 == vd_vertices.end()) { - graph->nodes.push_back(p1); - v1_idx = node_idx; - vd_vertices[v1] = node_idx; - node_idx++; - } else { - v1_idx = i_v1->second; - } - - double dist = graph->nodes[v0_idx].distance_to(graph->nodes[v1_idx]); - graph->add_edge(v0_idx, v1_idx, dist); + const VD::vertex_type* v0 = edge->vertex0(); + const VD::vertex_type* v1 = edge->vertex1(); + Point p0 = Point(v0->x(), v0->y()); + Point p1 = Point(v1->x(), v1->y()); + + // skip edge if any of its endpoints is outside our configuration space + if (!env.contains_b(p0) || !env.contains_b(p1)) continue; + + t_vd_vertices::const_iterator i_v0 = vd_vertices.find(v0); + size_t v0_idx; + if (i_v0 == vd_vertices.end()) { + graph->nodes.push_back(p0); + vd_vertices[v0] = v0_idx = graph->nodes.size()-1; + } else { + v0_idx = i_v0->second; } + + t_vd_vertices::const_iterator i_v1 = vd_vertices.find(v1); + size_t v1_idx; + if (i_v1 == vd_vertices.end()) { + graph->nodes.push_back(p1); + vd_vertices[v1] = v1_idx = graph->nodes.size()-1; + } else { + v1_idx = i_v1->second; + } + + // Euclidean distance is used as weight for the graph edge + double dist = graph->nodes[v0_idx].distance_to(graph->nodes[v1_idx]); + graph->add_edge(v0_idx, v1_idx, dist); } return graph; @@ -244,38 +308,61 @@ MotionPlannerGraph::shortest_path(size_t from, size_t to, Polyline* polyline) const weight_t max_weight = std::numeric_limits::infinity(); - std::vector min_distance; + std::vector dist; std::vector previous; { - int n = this->adjacency_list.size(); - min_distance.clear(); - min_distance.resize(n, max_weight); - min_distance[from] = 0; + // number of nodes + size_t n = this->adjacency_list.size(); + + // initialize dist and previous + dist.clear(); + dist.resize(n, max_weight); + dist[from] = 0; // distance from 'from' to itself previous.clear(); previous.resize(n, -1); - std::set > vertex_queue; - vertex_queue.insert(std::make_pair(min_distance[from], from)); - - while (!vertex_queue.empty()) + + // initialize the Q with all nodes + std::set Q; + for (node_t i = 0; i < n; ++i) Q.insert(i); + + while (!Q.empty()) { - weight_t dist = vertex_queue.begin()->first; - node_t u = vertex_queue.begin()->second; - vertex_queue.erase(vertex_queue.begin()); - - // Visit each edge exiting u + // get node in Q having the minimum dist ('from' in the first loop) + node_t u; + { + double min_dist = -1; + for (std::set::const_iterator n = Q.begin(); n != Q.end(); ++n) { + if (dist[*n] < min_dist || min_dist == -1) { + u = *n; + min_dist = dist[*n]; + } + } + } + Q.erase(u); + + // stop searching if we reached our destination + if (u == to) break; + + // Visit each edge starting from node u const std::vector &neighbors = this->adjacency_list[u]; for (std::vector::const_iterator neighbor_iter = neighbors.begin(); neighbor_iter != neighbors.end(); neighbor_iter++) { + // neighbor node is v node_t v = neighbor_iter->target; - weight_t weight = neighbor_iter->weight; - weight_t distance_through_u = dist + weight; - if (distance_through_u < min_distance[v]) { - vertex_queue.erase(std::make_pair(min_distance[v], v)); - min_distance[v] = distance_through_u; + + // skip if we already visited this + if (Q.find(v) == Q.end()) continue; + + // calculate total distance + weight_t alt = dist[u] + neighbor_iter->weight; + + // if total distance through u is shorter than the previous + // distance (if any) between 'from' and 'v', replace it + if (alt < dist[v]) { + dist[v] = alt; previous[v] = u; - vertex_queue.insert(std::make_pair(min_distance[v], v)); } } @@ -284,6 +371,7 @@ MotionPlannerGraph::shortest_path(size_t from, size_t to, Polyline* polyline) for (node_t vertex = to; vertex != -1; vertex = previous[vertex]) polyline->points.push_back(this->nodes[vertex]); + polyline->points.push_back(this->nodes[from]); polyline->reverse(); } diff --git a/xs/src/libslic3r/MotionPlanner.hpp b/xs/src/libslic3r/MotionPlanner.hpp index b40ed2ef7..c1617e184 100644 --- a/xs/src/libslic3r/MotionPlanner.hpp +++ b/xs/src/libslic3r/MotionPlanner.hpp @@ -33,10 +33,14 @@ class MotionPlanner void initialize(); MotionPlannerGraph* init_graph(int island_idx); + ExPolygonCollection get_env(size_t island_idx) const; + Point nearest_env_point(const ExPolygonCollection &env, const Point &from, const Point &to) const; }; class MotionPlannerGraph { + friend class MotionPlanner; + private: typedef size_t node_t; typedef double weight_t; diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp index e4944edca..6ed430cf7 100644 --- a/xs/src/libslic3r/MultiPoint.cpp +++ b/xs/src/libslic3r/MultiPoint.cpp @@ -76,6 +76,13 @@ MultiPoint::find_point(const Point &point) const return -1; // not found } +bool +MultiPoint::has_boundary_point(const Point &point) const +{ + double dist = point.distance_to(point.projection_onto(*this)); + return dist < SCALED_EPSILON; +} + void MultiPoint::bounding_box(BoundingBox* bb) const { diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index eaca9b8eb..96c2876f5 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -30,6 +30,7 @@ class MultiPoint double length() const; bool is_valid() const; int find_point(const Point &point) const; + bool has_boundary_point(const Point &point) const; void bounding_box(BoundingBox* bb) const; static Points _douglas_peucker(const Points &points, const double tolerance); diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index d40a8efc7..9a564f879 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -104,6 +104,32 @@ Point::nearest_point_index(const PointConstPtrs &points) const return idx; } +/* This method finds the point that is closest to both this point and the supplied one */ +size_t +Point::nearest_waypoint_index(const Points &points, const Point &dest) const +{ + size_t idx = -1; + double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough + + for (Points::const_iterator p = points.begin(); p != points.end(); ++p) { + // distance from this to candidate + double d = pow(this->x - p->x, 2) + pow(this->y - p->y, 2); + + // distance from candidate to dest + d += pow(p->x - dest.x, 2) + pow(p->y - dest.y, 2); + + // if the total distance is greater than current min distance, ignore it + if (distance != -1 && d > distance) continue; + + idx = p - points.begin(); + distance = d; + + if (distance < EPSILON) break; + } + + return idx; +} + int Point::nearest_point_index(const PointPtrs &points) const { @@ -123,6 +149,15 @@ Point::nearest_point(const Points &points, Point* point) const return true; } +bool +Point::nearest_waypoint(const Points &points, const Point &dest, Point* point) const +{ + int idx = this->nearest_waypoint_index(points, dest); + if (idx == -1) return false; + *point = points.at(idx); + return true; +} + double Point::distance_to(const Point &point) const { diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 6fa216405..a3283c260 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -45,7 +45,9 @@ class Point int nearest_point_index(const Points &points) const; int nearest_point_index(const PointConstPtrs &points) const; int nearest_point_index(const PointPtrs &points) const; + size_t nearest_waypoint_index(const Points &points, const Point &point) const; bool nearest_point(const Points &points, Point* point) const; + bool nearest_waypoint(const Points &points, const Point &dest, Point* point) const; double distance_to(const Point &point) const; double distance_to(const Line &line) const; double perp_distance_to(const Line &line) const; diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index 7f27adf58..ba41893f8 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -1,4 +1,6 @@ #include "Polyline.hpp" +#include "ExPolygon.hpp" +#include "ExPolygonCollection.hpp" #include "Line.hpp" #include "Polygon.hpp" #include @@ -127,6 +129,36 @@ Polyline::simplify(double tolerance) this->points = MultiPoint::_douglas_peucker(this->points, tolerance); } +/* This method simplifies all *lines* contained in the supplied area */ +template +void +Polyline::simplify_by_visibility(const T &area) +{ + Points &pp = this->points; + + // find first point in area + size_t start = 0; + while (start < pp.size() && !area.contains(pp[start])) { + start++; + } + + for (size_t s = start; s < pp.size() && !pp.empty(); ++s) { + // find the farthest point to which we can build + // a line that is contained in the supplied area + // a binary search would be more efficient for this + for (size_t e = pp.size()-1; e > (s + 1); --e) { + if (area.contains(Line(pp[s], pp[e]))) { + // we can suppress points between s and e + pp.erase(pp.begin() + s + 1, pp.begin() + e); + + // repeat recursively until no further simplification is possible + return this->simplify_by_visibility(area); + } + } + } +} +template void Polyline::simplify_by_visibility(const ExPolygonCollection &area); + void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const { diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index 2a9a79032..3fe89f26e 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -7,6 +7,7 @@ namespace Slic3r { +class ExPolygon; class Polyline; typedef std::vector Polylines; @@ -23,6 +24,7 @@ class Polyline : public MultiPoint { void extend_start(double distance); void equally_spaced_points(double distance, Points* points) const; void simplify(double tolerance); + template void simplify_by_visibility(const T &area); void split_at(const Point &point, Polyline* p1, Polyline* p2) const; bool is_straight() const; std::string wkt() const; diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t index 824f049e9..fa5a3c48b 100644 --- a/xs/t/09_polyline.t +++ b/xs/t/09_polyline.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 18; +use Test::More tests => 21; my $points = [ [100, 100], @@ -88,4 +88,40 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; is scalar(@$p2), 4, 'split_at'; } +{ + my $polyline = Slic3r::Polyline->new( + map [$_,10], (0,10,20,30,40,50,60) + ); + { + my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( + [25,0], [55,0], [55,30], [25,30], + )); + my $p = $polyline->clone; + $p->simplify_by_visibility($expolygon); + is_deeply $p->pp, [ + map [$_,10], (0,10,20,30,50,60) + ], 'simplify_by_visibility()'; + } + { + my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( + [-15,0], [75,0], [75,30], [-15,30], + )); + my $p = $polyline->clone; + $p->simplify_by_visibility($expolygon); + is_deeply $p->pp, [ + map [$_,10], (0,60) + ], 'simplify_by_visibility()'; + } + { + my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( + [-15,0], [25,0], [25,30], [-15,30], + )); + my $p = $polyline->clone; + $p->simplify_by_visibility($expolygon); + is_deeply $p->pp, [ + map [$_,10], (0,20,30,40,50,60) + ], 'simplify_by_visibility()'; + } +} + __END__ diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 2df0d17c1..a8eb527d5 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -32,6 +32,8 @@ void extend_end(double distance); void extend_start(double distance); void simplify(double tolerance); + void simplify_by_visibility(ExPolygon* expolygon) + %code{% THIS->simplify_by_visibility(*expolygon); %}; void split_at(Point* point, Polyline* p1, Polyline* p2) %code{% THIS->split_at(*point, p1, p2); %}; bool is_straight(); From 39172d5a08f22aaa660ec2d836c2857ef6383777 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 20:54:32 +0100 Subject: [PATCH 12/34] Fixed typo causing test to fail --- 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 f39ded634..0249b5be3 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -374,7 +374,7 @@ sub needs_retraction { return 0; } - if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel)) { + if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_polyline($travel)) { # skip retraction if this is a travel move inside a support material island return 0; } From d4ae734659196a3a784d297c879bda5ee8d208f8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 20:58:07 +0100 Subject: [PATCH 13/34] Minor improvement to IntersectionLine (now subclasses Line) --- xs/src/libslic3r/TriangleMesh.cpp | 6 ++---- xs/src/libslic3r/TriangleMesh.hpp | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 94e5668dd..7c2a0a344 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -614,10 +614,8 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int if (!points.empty()) { assert(points.size() == 2); // facets must intersect each plane 0 or 2 times IntersectionLine line; - line.a.x = points[1].x; - line.a.y = points[1].y; - line.b.x = points[0].x; - line.b.y = points[0].y; + line.a = (Point)points[1]; + line.b = (Point)points[0]; line.a_id = points[1].point_id; line.b_id = points[0].point_id; line.edge_a_id = points[1].edge_id; diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index afde8a65d..227bceae0 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -5,6 +5,7 @@ #include #include #include "BoundingBox.hpp" +#include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "ExPolygon.hpp" @@ -71,11 +72,9 @@ class IntersectionPoint : public Point IntersectionPoint() : point_id(-1), edge_id(-1) {}; }; -class IntersectionLine +class IntersectionLine : public Line { public: - Point a; - Point b; int a_id; int b_id; int edge_a_id; From 49817aac343cfd2b50618c9215026fcb4a1ce69b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 21:04:00 +0100 Subject: [PATCH 14/34] Removed test that doesn't apply anymore because the logic of only_retract_when_crossing_perimeters is much more complex now --- t/retraction.t | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/t/retraction.t b/t/retraction.t index f1656bc7f..4a8993ae5 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -1,4 +1,4 @@ -use Test::More tests => 19; +use Test::More tests => 18; use strict; use warnings; @@ -200,29 +200,4 @@ use Slic3r::Test qw(_eq); ok $retracted, 'retracting also when --retract-length is 0 but --use-firmware-retraction is enabled'; } -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('only_retract_when_crossing_perimeters', 1); - $config->set('fill_density', 0); - - my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); - my $retracted = 0; - my $traveling_without_retraction = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{retracting}) { - $retracted = 1; - } elsif ($info->{extruding} && $retracted) { - $retracted = 0; - } elsif ($info->{travel} && !$retracted) { - if ($info->{dist_XY} > $config->retract_before_travel->[0]) { - $traveling_without_retraction = 1; - } - } - }); - - ok !$traveling_without_retraction, 'always retract when using only_retract_when_crossing_perimeters and fill_density = 0'; -} - __END__ From 0de1c235a96141bd04eaaef2f60561e808936ed0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 21:08:33 +0100 Subject: [PATCH 15/34] Reversed mouse wheel zooming in 3D once more. #2478 --- lib/Slic3r/GUI/PreviewCanvas.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index ffa7c61b7..e8f0b4c36 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -90,7 +90,7 @@ sub new { my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); $zoom = max(min($zoom, 4), -4); $zoom /= 10; - $self->_zoom($self->_zoom * (1-$zoom)); + $self->_zoom($self->_zoom / (1-$zoom)); # In order to zoom around the mouse point we need to translate # the camera target From 21a660c56c1cb8ee05d82a9c77be1b123ecc4e13 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 21:29:32 +0100 Subject: [PATCH 16/34] Fix compilation --- xs/src/libslic3r/Polyline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index ba41893f8..f56dd03b9 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -157,6 +157,7 @@ Polyline::simplify_by_visibility(const T &area) } } } +template void Polyline::simplify_by_visibility(const ExPolygon &area); template void Polyline::simplify_by_visibility(const ExPolygonCollection &area); void From 1b766f12ca91a526c76434ec7ac619a9b6f5a117 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 6 Jan 2015 23:30:28 +0100 Subject: [PATCH 17/34] Minor fix after recent changes in MotionPlanner --- xs/src/libslic3r/MotionPlanner.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/xs/src/libslic3r/MotionPlanner.cpp b/xs/src/libslic3r/MotionPlanner.cpp index 9c76a3cd5..06b5f5e21 100644 --- a/xs/src/libslic3r/MotionPlanner.cpp +++ b/xs/src/libslic3r/MotionPlanner.cpp @@ -130,11 +130,8 @@ MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyl MotionPlannerGraph* graph = this->init_graph(island_idx); graph->shortest_path(graph->find_node(inner_from), graph->find_node(inner_to), polyline); - if (!from_is_inside) - polyline->points.insert(polyline->points.begin(), from); - - if (!to_is_inside) - polyline->points.push_back(to); + polyline->points.insert(polyline->points.begin(), from); + polyline->points.push_back(to); { // grow our environment slightly in order for simplify_by_visibility() From 4688ae2fb696cc63e576e8bcf45e81f4ba610d24 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 11:13:56 +0100 Subject: [PATCH 18/34] Bugfix: rotation in 3D view was randomly stopping. #2482 --- lib/Slic3r/GUI/PreviewCanvas.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index e8f0b4c36..61a2b20b4 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -171,7 +171,7 @@ sub mouse_event { $self->_drag_start_pos($cur_pos); $self->_dragged(1); $self->Refresh; - } elsif ($e->Dragging && !defined $self->_hover_volume_idx) { + } elsif ($e->Dragging) { if ($e->LeftIsDown) { # if dragging over blank area with left button, rotate if (defined $self->_drag_start_pos) { From 6962b8dddd59624f914bc0508b1a1cadd22f0dfa Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 11:58:22 +0100 Subject: [PATCH 19/34] Glitches when panning with middle mouse button. #2454 --- lib/Slic3r/GUI/PreviewCanvas.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 61a2b20b4..67bc49757 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -208,7 +208,7 @@ sub mouse_event { } $self->_drag_start_xy($pos); } - } elsif ($e->LeftUp || $e->RightUp) { + } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) { if ($self->on_move && defined $self->_drag_volume_idx) { $self->on_move->($self->_drag_volume_idx) if $self->_dragged; } From 82ec03fc233c993bb8852692bd2ce79235c95cd4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 16:04:53 +0100 Subject: [PATCH 20/34] Refactored perimeter generation code into a new separate class for easier unit testing --- lib/Slic3r.pm | 1 + lib/Slic3r/Layer/PerimeterGenerator.pm | 447 +++++++++++++++++++++++++ lib/Slic3r/Layer/Region.pm | 433 ++---------------------- 3 files changed, 468 insertions(+), 413 deletions(-) create mode 100644 lib/Slic3r/Layer/PerimeterGenerator.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 516eb2996..e0c29ada5 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -58,6 +58,7 @@ 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; diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm new file mode 100644 index 000000000..41ce10970 --- /dev/null +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -0,0 +1,447 @@ +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 union_pt intersection_ppl diff_ppl); +use Slic3r::Surface ':types'; + +has 'slices' => (is => 'ro', required => 1); +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::Region->new }); +has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); + +# 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 process { + my ($self) = @_; + + # other perimeters + my $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 + my $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 + my $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); + + my @contours = (); # array of Polygons with ccw orientation + my @holes = (); # array of Polygons with cw orientation + my @thin_walls = (); # array of ExPolygons + + # 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); + + my @last = @{$surface->expolygon}; + my @gaps = (); # array of ExPolygons + if ($loop_number > 0) { + # we loop one time more than needed in order to find gaps after the last perimeter was applied + for my $i (1 .. ($loop_number+1)) { # outer loop is 1 + my @offsets = (); + if ($i == 1) { + # 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_ex( + \@last, + offset(\@offsets, +0.5*$ext_pwidth), + 1, # medial axis requires non-overlapping geometry + ); + push @thin_walls, @$diff; + } + } else { + my $distance = ($i == 2) ? $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*$pspacing), + offset(\@offsets, +0.5*$pspacing + 10), # safety offset + ); + push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff; + } + } + + last if !@offsets; + last if $i > $loop_number; # we were only looking for gaps this time + + # clone polygons because these ExPolygons will go out of scope very soon + @last = @offsets; + foreach my $polygon (@offsets) { + if ($polygon->is_counter_clockwise) { + push @contours, $polygon; + } else { + push @holes, $polygon; + } + } + } + } + + # fill gaps + if (@gaps) { + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "gaps.svg", + expolygons => \@gaps, + ); + } + + # where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth + # where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth + my @gap_sizes = ( + [ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ], + [ 0.5*$pwidth, $pwidth, unscale 0.5*$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)}; + } + } + + # 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 $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)} ], + -($pspacing/2 + $min_perimeter_infill_spacing/2), + +$min_perimeter_infill_spacing/2, + )}; + } + + + # process thin walls by collapsing slices to single passes + my @thin_wall_polylines = (); + if (@thin_walls) { + # 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 = $pwidth / 4; + @thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$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_wall_polylines = map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls; + Slic3r::debugf " %d thin walls detected\n", scalar(@thin_wall_polylines) if $Slic3r::debug; + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "medial_axis.svg", + no_arrows => 1, + expolygons => \@thin_walls, + green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ], + red_polylines => \@thin_wall_polylines, + ); + } + } + + # find nesting hierarchies separately for contours and holes + my $contours_pt = union_pt(\@contours); + my $holes_pt = union_pt(\@holes); + + # prepare grown lower layer slices for overhang detection + my $lower_slices = Slic3r::ExPolygon::Collection->new; + 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); + $lower_slices->append($_) + for @{offset_ex([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2)}; + } + my $lower_slices_p = $lower_slices->polygons; + + # prepare a coderef for traversing the PolyTree object + # external contours are root items of $contours_pt + # internal contours are the ones next to external + my $traverse; + $traverse = sub { + my ($polynodes, $depth, $is_contour) = @_; + + # convert all polynodes to ExtrusionLoop objects + my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection + my @children = (); + foreach my $polynode (@$polynodes) { + my $polygon = ($polynode->{outer} // $polynode->{hole})->clone; + + my $role = EXTR_ROLE_PERIMETER; + my $loop_role = EXTRL_ROLE_DEFAULT; + + my $root_level = $depth == 0; + my $no_children = !@{ $polynode->{children} }; + my $is_external = $is_contour ? $root_level : $no_children; + my $is_internal = $is_contour ? $no_children : $root_level; + if ($is_contour && $is_internal) { + # internal perimeters are root level in case of holes + # and items with no children in case of contours + # 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; + } + if ($is_external) { + # external perimeters are root level in case of contours + # and items with no children in case of holes + $role = EXTR_ROLE_EXTERNAL_PERIMETER; + } + + # detect overhanging/bridging perimeters + my @paths = (); + if ($self->config->overhangs && $self->layer_id > 0) { + # get non-overhang paths by intersecting this loop with the grown lower slices + foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) { + push @paths, Slic3r::ExtrusionPath->new( + polyline => $polyline, + role => $role, + mm3_per_mm => ($is_external ? $ext_mm3_per_mm : $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([ $polygon ], $lower_slices_p) }) { + push @paths, Slic3r::ExtrusionPath->new( + polyline => $polyline, + role => EXTR_ROLE_OVERHANG_PERIMETER, + mm3_per_mm => $mm3_per_mm_overhang, + width => $self->overhang_flow->width, + height => $self->layer_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 => $polygon->split_at_first_point, + role => $role, + mm3_per_mm => $mm3_per_mm, + width => $self->perimeter_flow->width, + height => $self->layer_height, + ); + } + my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths); + $loop->role($loop_role); + + # return ccw contours and cw holes + # GCode.pm will convert all of them to ccw, but it needs to know + # what the holes are in order to compute the correct inwards move + # We do this on the final Loop object instead of the polygon because + # overhang clipping might have reversed its order since Clipper does + # not preserve polyline orientation. + if ($is_contour) { + $loop->make_counter_clockwise; + } else { + $loop->make_clockwise; + } + $collection->append($loop); + + # save the children + push @children, $polynode->{children}; + } + + # if we're handling the top-level contours, add thin walls as candidates too + # in order to include them in the nearest-neighbor search + if ($is_contour && $depth == 0) { + foreach my $polyline (@thin_wall_polylines) { + $collection->append(Slic3r::ExtrusionPath->new( + polyline => $polyline, + role => EXTR_ROLE_EXTERNAL_PERIMETER, + mm3_per_mm => $mm3_per_mm, + width => $self->perimeter_flow->width, + height => $self->layer_height, + )); + } + } + + # use a nearest neighbor search to order these children + # TODO: supply second argument to chained_path() too? + # (We used to skip this chiained_path() when $is_contour && + # $depth == 0 because slices are ordered at G_code export + # time, but multiple top-level perimeters might belong to + # the same slice actually, so that was a broken optimization.) + my $sorted_collection = $collection->chained_path_indices(0); + my @orig_indices = @{$sorted_collection->orig_indices}; + + my @loops = (); + foreach my $loop (@$sorted_collection) { + my $orig_index = shift @orig_indices; + + if ($loop->isa('Slic3r::ExtrusionPath')) { + push @loops, $loop->clone; + } else { + # if this is an external contour find all holes belonging to this contour(s) + # and prepend them + if ($is_contour && $depth == 0) { + # $loop is the outermost loop of an island + my @holes = (); + for (my $i = 0; $i <= $#$holes_pt; $i++) { + if ($loop->polygon->contains_point($holes_pt->[$i]{outer}->first_point)) { + push @holes, splice @$holes_pt, $i, 1; # remove from candidates to reduce complexity + $i--; + } + } + + # order holes efficiently + @holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}]; + + push @loops, reverse map $traverse->([$_], 0, 0), @holes; + } + + # traverse children and prepend them to this loop + push @loops, $traverse->($children[$orig_index], $depth+1, $is_contour); + push @loops, $loop->clone; + } + } + return @loops; + }; + + # order loops from inner to outer (in terms of object slices) + my @loops = $traverse->($contours_pt, 0, 1); + + # 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 + @loops = reverse @loops + if $self->config->external_perimeters_first + || ($self->layer_id == 0 && $self->print_config->brim_width > 0); + + # append perimeters + $self->loops->append($_) for @loops; +} + +sub _fill_gaps { + my ($self, $min, $max, $w, $gaps) = @_; + + my $this = diff_ex( + offset2([ map @$_, @$gaps ], -$min/2, +$min/2), + offset2([ map @$_, @$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/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 8e3db00e5..88f0db01f 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -2,14 +2,11 @@ package Slic3r::Layer::Region; use strict; use warnings; -use List::Util qw(sum first); -use Slic3r::ExtrusionLoop ':roles'; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(PI A B scale unscale chained_path); -use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex - offset offset_ex offset2 offset2_ex union_pt diff intersection - union diff intersection_ppl diff_ppl); +use Slic3r::Geometry qw(scale); +use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex + ); use Slic3r::Surface ':types'; @@ -31,418 +28,28 @@ sub config { return $_[0]->region->config; } sub make_perimeters { my ($self, $slices, $fill_surfaces) = @_; - # other perimeters - my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER); - my $mm3_per_mm = $perimeter_flow->mm3_per_mm; - my $pwidth = $perimeter_flow->scaled_width; - my $pspacing = $perimeter_flow->scaled_spacing; - - # external perimeters - my $ext_perimeter_flow = $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER); - my $ext_mm3_per_mm = $ext_perimeter_flow->mm3_per_mm; - my $ext_pwidth = $ext_perimeter_flow->scaled_width; - my $ext_pspacing = scale($ext_perimeter_flow->spacing_to($perimeter_flow)); - - # overhang perimeters - my $overhang_flow = $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object); - my $mm3_per_mm_overhang = $overhang_flow->mm3_per_mm; - - # solid infill - my $solid_infill_flow = $self->flow(FLOW_ROLE_SOLID_INFILL); - my $ispacing = $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); - $self->perimeters->clear; $self->thin_fills->clear; - my @contours = (); # array of Polygons with ccw orientation - my @holes = (); # array of Polygons with cw orientation - my @thin_walls = (); # array of ExPolygons - - # we need to process each island separately because we might have different - # extra perimeters for each one - foreach my $surface (@$slices) { - # detect how many perimeters must be generated for this island - my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0); + my $generator = Slic3r::Layer::PerimeterGenerator->new( + # input: + config => $self->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), - my @last = @{$surface->expolygon}; - my @gaps = (); # array of ExPolygons - if ($loop_number > 0) { - # we loop one time more than needed in order to find gaps after the last perimeter was applied - for my $i (1 .. ($loop_number+1)) { # outer loop is 1 - my @offsets = (); - if ($i == 1) { - # 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_ex( - \@last, - offset(\@offsets, +0.5*$ext_pwidth), - 1, # medial axis requires non-overlapping geometry - ); - push @thin_walls, @$diff; - } - } else { - my $distance = ($i == 2) ? $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->region->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*$pspacing), - offset(\@offsets, +0.5*$pspacing + 10), # safety offset - ); - push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff; - } - } - - last if !@offsets; - last if $i > $loop_number; # we were only looking for gaps this time - - # clone polygons because these ExPolygons will go out of scope very soon - @last = @offsets; - foreach my $polygon (@offsets) { - if ($polygon->is_counter_clockwise) { - push @contours, $polygon; - } else { - push @holes, $polygon; - } - } - } - } - - # fill gaps - if (@gaps) { - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "gaps.svg", - expolygons => \@gaps, - ); - } - - # where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth - # where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth - my @gap_sizes = ( - [ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ], - [ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ], - ); - foreach my $gap_size (@gap_sizes) { - my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps); - $self->thin_fills->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)}; - } - } - - # 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 $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - $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)} ], - -($pspacing/2 + $min_perimeter_infill_spacing/2), - +$min_perimeter_infill_spacing/2, - )}; - } - - - # process thin walls by collapsing slices to single passes - my @thin_wall_polylines = (); - if (@thin_walls) { - # 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 = $pwidth / 4; - @thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$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_wall_polylines = map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls; - Slic3r::debugf " %d thin walls detected\n", scalar(@thin_wall_polylines) if $Slic3r::debug; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "medial_axis.svg", - no_arrows => 1, - expolygons => \@thin_walls, - green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ], - red_polylines => \@thin_wall_polylines, - ); - } - } - - # find nesting hierarchies separately for contours and holes - my $contours_pt = union_pt(\@contours); - my $holes_pt = union_pt(\@holes); - - # prepare grown lower layer slices for overhang detection - my $lower_slices = Slic3r::ExPolygon::Collection->new; - if ($self->layer->lower_layer && $self->region->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->layer->print->config->get_at('nozzle_diameter', $self->region->config->perimeter_extruder-1); - $lower_slices->append($_) - for @{offset_ex([ map @$_, @{$self->layer->lower_layer->slices} ], scale +$nozzle_diameter/2)}; - } - my $lower_slices_p = $lower_slices->polygons; - - # prepare a coderef for traversing the PolyTree object - # external contours are root items of $contours_pt - # internal contours are the ones next to external - my $traverse; - $traverse = sub { - my ($polynodes, $depth, $is_contour) = @_; - - # convert all polynodes to ExtrusionLoop objects - my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection - my @children = (); - foreach my $polynode (@$polynodes) { - my $polygon = ($polynode->{outer} // $polynode->{hole})->clone; - - my $role = EXTR_ROLE_PERIMETER; - my $loop_role = EXTRL_ROLE_DEFAULT; - - my $root_level = $depth == 0; - my $no_children = !@{ $polynode->{children} }; - my $is_external = $is_contour ? $root_level : $no_children; - my $is_internal = $is_contour ? $no_children : $root_level; - if ($is_contour && $is_internal) { - # internal perimeters are root level in case of holes - # and items with no children in case of contours - # 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; - } - if ($is_external) { - # external perimeters are root level in case of contours - # and items with no children in case of holes - $role = EXTR_ROLE_EXTERNAL_PERIMETER; - } - - # detect overhanging/bridging perimeters - my @paths = (); - if ($self->region->config->overhangs && $self->layer->id > 0) { - # get non-overhang paths by intersecting this loop with the grown lower slices - foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => $role, - mm3_per_mm => ($is_external ? $ext_mm3_per_mm : $mm3_per_mm), - width => ($is_external ? $ext_perimeter_flow->width : $perimeter_flow->width), - height => $self->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([ $polygon ], $lower_slices_p) }) { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => EXTR_ROLE_OVERHANG_PERIMETER, - mm3_per_mm => $mm3_per_mm_overhang, - width => $overhang_flow->width, - height => $self->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 => $polygon->split_at_first_point, - role => $role, - mm3_per_mm => $mm3_per_mm, - width => $perimeter_flow->width, - height => $self->height, - ); - } - my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths); - $loop->role($loop_role); - - # return ccw contours and cw holes - # GCode.pm will convert all of them to ccw, but it needs to know - # what the holes are in order to compute the correct inwards move - # We do this on the final Loop object instead of the polygon because - # overhang clipping might have reversed its order since Clipper does - # not preserve polyline orientation. - if ($is_contour) { - $loop->make_counter_clockwise; - } else { - $loop->make_clockwise; - } - $collection->append($loop); - - # save the children - push @children, $polynode->{children}; - } - - # if we're handling the top-level contours, add thin walls as candidates too - # in order to include them in the nearest-neighbor search - if ($is_contour && $depth == 0) { - foreach my $polyline (@thin_wall_polylines) { - $collection->append(Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => $mm3_per_mm, - width => $perimeter_flow->width, - height => $self->height, - )); - } - } - - # use a nearest neighbor search to order these children - # TODO: supply second argument to chained_path() too? - # (We used to skip this chiained_path() when $is_contour && - # $depth == 0 because slices are ordered at G_code export - # time, but multiple top-level perimeters might belong to - # the same slice actually, so that was a broken optimization.) - my $sorted_collection = $collection->chained_path_indices(0); - my @orig_indices = @{$sorted_collection->orig_indices}; - - my @loops = (); - foreach my $loop (@$sorted_collection) { - my $orig_index = shift @orig_indices; - - if ($loop->isa('Slic3r::ExtrusionPath')) { - push @loops, $loop->clone; - } else { - # if this is an external contour find all holes belonging to this contour(s) - # and prepend them - if ($is_contour && $depth == 0) { - # $loop is the outermost loop of an island - my @holes = (); - for (my $i = 0; $i <= $#$holes_pt; $i++) { - if ($loop->polygon->contains_point($holes_pt->[$i]{outer}->first_point)) { - push @holes, splice @$holes_pt, $i, 1; # remove from candidates to reduce complexity - $i--; - } - } - - # order holes efficiently - @holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}]; - - push @loops, reverse map $traverse->([$_], 0, 0), @holes; - } - - # traverse children and prepend them to this loop - push @loops, $traverse->($children[$orig_index], $depth+1, $is_contour); - push @loops, $loop->clone; - } - } - return @loops; - }; - - # order loops from inner to outer (in terms of object slices) - my @loops = $traverse->($contours_pt, 0, 1); - - # 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 - @loops = reverse @loops - if $self->region->config->external_perimeters_first - || ($self->layer->id == 0 && $self->print->config->brim_width > 0); - - # append perimeters - $self->perimeters->append($_) for @loops; -} - -sub _fill_gaps { - my ($self, $min, $max, $w, $gaps) = @_; - - my $this = diff_ex( - offset2([ map @$_, @$gaps ], -$min/2, +$min/2), - offset2([ map @$_, @$gaps ], -$max/2, +$max/2), - 1, + # output: + loops => $self->perimeters, + gap_fill => $self->thin_fills, + fill_surfaces => $fill_surfaces, ); - 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 = $self->flow(FLOW_ROLE_SOLID_INFILL, 0, $w); - my %path_args = ( - role => EXTR_ROLE_GAPFILL, - mm3_per_mm => $flow->mm3_per_mm, - width => $flow->width, - height => $self->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; + $generator->process; } sub prepare_fill_surfaces { From b085710a4b9c58c9346d0b71669416e23999ba8c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 16:16:00 +0100 Subject: [PATCH 21/34] Further refactoring to PerimeterGenerator: remove the $traverse closure --- lib/Slic3r/Layer/PerimeterGenerator.pm | 325 +++++++++++++------------ 1 file changed, 163 insertions(+), 162 deletions(-) diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index 41ce10970..2feffb7df 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -18,6 +18,12 @@ has 'overhang_flow' => (is => 'ro', required => 1); has 'solid_infill_flow' => (is => 'ro', required => 1); has 'config' => (is => 'ro', default => sub { Slic3r::Config::Region->new }); has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); +has '_lower_slices_p' => (is => 'rw'); +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 }); @@ -32,17 +38,17 @@ sub process { my ($self) = @_; # other perimeters - my $mm3_per_mm = $self->perimeter_flow->mm3_per_mm; + $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 - my $ext_mm3_per_mm = $self->ext_perimeter_flow->mm3_per_mm; + $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 - my $mm3_per_mm_overhang = $self->overhang_flow->mm3_per_mm; + $self->_mm3_per_mm_overhang($self->overhang_flow->mm3_per_mm); # solid infill my $ispacing = $self->solid_infill_flow->scaled_spacing; @@ -192,7 +198,6 @@ sub process { # process thin walls by collapsing slices to single passes - my @thin_wall_polylines = (); if (@thin_walls) { # 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) @@ -200,8 +205,8 @@ sub process { @thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$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_wall_polylines = map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls; - Slic3r::debugf " %d thin walls detected\n", scalar(@thin_wall_polylines) if $Slic3r::debug; + $self->_thin_wall_polylines([ map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls ]); + Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->_thin_wall_polylines}) if $Slic3r::debug; if (0) { require "Slic3r/SVG.pm"; @@ -210,14 +215,14 @@ sub process { no_arrows => 1, expolygons => \@thin_walls, green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ], - red_polylines => \@thin_wall_polylines, + red_polylines => $self->_thin_wall_polylines, ); } } # find nesting hierarchies separately for contours and holes my $contours_pt = union_pt(\@contours); - my $holes_pt = union_pt(\@holes); + $self->_holes_pt(union_pt(\@holes)); # prepare grown lower layer slices for overhang detection my $lower_slices = Slic3r::ExPolygon::Collection->new; @@ -229,162 +234,10 @@ sub process { $lower_slices->append($_) for @{offset_ex([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2)}; } - my $lower_slices_p = $lower_slices->polygons; - - # prepare a coderef for traversing the PolyTree object - # external contours are root items of $contours_pt - # internal contours are the ones next to external - my $traverse; - $traverse = sub { - my ($polynodes, $depth, $is_contour) = @_; - - # convert all polynodes to ExtrusionLoop objects - my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection - my @children = (); - foreach my $polynode (@$polynodes) { - my $polygon = ($polynode->{outer} // $polynode->{hole})->clone; - - my $role = EXTR_ROLE_PERIMETER; - my $loop_role = EXTRL_ROLE_DEFAULT; - - my $root_level = $depth == 0; - my $no_children = !@{ $polynode->{children} }; - my $is_external = $is_contour ? $root_level : $no_children; - my $is_internal = $is_contour ? $no_children : $root_level; - if ($is_contour && $is_internal) { - # internal perimeters are root level in case of holes - # and items with no children in case of contours - # 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; - } - if ($is_external) { - # external perimeters are root level in case of contours - # and items with no children in case of holes - $role = EXTR_ROLE_EXTERNAL_PERIMETER; - } - - # detect overhanging/bridging perimeters - my @paths = (); - if ($self->config->overhangs && $self->layer_id > 0) { - # get non-overhang paths by intersecting this loop with the grown lower slices - foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => $role, - mm3_per_mm => ($is_external ? $ext_mm3_per_mm : $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([ $polygon ], $lower_slices_p) }) { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => EXTR_ROLE_OVERHANG_PERIMETER, - mm3_per_mm => $mm3_per_mm_overhang, - width => $self->overhang_flow->width, - height => $self->layer_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 => $polygon->split_at_first_point, - role => $role, - mm3_per_mm => $mm3_per_mm, - width => $self->perimeter_flow->width, - height => $self->layer_height, - ); - } - my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths); - $loop->role($loop_role); - - # return ccw contours and cw holes - # GCode.pm will convert all of them to ccw, but it needs to know - # what the holes are in order to compute the correct inwards move - # We do this on the final Loop object instead of the polygon because - # overhang clipping might have reversed its order since Clipper does - # not preserve polyline orientation. - if ($is_contour) { - $loop->make_counter_clockwise; - } else { - $loop->make_clockwise; - } - $collection->append($loop); - - # save the children - push @children, $polynode->{children}; - } - - # if we're handling the top-level contours, add thin walls as candidates too - # in order to include them in the nearest-neighbor search - if ($is_contour && $depth == 0) { - foreach my $polyline (@thin_wall_polylines) { - $collection->append(Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => $mm3_per_mm, - width => $self->perimeter_flow->width, - height => $self->layer_height, - )); - } - } - - # use a nearest neighbor search to order these children - # TODO: supply second argument to chained_path() too? - # (We used to skip this chiained_path() when $is_contour && - # $depth == 0 because slices are ordered at G_code export - # time, but multiple top-level perimeters might belong to - # the same slice actually, so that was a broken optimization.) - my $sorted_collection = $collection->chained_path_indices(0); - my @orig_indices = @{$sorted_collection->orig_indices}; - - my @loops = (); - foreach my $loop (@$sorted_collection) { - my $orig_index = shift @orig_indices; - - if ($loop->isa('Slic3r::ExtrusionPath')) { - push @loops, $loop->clone; - } else { - # if this is an external contour find all holes belonging to this contour(s) - # and prepend them - if ($is_contour && $depth == 0) { - # $loop is the outermost loop of an island - my @holes = (); - for (my $i = 0; $i <= $#$holes_pt; $i++) { - if ($loop->polygon->contains_point($holes_pt->[$i]{outer}->first_point)) { - push @holes, splice @$holes_pt, $i, 1; # remove from candidates to reduce complexity - $i--; - } - } - - # order holes efficiently - @holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}]; - - push @loops, reverse map $traverse->([$_], 0, 0), @holes; - } - - # traverse children and prepend them to this loop - push @loops, $traverse->($children[$orig_index], $depth+1, $is_contour); - push @loops, $loop->clone; - } - } - return @loops; - }; + $self->_lower_slices_p($lower_slices->polygons); # order loops from inner to outer (in terms of object slices) - my @loops = $traverse->($contours_pt, 0, 1); + my @loops = $self->_traverse_pt($contours_pt, 0, 1); # if brim will be printed, reverse the order of perimeters so that # we continue inwards after having finished the brim @@ -397,6 +250,154 @@ sub process { $self->loops->append($_) for @loops; } +sub _traverse_pt { + my ($self, $polynodes, $depth, $is_contour) = @_; + + # convert all polynodes to ExtrusionLoop objects + my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection + my @children = (); + foreach my $polynode (@$polynodes) { + my $polygon = ($polynode->{outer} // $polynode->{hole})->clone; + + my $role = EXTR_ROLE_PERIMETER; + my $loop_role = EXTRL_ROLE_DEFAULT; + + my $root_level = $depth == 0; + my $no_children = !@{ $polynode->{children} }; + my $is_external = $is_contour ? $root_level : $no_children; + my $is_internal = $is_contour ? $no_children : $root_level; + if ($is_contour && $is_internal) { + # internal perimeters are root level in case of holes + # and items with no children in case of contours + # 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; + } + if ($is_external) { + # external perimeters are root level in case of contours + # and items with no children in case of holes + $role = EXTR_ROLE_EXTERNAL_PERIMETER; + } + + # detect overhanging/bridging perimeters + my @paths = (); + if ($self->config->overhangs && $self->layer_id > 0) { + # get non-overhang paths by intersecting this loop with the grown lower slices + foreach my $polyline (@{ intersection_ppl([ $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([ $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->layer_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 => $polygon->split_at_first_point, + role => $role, + mm3_per_mm => $self->_mm3_per_mm, + width => $self->perimeter_flow->width, + height => $self->layer_height, + ); + } + my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths); + $loop->role($loop_role); + + # return ccw contours and cw holes + # GCode.pm will convert all of them to ccw, but it needs to know + # what the holes are in order to compute the correct inwards move + # We do this on the final Loop object instead of the polygon because + # overhang clipping might have reversed its order since Clipper does + # not preserve polyline orientation. + if ($is_contour) { + $loop->make_counter_clockwise; + } else { + $loop->make_clockwise; + } + $collection->append($loop); + + # save the children + push @children, $polynode->{children}; + } + + # if we're handling the top-level contours, add thin walls as candidates too + # in order to include them in the nearest-neighbor search + if ($is_contour && $depth == 0) { + foreach my $polyline (@{$self->_thin_wall_polylines}) { + $collection->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, + )); + } + } + + # use a nearest neighbor search to order these children + # TODO: supply second argument to chained_path() too? + # (We used to skip this chiained_path() when $is_contour && + # $depth == 0 because slices are ordered at G_code export + # time, but multiple top-level perimeters might belong to + # the same slice actually, so that was a broken optimization.) + my $sorted_collection = $collection->chained_path_indices(0); + my @orig_indices = @{$sorted_collection->orig_indices}; + + my @loops = (); + foreach my $loop (@$sorted_collection) { + my $orig_index = shift @orig_indices; + + if ($loop->isa('Slic3r::ExtrusionPath')) { + push @loops, $loop->clone; + } else { + # if this is an external contour find all holes belonging to this contour(s) + # and prepend them + if ($is_contour && $depth == 0) { + # $loop is the outermost loop of an island + my @holes = (); + for (my $i = 0; $i <= $#{$self->_holes_pt}; $i++) { + if ($loop->polygon->contains_point($self->_holes_pt->[$i]{outer}->first_point)) { + push @holes, splice @{$self->_holes_pt}, $i, 1; # remove from candidates to reduce complexity + $i--; + } + } + + # order holes efficiently + @holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}]; + + push @loops, reverse map $self->_traverse_pt([$_], 0, 0), @holes; + } + + # traverse children and prepend them to this loop + push @loops, $self->_traverse_pt($children[$orig_index], $depth+1, $is_contour); + push @loops, $loop->clone; + } + } + return @loops; +} + sub _fill_gaps { my ($self, $min, $max, $w, $gaps) = @_; From 06aaf83cfe81f3ffd0b1ddcfc8df0fb53d1612c1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 19:46:37 +0100 Subject: [PATCH 22/34] Restored correct transparency of the cutting plane --- lib/Slic3r/GUI/PreviewCanvas.pm | 144 ++++++++++++++++---------------- 1 file changed, 71 insertions(+), 73 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 67bc49757..355fcd606 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -729,81 +729,60 @@ sub Render { # draw ground and axes glDisable(GL_LIGHTING); - my $z0 = 0; + + # draw ground + my $ground_z = GROUND_Z; + if ($self->bed_triangles) { + glDisable(GL_DEPTH_TEST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnableClientState(GL_VERTEX_ARRAY); + glColor4f(0.8, 0.6, 0.5, 0.4); + glNormal3d(0,0,1); + glVertexPointer_p(3, $self->bed_triangles); + glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3); + glDisableClientState(GL_VERTEX_ARRAY); + + glEnable(GL_DEPTH_TEST); + + # draw grid + glLineWidth(3); + glColor4f(0.2, 0.2, 0.2, 0.4); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer_p(3, $self->bed_grid_lines); + glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3); + glDisableClientState(GL_VERTEX_ARRAY); + + glDisable(GL_BLEND); + } + + my $volumes_bb = $self->volumes_bounding_box; { - # draw ground - my $ground_z = GROUND_Z; - if ($self->bed_triangles) { - glDisable(GL_DEPTH_TEST); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glEnableClientState(GL_VERTEX_ARRAY); - glColor4f(0.8, 0.6, 0.5, 0.4); - glNormal3d(0,0,1); - glVertexPointer_p(3, $self->bed_triangles); - glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3); - glDisableClientState(GL_VERTEX_ARRAY); - - glEnable(GL_DEPTH_TEST); - - # draw grid - glLineWidth(3); - glColor4f(0.2, 0.2, 0.2, 0.4); - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer_p(3, $self->bed_grid_lines); - glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3); - glDisableClientState(GL_VERTEX_ARRAY); - - glDisable(GL_BLEND); - } - - my $volumes_bb = $self->volumes_bounding_box; - - { - # draw axes - $ground_z += 0.02; - my $origin = $self->origin; - my $axis_len = max( - 0.3 * max(@{ $self->bed_bounding_box->size }), - 2 * max(@{ $volumes_bb->size }), - ); - glLineWidth(2); - glBegin(GL_LINES); - # draw line for x axis - glColor3f(1, 0, 0); - glVertex3f(@$origin, $ground_z); - glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,, - # draw line for y axis - glColor3f(0, 1, 0); - glVertex3f(@$origin, $ground_z); - glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++ - # draw line for Z axis - glColor3f(0, 0, 1); - glVertex3f(@$origin, $ground_z); - glVertex3f(@$origin, $ground_z+$axis_len); - glEnd(); - } - - # draw cutting plane - if (defined $self->cutting_plane_z) { - my $plane_z = $z0 + $self->cutting_plane_z; - my $bb = $volumes_bb; - glDisable(GL_CULL_FACE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBegin(GL_QUADS); - glColor4f(0.8, 0.8, 0.8, 0.5); - glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z); - glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z); - glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z); - glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z); - glEnd(); - glEnable(GL_CULL_FACE); - glDisable(GL_BLEND); - } + # draw axes + $ground_z += 0.02; + my $origin = $self->origin; + my $axis_len = max( + 0.3 * max(@{ $self->bed_bounding_box->size }), + 2 * max(@{ $volumes_bb->size }), + ); + glLineWidth(2); + glBegin(GL_LINES); + # draw line for x axis + glColor3f(1, 0, 0); + glVertex3f(@$origin, $ground_z); + glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,, + # draw line for y axis + glColor3f(0, 1, 0); + glVertex3f(@$origin, $ground_z); + glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++ + # draw line for Z axis + glColor3f(0, 0, 1); + glVertex3f(@$origin, $ground_z); + glVertex3f(@$origin, $ground_z+$axis_len); + glEnd(); } glEnable(GL_LIGHTING); @@ -811,6 +790,25 @@ sub Render { # draw objects $self->draw_volumes; + # draw cutting plane + if (defined $self->cutting_plane_z) { + my $plane_z = $self->cutting_plane_z; + my $bb = $volumes_bb; + glDisable(GL_CULL_FACE); + glDisable(GL_LIGHTING); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_QUADS); + glColor4f(0.8, 0.8, 0.8, 0.5); + glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z); + glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z); + glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z); + glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z); + glEnd(); + glEnable(GL_CULL_FACE); + glDisable(GL_BLEND); + } + glFlush(); $self->SwapBuffers(); From a4235f5f44749da1d53d21cdfffd3fca0816a407 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 19:48:25 +0100 Subject: [PATCH 23/34] Better axes rendering --- lib/Slic3r/GUI/PreviewCanvas.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 355fcd606..6a4568d05 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -745,6 +745,8 @@ sub Render { glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3); glDisableClientState(GL_VERTEX_ARRAY); + # we need depth test for grid, otherwise it would disappear when looking + # the object from below glEnable(GL_DEPTH_TEST); # draw grid @@ -762,7 +764,8 @@ sub Render { { # draw axes - $ground_z += 0.02; + #$ground_z += 0.02; + glDisable(GL_DEPTH_TEST); my $origin = $self->origin; my $axis_len = max( 0.3 * max(@{ $self->bed_bounding_box->size }), @@ -783,6 +786,7 @@ sub Render { glVertex3f(@$origin, $ground_z); glVertex3f(@$origin, $ground_z+$axis_len); glEnd(); + glEnable(GL_DEPTH_TEST); } glEnable(GL_LIGHTING); From d5cab6221d05c37dd9691ca5001dee7dd09af1c1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 20:11:03 +0100 Subject: [PATCH 24/34] Better lighting in the 3D view --- lib/Slic3r/GUI/PreviewCanvas.pm | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 6a4568d05..325d39bc1 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -44,7 +44,7 @@ use constant TRACKBALLSIZE => 0.8; use constant TURNTABLE_MODE => 1; use constant GROUND_Z => -0.02; use constant SELECTED_COLOR => [0,1,0,1]; -use constant HOVER_COLOR => [0.8,0.8,0,1]; +use constant HOVER_COLOR => [0.4,0.9,0,1]; use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ]; # make OpenGL::Array thread-safe @@ -629,17 +629,16 @@ sub InitGL { glEnable(GL_MULTISAMPLE); # ambient lighting - glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1); + glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); - glLightfv_p(GL_LIGHT0, GL_POSITION, 0.5, 0.5, 1, 0); - glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.5, 0.5, 0.5, 1); - glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.8, 0.8, 0.8, 1); - glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 0.5, 0); - glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.5, 0.5, 0.5, 1); - glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 1, 1, 1, 1); + + # light from camera + glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); + glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1); + glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1); # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. glShadeModel(GL_SMOOTH); @@ -681,6 +680,11 @@ sub Render { } glTranslatef(@{ $self->_camera_target->negative }); + # light from above + glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0); + glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1); + glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1); + if ($self->enable_picking) { glDisable(GL_LIGHTING); $self->draw_volumes(1); @@ -706,6 +710,7 @@ sub Render { # draw fixed background if ($self->background) { + glDisable(GL_LIGHTING); glPushMatrix(); glLoadIdentity(); @@ -725,6 +730,7 @@ sub Render { glMatrixMode(GL_MODELVIEW); glPopMatrix(); + glEnable(GL_LIGHTING); } # draw ground and axes From 9b9ed91e6e013d4be7b8ddee279ef7318e9048b3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 20:45:50 +0100 Subject: [PATCH 25/34] Nicer rendering for 3D slices --- lib/Slic3r/GUI/PreviewCanvas.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 325d39bc1..c0188edde 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -874,9 +874,13 @@ sub draw_volumes { glLineWidth(0); glColor3f(@{COLORS->[0]}); glBegin(GL_QUADS); - glNormal3f((map $_/$line->length, @{$line->normal}), 0); + # We'll use this for the middle normal when using 4 quads: + #my $xy_normal = $line->normal; + #$_xynormal->scale(1/$line->length); + glNormal3f(0,0,-1); glVertex3f((map unscale($_), @{$line->a}), $bottom_z); glVertex3f((map unscale($_), @{$line->b}), $bottom_z); + glNormal3f(0,0,1); glVertex3f((map unscale($_), @{$line->b}), $top_z); glVertex3f((map unscale($_), @{$line->a}), $top_z); glEnd(); From af92e3d49efc11525e7f9e814b5f64bf346bfca8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 7 Jan 2015 21:57:22 +0100 Subject: [PATCH 26/34] Bugfix: validation for sequential printing was not entirely correct. #2480 --- xs/src/libslic3r/Print.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index b87d1c8cf..47faf2a13 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -561,25 +561,26 @@ Print::validate() const FOREACH_OBJECT(this, i_object) { PrintObject* object = *i_object; - // get convex hulls of all meshes assigned to this print object - Polygons mesh_convex_hulls; - for (size_t i = 0; i < this->regions.size(); ++i) { - for (std::vector::const_iterator it = object->region_volumes[i].begin(); it != object->region_volumes[i].end(); ++it) { - Polygon hull; - object->model_object()->volumes[*it]->mesh.convex_hull(&hull); - mesh_convex_hulls.push_back(hull); - } - } - - // make a single convex hull for all of them + /* get convex hull of all meshes assigned to this print object + (this is the same as model_object()->raw_mesh.convex_hull() + but probably more efficient */ Polygon convex_hull; - Slic3r::Geometry::convex_hull(mesh_convex_hulls, &convex_hull); + { + Polygons mesh_convex_hulls; + for (size_t i = 0; i < this->regions.size(); ++i) { + for (std::vector::const_iterator it = object->region_volumes[i].begin(); it != object->region_volumes[i].end(); ++it) { + Polygon hull; + object->model_object()->volumes[*it]->mesh.convex_hull(&hull); + mesh_convex_hulls.push_back(hull); + } + } + + // make a single convex hull for all of them + Slic3r::Geometry::convex_hull(mesh_convex_hulls, &convex_hull); + } // apply the same transformations we apply to the actual meshes when slicing them object->model_object()->instances.front()->transform_polygon(&convex_hull); - - // align object to Z = 0 and apply XY shift - convex_hull.translate(object->_copies_shift); // grow convex hull with the clearance margin { From 406d045ced55e5dd40071ce9ebe0cff5ba6a51a4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 8 Jan 2015 15:19:56 +0100 Subject: [PATCH 27/34] The inwards move after an external loop was still randomly generated outwards in some cases. Perimeters are now generated with a distinct iterator for each slice. Nested islands are also correctly supported too. Various regression tests included. #2253 --- lib/Slic3r/GCode.pm | 7 +- lib/Slic3r/Layer/PerimeterGenerator.pm | 132 ++++++++++-------- lib/Slic3r/Print/GCode.pm | 8 +- lib/Slic3r/Print/SupportMaterial.pm | 2 +- t/perimeters.t | 91 +++++++++++- xs/src/libslic3r/ExtrusionEntity.hpp | 6 + .../libslic3r/ExtrusionEntityCollection.cpp | 7 +- 7 files changed, 183 insertions(+), 70 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 0249b5be3..c3bc2dfb4 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -207,8 +207,11 @@ sub extrude_loop { 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 = $was_clockwise ? (-2, 1) : (1, -2); - my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3; + 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 diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index 2feffb7df..90893d535 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -8,7 +8,7 @@ use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset of offset_ex offset2_ex union_pt intersection_ppl diff_ppl); use Slic3r::Surface ':types'; -has 'slices' => (is => 'ro', required => 1); +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 }); @@ -16,9 +16,9 @@ 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::Region->new }); +has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new }); has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); -has '_lower_slices_p' => (is => 'rw'); +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'); @@ -34,6 +34,19 @@ has 'gap_fill' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Coll # 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) = @_; @@ -62,13 +75,25 @@ sub process { my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - my @contours = (); # array of Polygons with ccw orientation - my @holes = (); # array of Polygons with cw orientation - my @thin_walls = (); # array of ExPolygons + # 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}) { + my @contours = (); # array of Polygons with ccw orientation + my @holes = (); # array of Polygons with cw orientation + my @thin_walls = (); # array of ExPolygons + # detect how many perimeters must be generated for this island my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0); @@ -194,60 +219,48 @@ sub process { -($pspacing/2 + $min_perimeter_infill_spacing/2), +$min_perimeter_infill_spacing/2, )}; - } - # process thin walls by collapsing slices to single passes - if (@thin_walls) { - # 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 = $pwidth / 4; - @thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)}; + # process thin walls by collapsing slices to single passes + if (@thin_walls) { + # 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 = $pwidth / 4; + @thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)}; - # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - $self->_thin_wall_polylines([ map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls ]); - Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->_thin_wall_polylines}) if $Slic3r::debug; + # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop + $self->_thin_wall_polylines([ map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls ]); + Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->_thin_wall_polylines}) if $Slic3r::debug; - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "medial_axis.svg", - no_arrows => 1, - expolygons => \@thin_walls, - green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ], - red_polylines => $self->_thin_wall_polylines, - ); + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "medial_axis.svg", + no_arrows => 1, + expolygons => \@thin_walls, + green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ], + red_polylines => $self->_thin_wall_polylines, + ); + } } + + # find nesting hierarchies separately for contours and holes + my $contours_pt = union_pt(\@contours); + $self->_holes_pt(union_pt(\@holes)); + + # order loops from inner to outer (in terms of object slices) + my @loops = $self->_traverse_pt($contours_pt, 0, 1); + + # 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 + @loops = reverse @loops + 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(@loops)); } - - # find nesting hierarchies separately for contours and holes - my $contours_pt = union_pt(\@contours); - $self->_holes_pt(union_pt(\@holes)); - - # prepare grown lower layer slices for overhang detection - my $lower_slices = Slic3r::ExPolygon::Collection->new; - 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); - $lower_slices->append($_) - for @{offset_ex([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2)}; - } - $self->_lower_slices_p($lower_slices->polygons); - - # order loops from inner to outer (in terms of object slices) - my @loops = $self->_traverse_pt($contours_pt, 0, 1); - - # 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 - @loops = reverse @loops - if $self->config->external_perimeters_first - || ($self->layer_id == 0 && $self->print_config->brim_width > 0); - - # append perimeters - $self->loops->append($_) for @loops; } sub _traverse_pt { @@ -328,9 +341,8 @@ sub _traverse_pt { # return ccw contours and cw holes # GCode.pm will convert all of them to ccw, but it needs to know # what the holes are in order to compute the correct inwards move - # We do this on the final Loop object instead of the polygon because - # overhang clipping might have reversed its order since Clipper does - # not preserve polyline orientation. + # We do this on the final Loop object because overhang clipping + # does not keep orientation. if ($is_contour) { $loop->make_counter_clockwise; } else { @@ -358,10 +370,13 @@ sub _traverse_pt { # use a nearest neighbor search to order these children # TODO: supply second argument to chained_path() too? - # (We used to skip this chiained_path() when $is_contour && + # (We used to skip this chained_path() when $is_contour && # $depth == 0 because slices are ordered at G_code export # time, but multiple top-level perimeters might belong to # the same slice actually, so that was a broken optimization.) + # We supply no_reverse = false because we want to permit reversal + # of thin walls, but we rely on the fact that loops will never + # be reversed anyway. my $sorted_collection = $collection->chained_path_indices(0); my @orig_indices = @{$sorted_collection->orig_indices}; @@ -407,7 +422,6 @@ sub _fill_gaps { 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 diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index da4d51416..fa9c919ef 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -424,17 +424,17 @@ sub process_layer { # process perimeters { my $extruder_id = $region->config->perimeter_extruder-1; - foreach my $perimeter (@{$layerm->perimeters}) { + foreach my $perimeter_coll (@{$layerm->perimeters}) { # init by_extruder item only if we actually use the extruder $by_extruder{$extruder_id} //= []; - # $perimeter is an ExtrusionLoop or ExtrusionPath object + # $perimeter_coll is an ExtrusionPath::Collection object representing a single slice for my $i (0 .. $#{$layer->slices}) { if ($i == $#{$layer->slices} - || $layer->slices->[$i]->contour->contains_point($perimeter->first_point)) { + || $layer->slices->[$i]->contour->contains_point($perimeter_coll->first_point)) { $by_extruder{$extruder_id}[$i] //= { perimeters => {} }; $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= []; - push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, $perimeter; + push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, @$perimeter_coll; last; } } diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 7e23a75c7..e431f3fb5 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -178,7 +178,7 @@ sub contact_area { # TODO: split_at_first_point() could split a bridge mid-way my @overhang_perimeters = map { $_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline->clone } - @{$layerm->perimeters}; + map @$_, @{$layerm->perimeters}; # workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() $_->[0]->translate(1,0) for @overhang_perimeters; diff --git a/t/perimeters.t b/t/perimeters.t index 49217fd82..e2a5bc344 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -1,4 +1,4 @@ -use Test::More tests => 11; +use Test::More tests => 29; use strict; use warnings; @@ -7,6 +7,8 @@ BEGIN { use lib "$FindBin::Bin/../lib"; } +use Slic3r::ExtrusionLoop ':roles'; +use Slic3r::ExtrusionPath ':roles'; use List::Util qw(first); use Slic3r; use Slic3r::Flow ':roles'; @@ -188,7 +190,7 @@ use Slic3r::Test; my $pflow = $layerm->flow(FLOW_ROLE_PERIMETER); my $iflow = $layerm->flow(FLOW_ROLE_INFILL); my $covered_by_perimeters = union_ex([ - (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, @{$layerm->perimeters}), + (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, map @$_, @{$layerm->perimeters}), ]); my $covered_by_infill = union_ex([ (map $_->p, @{$layerm->fill_surfaces}), @@ -285,4 +287,89 @@ 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'; + is scalar(map @$_, @{$g->loops}), + $expected{total}, 'expected number of loops'; + is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, map @$_, @{$g->loops}), + $expected{external}, 'expected number of external loops'; + is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, map @$_, @{$g->loops}), + $expected{cinternal}, 'expected number of internal contour loops'; + is scalar(grep $_->polygon->is_counter_clockwise, map @$_, @{$g->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/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 65a1aba59..c40291190 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -35,6 +35,9 @@ enum ExtrusionLoopRole { class ExtrusionEntity { public: + virtual bool is_loop() const { + return false; + }; virtual ExtrusionEntity* clone() const = 0; virtual ~ExtrusionEntity() {}; virtual void reverse() = 0; @@ -84,6 +87,9 @@ class ExtrusionLoop : public ExtrusionEntity ExtrusionLoopRole role; ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {}; + bool is_loop() const { + return true; + }; operator Polygon() const; ExtrusionLoop* clone() const; bool make_clockwise(); diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index a958e53cf..4e3c596cf 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -37,7 +37,9 @@ void ExtrusionEntityCollection::reverse() { for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) { - (*it)->reverse(); + // Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering + // and caller might rely on winding order + if (!(*it)->is_loop()) (*it)->reverse(); } std::reverse(this->entities.begin(), this->entities.end()); } @@ -96,7 +98,8 @@ ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCo int start_index = start_near.nearest_point_index(endpoints); int path_index = start_index/2; ExtrusionEntity* entity = my_paths.at(path_index); - if (start_index % 2 && !no_reverse) { + // never reverse loops, since it's pointless for chained path and callers might depend on orientation + if (start_index % 2 && !no_reverse && !entity->is_loop()) { entity->reverse(); } retval->entities.push_back(my_paths.at(path_index)); From c908d4d96ef02aa92cbc2a7ee6bd9732fb7ac0f9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 8 Jan 2015 15:31:13 +0100 Subject: [PATCH 28/34] Restore correct depth test for the Z axis. #2510 --- lib/Slic3r/GUI/PreviewCanvas.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index c0188edde..6872af989 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -770,7 +770,7 @@ sub Render { { # draw axes - #$ground_z += 0.02; + # disable depth testing so that axes are not covered by ground glDisable(GL_DEPTH_TEST); my $origin = $self->origin; my $axis_len = max( @@ -787,12 +787,15 @@ sub Render { glColor3f(0, 1, 0); glVertex3f(@$origin, $ground_z); glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++ + glEnd(); # draw line for Z axis + # (re-enable depth test so that axis is correctly shown when objects are behind it) + glEnable(GL_DEPTH_TEST); + glBegin(GL_LINES); glColor3f(0, 0, 1); glVertex3f(@$origin, $ground_z); glVertex3f(@$origin, $ground_z+$axis_len); glEnd(); - glEnable(GL_DEPTH_TEST); } glEnable(GL_LIGHTING); From fc47892474e3e2bdb76008332f731f4b14bdbf2b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 8 Jan 2015 15:31:59 +0100 Subject: [PATCH 29/34] 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 4a02ac2cd..f700fd35a 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -6,7 +6,7 @@ #include #include -#define SLIC3R_VERSION "1.2.4" +#define SLIC3R_VERSION "1.2.5-dev" #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 From 8b11adb883e804f49066ff7f53b04a27b70acdee Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 8 Jan 2015 15:34:42 +0100 Subject: [PATCH 30/34] Enlarge the About dialog. #2476 --- lib/Slic3r/GUI/AboutDialog.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm index 3f946bb4c..b64bbea40 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, 270]); + my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300]); $self->SetBackgroundColour(Wx::wxWHITE); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -47,7 +47,7 @@ sub new { '' . '' . '' . - 'Copyright © 2011-2014 Alessandro Ranellucci.
' . + 'Copyright © 2011-2015 Alessandro Ranellucci.
' . 'Slic3r is licensed under the ' . 'GNU Affero General Public License, version 3.' . '


' . From 2f255620c62022fcce12a1aa697629819d150a5e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 8 Jan 2015 15:41:17 +0100 Subject: [PATCH 31/34] Fix toolpath preview after recent change of semantics of LayerRegion::perimeters --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index a0721acd0..3e6f09b27 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -301,7 +301,7 @@ sub Render { foreach my $layerm (@{$layer->regions}) { if ($object->step_done(STEP_PERIMETERS)) { $self->color([0.7, 0, 0]); - $self->_draw($object, $print_z, $_) for @{$layerm->perimeters}; + $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters}; } if ($object->step_done(STEP_INFILL)) { From 79cb350f2d8ef8430e5c8b4ad42b8a502f86f478 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 8 Jan 2015 21:24:51 +0100 Subject: [PATCH 32/34] Fixed segfault in new MotionPlanner code when environments were empty (small islands). #2511 --- xs/src/libslic3r/MotionPlanner.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/xs/src/libslic3r/MotionPlanner.cpp b/xs/src/libslic3r/MotionPlanner.cpp index 06b5f5e21..47e058497 100644 --- a/xs/src/libslic3r/MotionPlanner.cpp +++ b/xs/src/libslic3r/MotionPlanner.cpp @@ -111,12 +111,21 @@ MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyl } } + // get environment + ExPolygonCollection env = this->get_env(island_idx); + if (env.expolygons.empty()) { + // if this environment is empty (probably because it's too small), perform straight move + // and avoid running the algorithms on empty dataset + polyline->points.push_back(from); + polyline->points.push_back(to); + return; // bye bye + } + // Now check whether points are inside the environment. Point inner_from = from; Point inner_to = to; bool from_is_inside, to_is_inside; - ExPolygonCollection env = this->get_env(island_idx); if (!(from_is_inside = env.contains(from))) { // Find the closest inner point to start from. inner_from = this->nearest_env_point(env, from, to); @@ -210,8 +219,12 @@ MotionPlanner::nearest_env_point(const ExPolygonCollection &env, const Point &fr } } - // if we're here, return last point (better than nothing) - return pp.front(); + // if we're here, return last point if any (better than nothing) + if (!pp.empty()) return pp.front(); + + // if we have no points at all, then we have an empty environment and we + // make this method behave as a no-op (we shouldn't get here by the way) + return from; } MotionPlannerGraph* From 24daa50bfd7e8402524900b8885e41697e670941 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 8 Jan 2015 21:34:51 +0100 Subject: [PATCH 33/34] Button for testing OctoPrint connectivity. #2509 --- lib/Slic3r/GUI/Tab.pm | 50 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 7a21b3fc0..b1930b40c 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -998,8 +998,8 @@ sub build { { my $optgroup = $page->new_optgroup('OctoPrint upload'); - # append a button to the Host line - my $octoprint_host_widget = sub { + # append two buttons to the Host line + my $octoprint_host_browse = sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "Browse…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); @@ -1011,10 +1011,7 @@ sub build { if (!eval "use Net::Bonjour; 1") { $btn->Disable; } - - my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $sizer->Add($btn); - + EVT_BUTTON($self, $btn, sub { my $dlg = Slic3r::GUI::BonjourBrowser->new($self); if ($dlg->ShowModal == wxID_OK) { @@ -1026,11 +1023,43 @@ sub build { } }); - return $sizer; + return $btn; + }; + my $octoprint_host_test = sub { + my ($parent) = @_; + + my $btn = $self->{octoprint_host_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + $btn->SetFont($Slic3r::GUI::small_font); + if ($Slic3r::GUI::have_button_icons) { + $btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/wrench.png", wxBITMAP_TYPE_PNG)); + } + + if (!eval "use LWP::UserAgent; 1") { + $btn->Disable; + } + + EVT_BUTTON($self, $btn, sub { + my $ua = LWP::UserAgent->new; + $ua->timeout(10); + + my $res = $ua->post( + "http://" . $self->{config}->octoprint_host . "/api/version", + 'X-Api-Key' => $self->{config}->octoprint_apikey, + ); + if ($res->is_success) { + Slic3r::GUI::show_info($self, "Connection to OctoPrint works correctly.", "Success!"); + } else { + Slic3r::GUI::show_error($self, + "I wasn't able to connect to OctoPrint (" . $res->status_line . "). " + . "Check hostname and OctoPrint version (at least 1.1.0 is required)."); + } + }); + return $btn; }; my $host_line = $optgroup->create_single_option_line('octoprint_host'); - $host_line->append_widget($octoprint_host_widget); + $host_line->append_widget($octoprint_host_browse); + $host_line->append_widget($octoprint_host_test); $optgroup->append_line($host_line); $optgroup->append_single_option_line('octoprint_apikey'); } @@ -1173,6 +1202,11 @@ sub _update { my $config = $self->{config}; + if ($config->get('octoprint_host')) { + $self->{octoprint_host_test_btn}->Enable; + } else { + $self->{octoprint_host_test_btn}->Disable; + } $self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host')); my $have_multiple_extruders = $self->{extruders_count} > 1; From 9f4f7110178596877b4ae692a8c3ea4142255e03 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 8 Jan 2015 21:37:00 +0100 Subject: [PATCH 34/34] Disable the OctoPrint test button when LWP::UserAgent is not available --- lib/Slic3r/GUI/Tab.pm | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index b1930b40c..e979540d3 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -1034,10 +1034,6 @@ sub build { $btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/wrench.png", wxBITMAP_TYPE_PNG)); } - if (!eval "use LWP::UserAgent; 1") { - $btn->Disable; - } - EVT_BUTTON($self, $btn, sub { my $ua = LWP::UserAgent->new; $ua->timeout(10); @@ -1202,7 +1198,7 @@ sub _update { my $config = $self->{config}; - if ($config->get('octoprint_host')) { + if ($config->get('octoprint_host') && eval "use LWP::UserAgent; 1") { $self->{octoprint_host_test_btn}->Enable; } else { $self->{octoprint_host_test_btn}->Disable;