From 47940a712d4b90fd020fcd4bc1211415182d3494 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 10 May 2014 20:54:12 +0200 Subject: [PATCH 01/48] Bugfix: previous brim and skirt were not cleared when disabled after first G-code export. #2024 --- lib/Slic3r/Print.pm | 14 ++++++++++---- lib/Slic3r/Print/Object.pm | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index cdf26c9e0..c381ce95c 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -625,11 +625,14 @@ EOF sub make_skirt { my $self = shift; + + # since this method must be idempotent, we clear skirt paths *before* + # checking whether we need to generate them + $self->skirt->clear; + return unless $self->config->skirts > 0 || ($self->config->ooze_prevention && @{$self->extruders} > 1); - $self->skirt->clear; # method must be idempotent - # First off we need to decide how tall the skirt must be. # The skirt_height option from config is expressed in layers, but our # object might have different layer heights, so we need to find the print_z @@ -737,9 +740,12 @@ sub make_skirt { sub make_brim { my $self = shift; - return unless $self->config->brim_width > 0; - $self->brim->clear; # method must be idempotent + # since this method must be idempotent, we clear brim paths *before* + # checking whether we need to generate them + $self->brim->clear; + + return unless $self->config->brim_width > 0; # brim is only printed on first layer and uses support material extruder my $first_layer_height = $self->objects->[0]->config->get_abs_value('first_layer_height'); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index a43123a88..61b3da837 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -965,6 +965,9 @@ sub combine_infill { sub generate_support_material { my $self = shift; + + # TODO: make this method idempotent by removing all support layers + # before checking whether we need to generate support or not return unless ($self->config->support_material || $self->config->raft_layers > 0) && scalar(@{$self->layers}) >= 2; From 69002b8ea25355b096d77b1f87f0ccd4ae01f996 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 12 May 2014 21:49:17 +0200 Subject: [PATCH 02/48] No tests were covering randomize-start, which was not working anymore after recent ExtrusionLoop refactoring. #2028 --- lib/Slic3r/ExtrusionLoop.pm | 7 +++++++ lib/Slic3r/ExtrusionPath.pm | 5 ++--- lib/Slic3r/Fill.pm | 4 +--- lib/Slic3r/GCode.pm | 5 ++--- lib/Slic3r/Layer/Region.pm | 10 +++++++--- t/perimeters.t | 9 ++++++++- xs/src/ExtrusionEntity.cpp | 12 +++++------- xs/src/ExtrusionEntity.hpp | 24 ++++++++++++++++-------- xs/xsp/ExtrusionLoop.xsp | 26 ++++++++++++++++++++++++++ xs/xsp/ExtrusionPath.xsp | 15 ++++++--------- xs/xsp/my.map | 1 + xs/xsp/typemap.xspt | 6 ++++++ 12 files changed, 87 insertions(+), 37 deletions(-) diff --git a/lib/Slic3r/ExtrusionLoop.pm b/lib/Slic3r/ExtrusionLoop.pm index 710beaa41..2c9cffa19 100644 --- a/lib/Slic3r/ExtrusionLoop.pm +++ b/lib/Slic3r/ExtrusionLoop.pm @@ -2,4 +2,11 @@ package Slic3r::ExtrusionLoop; use strict; use warnings; +use parent qw(Exporter); + +our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT EXTRL_ROLE_EXTERNAL_PERIMETER + EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER); +our %EXPORT_TAGS = (roles => \@EXPORT_OK); + + 1; diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index a03ff487e..20c7930ed 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -4,10 +4,9 @@ use warnings; use parent qw(Exporter); -our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER - EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER +our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_BRIDGE - EXTR_ROLE_INTERNALBRIDGE EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_GAPFILL); + EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_GAPFILL); our %EXPORT_TAGS = (roles => \@EXPORT_OK); 1; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 678e71f9a..6d1fae502 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -230,9 +230,7 @@ sub make_fill { $collection->append( map Slic3r::ExtrusionPath->new( polyline => $_, - role => ($surface->surface_type == S_TYPE_INTERNALBRIDGE - ? EXTR_ROLE_INTERNALBRIDGE - : $is_bridge + role => ($is_bridge ? EXTR_ROLE_BRIDGE : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 907a0048a..6e9ed68b0 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -2,6 +2,7 @@ package Slic3r::GCode; use Moo; use List::Util qw(min max first); +use Slic3r::ExtrusionLoop ':roles'; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI X Y B); @@ -76,12 +77,10 @@ my %role_speeds = ( &EXTR_ROLE_PERIMETER => 'perimeter', &EXTR_ROLE_EXTERNAL_PERIMETER => 'external_perimeter', &EXTR_ROLE_OVERHANG_PERIMETER => 'bridge', - &EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 'perimeter', &EXTR_ROLE_FILL => 'infill', &EXTR_ROLE_SOLIDFILL => 'solid_infill', &EXTR_ROLE_TOPSOLIDFILL => 'top_solid_infill', &EXTR_ROLE_BRIDGE => 'bridge', - &EXTR_ROLE_INTERNALBRIDGE => 'bridge', &EXTR_ROLE_SKIRT => 'perimeter', &EXTR_ROLE_GAPFILL => 'gap_fill', ); @@ -217,7 +216,7 @@ sub extrude_loop { # find the point of the loop that is closest to the current extruder position # or randomize if requested my $last_pos = $self->last_pos; - if ($self->print_config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) { + if ($self->print_config->randomize_start && $loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { $last_pos = Slic3r::Point->new(scale $self->print_config->print_center->[X], scale $self->print_config->bed_size->[Y]); $last_pos->rotate(rand(2*PI), $self->print_config->print_center); } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 184f7bab6..8f77ec2d0 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -2,6 +2,7 @@ package Slic3r::Layer::Region; use Moo; 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 points_coincide); @@ -257,13 +258,15 @@ sub make_perimeters { foreach my $polynode (@$polynodes) { my $polygon = ($polynode->{outer} // $polynode->{hole})->clone; - my $role = EXTR_ROLE_PERIMETER; + my $role = EXTR_ROLE_PERIMETER; + my $loop_role = EXTRL_ROLE_DEFAULT; if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) { # external perimeters are root level in case of contours # and items with no children in case of holes - $role = EXTR_ROLE_EXTERNAL_PERIMETER; + $role = EXTR_ROLE_EXTERNAL_PERIMETER; + $loop_role = EXTRL_ROLE_EXTERNAL_PERIMETER; } elsif ($depth == 1 && $is_contour) { - $role = EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER; + $loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER; } # detect overhanging/bridging perimeters @@ -309,6 +312,7 @@ sub make_perimeters { ); } 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 diff --git a/t/perimeters.t b/t/perimeters.t index b12b8f938..58ebc3ed1 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -1,4 +1,4 @@ -use Test::More tests => 9; +use Test::More tests => 10; use strict; use warnings; @@ -265,4 +265,11 @@ use Slic3r::Test; 'overhangs printed with bridge speed'; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('randomize_start', 1); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + ok Slic3r::Test::gcode($print), 'successful generation of G-code with randomize_start option'; +} + __END__ diff --git a/xs/src/ExtrusionEntity.cpp b/xs/src/ExtrusionEntity.cpp index 5b15ee02e..0abab7510 100644 --- a/xs/src/ExtrusionEntity.cpp +++ b/xs/src/ExtrusionEntity.cpp @@ -72,23 +72,21 @@ ExtrusionPath::is_perimeter() const { return this->role == erPerimeter || this->role == erExternalPerimeter - || this->role == erOverhangPerimeter - || this->role == erContourInternalPerimeter; + || this->role == erOverhangPerimeter; } bool ExtrusionPath::is_fill() const { - return this->role == erFill - || this->role == erSolidFill - || this->role == erTopSolidFill; + return this->role == erInternalInfill + || this->role == erSolidInfill + || this->role == erTopSolidInfill; } bool ExtrusionPath::is_bridge() const { - return this->role == erBridge - || this->role == erInternalBridge + return this->role == erBridgeInfill || this->role == erOverhangPerimeter; } diff --git a/xs/src/ExtrusionEntity.hpp b/xs/src/ExtrusionEntity.hpp index 8504aa0ed..40e939d0e 100644 --- a/xs/src/ExtrusionEntity.hpp +++ b/xs/src/ExtrusionEntity.hpp @@ -11,19 +11,25 @@ class ExPolygonCollection; class ExtrusionEntityCollection; class Extruder; +/* Each ExtrusionRole value identifies a distinct set of { extruder, speed } */ enum ExtrusionRole { erPerimeter, erExternalPerimeter, erOverhangPerimeter, - erContourInternalPerimeter, - erFill, - erSolidFill, - erTopSolidFill, - erBridge, - erInternalBridge, + erInternalInfill, + erSolidInfill, + erTopSolidInfill, + erBridgeInfill, + erGapFill, erSkirt, erSupportMaterial, - erGapFill, +}; + +/* Special flags describing loop */ +enum ExtrusionLoopRole { + elrDefault, + elrExternalPerimeter, + elrContourInternalPerimeter, }; class ExtrusionEntity @@ -47,7 +53,7 @@ class ExtrusionPath : public ExtrusionEntity float width; float height; - ExtrusionPath() : mm3_per_mm(-1), width(-1), height(-1) {}; + ExtrusionPath(ExtrusionRole role) : role(role), mm3_per_mm(-1), width(-1), height(-1) {}; ExtrusionPath* clone() const; void reverse(); Point first_point() const; @@ -74,7 +80,9 @@ class ExtrusionLoop : public ExtrusionEntity { public: ExtrusionPaths paths; + ExtrusionLoopRole role; + ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {}; operator Polygon() const; ExtrusionLoop* clone() const; bool make_clockwise(); diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp index 731c7ef59..3ab469bc5 100644 --- a/xs/xsp/ExtrusionLoop.xsp +++ b/xs/xsp/ExtrusionLoop.xsp @@ -41,5 +41,31 @@ ExtrusionLoop::arrayref() OUTPUT: RETVAL +ExtrusionLoopRole +ExtrusionLoop::role(...) + CODE: + if (items > 1) { + THIS->role = (ExtrusionLoopRole)SvUV(ST(1)); + } + RETVAL = THIS->role; + OUTPUT: + RETVAL + %} }; + +%package{Slic3r::ExtrusionLoop}; +%{ + +IV +_constant() + ALIAS: + EXTRL_ROLE_DEFAULT = elrDefault + EXTRL_ROLE_EXTERNAL_PERIMETER = elrExternalPerimeter + EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER = erInternalInfill + PROTOTYPE: + CODE: + RETVAL = ix; + OUTPUT: RETVAL + +%} diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp index 1b2782258..c465cc041 100644 --- a/xs/xsp/ExtrusionPath.xsp +++ b/xs/xsp/ExtrusionPath.xsp @@ -39,9 +39,8 @@ _new(CLASS, polyline_sv, role, mm3_per_mm, width, height) float width; float height; CODE: - RETVAL = new ExtrusionPath (); + RETVAL = new ExtrusionPath (role); RETVAL->polyline.from_SV_check(polyline_sv); - RETVAL->role = role; RETVAL->mm3_per_mm = mm3_per_mm; RETVAL->width = width; RETVAL->height = height; @@ -135,15 +134,13 @@ _constant() EXTR_ROLE_PERIMETER = erPerimeter EXTR_ROLE_EXTERNAL_PERIMETER = erExternalPerimeter EXTR_ROLE_OVERHANG_PERIMETER = erOverhangPerimeter - EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER = erContourInternalPerimeter - EXTR_ROLE_FILL = erFill - EXTR_ROLE_SOLIDFILL = erSolidFill - EXTR_ROLE_TOPSOLIDFILL = erTopSolidFill - EXTR_ROLE_BRIDGE = erBridge - EXTR_ROLE_INTERNALBRIDGE = erInternalBridge + EXTR_ROLE_FILL = erInternalInfill + EXTR_ROLE_SOLIDFILL = erSolidInfill + EXTR_ROLE_TOPSOLIDFILL = erTopSolidInfill + EXTR_ROLE_BRIDGE = erBridgeInfill + EXTR_ROLE_GAPFILL = erGapFill EXTR_ROLE_SKIRT = erSkirt EXTR_ROLE_SUPPORTMATERIAL = erSupportMaterial - EXTR_ROLE_GAPFILL = erGapFill PROTOTYPE: CODE: RETVAL = ix; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index b122d95d7..b0aed2e49 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -111,6 +111,7 @@ Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +ExtrusionLoopRole T_UV ExtrusionRole T_UV FlowRole T_UV PrintStep T_UV diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 945b104bf..7cf7c8458 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -112,6 +112,12 @@ $CVar = (SurfaceType)SvUV($PerlVar); %}; }; +%typemap{ExtrusionLoopRole}{parsed}{ + %cpp_type{ExtrusionLoopRole}; + %precall_code{% + $CVar = (ExtrusionLoopRole)SvUV($PerlVar); + %}; +}; %typemap{ExtrusionRole}{parsed}{ %cpp_type{ExtrusionRole}; %precall_code{% From baefefc50dab667355ee3f3903851646da7772ad Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 12 May 2014 22:42:50 +0200 Subject: [PATCH 03/48] One method call not changed after Model refactoring --- lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index b40d081e5..a2d0b2d38 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -150,7 +150,7 @@ sub Closing { my $self = shift; # save ranges into the plater object - $self->model_object->layer_height_ranges([ $self->_get_ranges ]); + $self->model_object->set_layer_height_ranges([ $self->_get_ranges ]); } sub _get_ranges { From 59f0e76da1c4d475c8ef062c55697aae80aa0d01 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 12 May 2014 22:59:49 +0200 Subject: [PATCH 04/48] Distinct extrusion role for support material interface --- lib/Slic3r/ExtrusionPath.pm | 4 ++-- lib/Slic3r/Print/SupportMaterial.pm | 4 ++-- xs/src/ExtrusionEntity.hpp | 1 + xs/xsp/ExtrusionPath.xsp | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index 20c7930ed..0c324ab09 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -5,8 +5,8 @@ use warnings; use parent qw(Exporter); our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER - EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_BRIDGE - EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_GAPFILL); + EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE + EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE); our %EXPORT_TAGS = (roles => \@EXPORT_OK); 1; diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 09973f8f3..0a5ba7222 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -608,7 +608,7 @@ sub generate_toolpaths { my $mm3_per_mm = $interface_flow->mm3_per_mm($layer->height); @loops = map Slic3r::ExtrusionPath->new( polyline => $_, - role => EXTR_ROLE_SUPPORTMATERIAL, + role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE, mm3_per_mm => $mm3_per_mm, width => $interface_flow->width, height => $layer->height, @@ -656,7 +656,7 @@ sub generate_toolpaths { push @paths, map Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$_), - role => EXTR_ROLE_SUPPORTMATERIAL, + role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE, mm3_per_mm => $mm3_per_mm, width => $params->{flow}->width, height => $layer->height, diff --git a/xs/src/ExtrusionEntity.hpp b/xs/src/ExtrusionEntity.hpp index 40e939d0e..c1cffa612 100644 --- a/xs/src/ExtrusionEntity.hpp +++ b/xs/src/ExtrusionEntity.hpp @@ -23,6 +23,7 @@ enum ExtrusionRole { erGapFill, erSkirt, erSupportMaterial, + erSupportMaterialInterface, }; /* Special flags describing loop */ diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp index c465cc041..df3813d51 100644 --- a/xs/xsp/ExtrusionPath.xsp +++ b/xs/xsp/ExtrusionPath.xsp @@ -141,6 +141,7 @@ _constant() EXTR_ROLE_GAPFILL = erGapFill EXTR_ROLE_SKIRT = erSkirt EXTR_ROLE_SUPPORTMATERIAL = erSupportMaterial + EXTR_ROLE_SUPPORTMATERIAL_INTERFACE = erSupportMaterialInterface PROTOTYPE: CODE: RETVAL = ix; From 5d12a03b82d78b0f3ebacba519b181ed91987e3e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 12 May 2014 23:02:33 +0200 Subject: [PATCH 05/48] Move Detect Bridging Perimeters to region config --- lib/Slic3r/Layer/Region.pm | 4 ++-- xs/src/PrintConfig.hpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 8f77ec2d0..60d024fbc 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -234,7 +234,7 @@ sub make_perimeters { # prepare grown lower layer slices for overhang detection my $lower_slices = Slic3r::ExPolygon::Collection->new; - if ($self->layer->lower_layer && $self->layer->print->config->overhangs) { + 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 @@ -271,7 +271,7 @@ sub make_perimeters { # detect overhanging/bridging perimeters my @paths = (); - if ($self->layer->print->config->overhangs && $lower_slices->count > 0) { + if ($self->region->config->overhangs && $lower_slices->count > 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( diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index b0082ad8c..758231930 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -1054,6 +1054,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig ConfigOptionInt infill_extruder; ConfigOptionFloatOrPercent infill_extrusion_width; ConfigOptionInt infill_every_layers; + ConfigOptionBool overhangs; ConfigOptionInt perimeter_extruder; ConfigOptionFloatOrPercent perimeter_extrusion_width; ConfigOptionInt perimeters; @@ -1075,6 +1076,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig this->infill_extrusion_width.value = 0; this->infill_extrusion_width.percent = false; this->infill_every_layers.value = 1; + this->overhangs.value = true; this->perimeter_extruder.value = 1; this->perimeter_extrusion_width.value = 0; this->perimeter_extrusion_width.percent = false; @@ -1099,6 +1101,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig if (opt_key == "infill_extruder") return &this->infill_extruder; if (opt_key == "infill_extrusion_width") return &this->infill_extrusion_width; if (opt_key == "infill_every_layers") return &this->infill_every_layers; + if (opt_key == "overhangs") return &this->overhangs; if (opt_key == "perimeter_extruder") return &this->perimeter_extruder; if (opt_key == "perimeter_extrusion_width") return &this->perimeter_extrusion_width; if (opt_key == "perimeters") return &this->perimeters; @@ -1164,7 +1167,6 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionBool only_retract_when_crossing_perimeters; ConfigOptionBool ooze_prevention; ConfigOptionString output_filename_format; - ConfigOptionBool overhangs; ConfigOptionFloat perimeter_acceleration; ConfigOptionFloat perimeter_speed; ConfigOptionStrings post_process; @@ -1257,7 +1259,6 @@ class PrintConfig : public virtual StaticPrintConfig this->only_retract_when_crossing_perimeters.value = true; this->ooze_prevention.value = false; this->output_filename_format.value = "[input_filename_base].gcode"; - this->overhangs.value = true; this->perimeter_acceleration.value = 0; this->perimeter_speed.value = 30; this->print_center.point = Pointf(100,100); @@ -1355,7 +1356,6 @@ class PrintConfig : public virtual StaticPrintConfig if (opt_key == "only_retract_when_crossing_perimeters") return &this->only_retract_when_crossing_perimeters; if (opt_key == "ooze_prevention") return &this->ooze_prevention; if (opt_key == "output_filename_format") return &this->output_filename_format; - if (opt_key == "overhangs") return &this->overhangs; if (opt_key == "perimeter_acceleration") return &this->perimeter_acceleration; if (opt_key == "perimeter_speed") return &this->perimeter_speed; if (opt_key == "post_process") return &this->post_process; From ee2c1c6127f12b3a14ac86c95bb0438f2be77a30 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 13 May 2014 08:34:21 +0200 Subject: [PATCH 06/48] Refactored the Slic3r::GCode logic for speeds --- lib/Slic3r/GCode.pm | 242 +++++++++++++++++--------------------- lib/Slic3r/GCode/Layer.pm | 7 +- lib/Slic3r/Print.pm | 4 +- xs/src/PrintConfig.hpp | 18 +-- xs/xsp/Config.xsp | 8 +- 5 files changed, 131 insertions(+), 148 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 6e9ed68b0..5920e6eac 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -9,7 +9,7 @@ use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI use Slic3r::Geometry::Clipper qw(union_ex offset_ex); use Slic3r::Surface ':types'; -has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); +has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new }); has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new }); has 'standby_points' => (is => 'rw'); has 'enable_loop_clipping' => (is => 'rw', default => sub {1}); @@ -17,19 +17,16 @@ has 'enable_wipe' => (is => 'rw', default => sub {0}); # at least one e has 'layer_count' => (is => 'ro', required => 1 ); has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter has 'layer' => (is => 'rw'); -has 'region' => (is => 'rw'); has '_layer_islands' => (is => 'rw'); has '_upper_layer_islands' => (is => 'rw'); has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); -has 'speed' => (is => 'rw'); has '_extrusion_axis' => (is => 'rw'); has '_retract_lift' => (is => 'rw'); has 'extruders' => (is => 'ro', default => sub {{}}); has 'multiple_extruders' => (is => 'rw', default => sub {0}); has 'extruder' => (is => 'rw'); -has 'speeds' => (is => 'lazy'); # mm/min has 'external_mp' => (is => 'rw'); has 'layer_mp' => (is => 'rw'); has 'new_object' => (is => 'rw', default => sub {0}); @@ -37,23 +34,21 @@ has 'straight_once' => (is => 'rw', default => sub {1}); has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'lifted' => (is => 'rw', default => sub {0} ); has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } ); -has 'last_speed' => (is => 'rw', default => sub {""}); -has 'last_f' => (is => 'rw', default => sub {""}); has 'last_fan_speed' => (is => 'rw', default => sub {0}); has 'wipe_path' => (is => 'rw'); sub BUILD { my ($self) = @_; - $self->_extrusion_axis($self->print_config->get_extrusion_axis); - $self->_retract_lift($self->print_config->retract_lift->[0]); + $self->_extrusion_axis($self->config->get_extrusion_axis); + $self->_retract_lift($self->config->retract_lift->[0]); } sub set_extruders { - my ($self, $extruder_ids) = @_; + my ($self, $extruder_ids, $print_config) = @_; foreach my $i (@$extruder_ids) { - $self->extruders->{$i} = my $e = Slic3r::Extruder->new($i, $self->print_config); + $self->extruders->{$i} = my $e = Slic3r::Extruder->new($i, $print_config); $self->enable_wipe(1) if $e->wipe; } @@ -63,28 +58,6 @@ sub set_extruders { $self->multiple_extruders(max(@$extruder_ids) > 0); } -sub _build_speeds { - my $self = shift; - return { - map { $_ => 60 * $self->print_config->get_value("${_}_speed") } - qw(travel perimeter small_perimeter external_perimeter infill - solid_infill top_solid_infill bridge gap_fill retract), - }; -} - -# assign speeds to roles -my %role_speeds = ( - &EXTR_ROLE_PERIMETER => 'perimeter', - &EXTR_ROLE_EXTERNAL_PERIMETER => 'external_perimeter', - &EXTR_ROLE_OVERHANG_PERIMETER => 'bridge', - &EXTR_ROLE_FILL => 'infill', - &EXTR_ROLE_SOLIDFILL => 'solid_infill', - &EXTR_ROLE_TOPSOLIDFILL => 'top_solid_infill', - &EXTR_ROLE_BRIDGE => 'bridge', - &EXTR_ROLE_SKIRT => 'perimeter', - &EXTR_ROLE_GAPFILL => 'gap_fill', -); - sub set_shift { my ($self, @shift) = @_; @@ -109,23 +82,23 @@ sub change_layer { # avoid computing islands and overhangs if they're not needed $self->_layer_islands($layer->islands); $self->_upper_layer_islands($layer->upper_layer ? $layer->upper_layer->islands : []); - if ($self->print_config->avoid_crossing_perimeters) { + if ($self->config->avoid_crossing_perimeters) { $self->layer_mp(Slic3r::GCode::MotionPlanner->new( islands => union_ex([ map @$_, @{$layer->slices} ], 1), )); } my $gcode = ""; - if ($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { $gcode .= sprintf "M73 P%s%s\n", int(99 * ($self->_layer_index / ($self->layer_count - 1))), - ($self->print_config->gcode_comments ? ' ; update progress' : ''); + ($self->config->gcode_comments ? ' ; update progress' : ''); } - if ($self->print_config->first_layer_acceleration) { + if ($self->config->first_layer_acceleration) { if ($layer->id == 0) { - $gcode .= $self->set_acceleration($self->print_config->first_layer_acceleration); + $gcode .= $self->set_acceleration($self->config->first_layer_acceleration); } elsif ($layer->id == 1) { - $gcode .= $self->set_acceleration($self->print_config->default_acceleration); + $gcode .= $self->set_acceleration($self->config->default_acceleration); } } @@ -139,7 +112,7 @@ sub move_z { my $gcode = ""; - $z += $self->print_config->z_offset; + $z += $self->config->z_offset; my $current_z = $self->z; my $nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef; @@ -156,8 +129,7 @@ sub move_z { $current_z = $self->z; # update current z in case retract() changed it $nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef; } - $self->speed('travel'); - $gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')')) + $gcode .= $self->G0(undef, $z, 0, $self->config->travel_speed*60, $comment || ('move to next layer (' . $self->layer->id . ')')) if !defined $current_z || abs($z - $nominal_z) > epsilon; } elsif ($z < $current_z) { # we're moving above the current nominal layer height and below the current actual one. @@ -191,11 +163,11 @@ sub extrude_loop { # start looking for concave vertices not being overhangs my $polygon = $loop->polygon; my @concave = (); - if ($self->print_config->start_perimeters_at_concave_points) { + if ($self->config->start_perimeters_at_concave_points) { @concave = $polygon->concave_points; } my @candidates = (); - if ($self->print_config->start_perimeters_at_non_overhang) { + if ($self->config->start_perimeters_at_non_overhang) { @candidates = grep !$loop->has_overhang_point($_), @concave; } if (!@candidates) { @@ -203,7 +175,7 @@ sub extrude_loop { @candidates = @concave; if (!@candidates) { # if none, look for any non-overhang vertex - if ($self->print_config->start_perimeters_at_non_overhang) { + if ($self->config->start_perimeters_at_non_overhang) { @candidates = grep !$loop->has_overhang_point($_), @$polygon; } if (!@candidates) { @@ -216,9 +188,9 @@ sub extrude_loop { # find the point of the loop that is closest to the current extruder position # or randomize if requested my $last_pos = $self->last_pos; - if ($self->print_config->randomize_start && $loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { - $last_pos = Slic3r::Point->new(scale $self->print_config->print_center->[X], scale $self->print_config->bed_size->[Y]); - $last_pos->rotate(rand(2*PI), $self->print_config->print_center); + if ($self->config->randomize_start && $loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { + $last_pos = Slic3r::Point->new(scale $self->config->print_center->[X], scale $self->config->bed_size->[Y]); + $last_pos->rotate(rand(2*PI), $self->config->print_center); } # split the loop at the starting point @@ -236,17 +208,17 @@ sub extrude_loop { return '' if !@paths; # apply the small perimeter speed - my %params = (); - if ($paths[0]->is_perimeter && abs($loop->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) { - $params{speed} = 'small_perimeter'; + my $speed = -1; + if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) { + $speed = $self->config->get_abs_value('small_perimeter_speed'); } # extrude along the path - my $gcode = join '', map $self->extrude_path($_, $description, %params), @paths; + my $gcode = join '', map $self->extrude_path($_, $description, $speed), @paths; $self->wipe_path($paths[-1]->polyline->clone) if $self->enable_wipe; # TODO: don't limit wipe to last path # make a little move inwards before leaving loop - if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->region->config->perimeters > 1) { + if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) { my $last_path_polyline = $paths[-1]->polyline; # detect angle between last and first segment # the side depends on the original winding order of the polygon (left for contours, right for holes) @@ -270,7 +242,7 @@ sub extrude_loop { } sub extrude_path { - my ($self, $path, $description, %params) = @_; + my ($self, $path, $description, $speed) = @_; $path->simplify(&Slic3r::SCALED_RESOLUTION); @@ -287,13 +259,13 @@ sub extrude_path { # adjust acceleration my $acceleration; - if (!$self->print_config->first_layer_acceleration || $self->layer->id != 0) { - if ($self->print_config->perimeter_acceleration && $path->is_perimeter) { - $acceleration = $self->print_config->perimeter_acceleration; - } elsif ($self->print_config->infill_acceleration && $path->is_fill) { - $acceleration = $self->print_config->infill_acceleration; - } elsif ($self->print_config->infill_acceleration && $path->is_bridge) { - $acceleration = $self->print_config->bridge_acceleration; + if (!$self->config->first_layer_acceleration || $self->layer->id != 0) { + if ($self->config->perimeter_acceleration && $path->is_perimeter) { + $acceleration = $self->config->perimeter_acceleration; + } elsif ($self->config->infill_acceleration && $path->is_fill) { + $acceleration = $self->config->infill_acceleration; + } elsif ($self->config->infill_acceleration && $path->is_bridge) { + $acceleration = $self->config->bridge_acceleration; } $gcode .= $self->set_acceleration($acceleration) if $acceleration; } @@ -303,10 +275,29 @@ sub extrude_path { $e = 0 if !$self->_extrusion_axis; # set speed - $self->speed( $params{speed} || $role_speeds{$path->role} || die "Unknown role: " . $path->role ); - my $F = $self->speeds->{$self->speed} // $self->speed; + my $F; + if ($path->role == EXTR_ROLE_PERIMETER || $path->role == EXTR_ROLE_SKIRT) { + $F = $self->config->get_abs_value('perimeter_speed'); + } elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) { + $F = $self->config->get_abs_value('external_perimeter_speed'); + } elsif ($path->role == EXTR_ROLE_OVERHANG_PERIMETER || $path->role == EXTR_ROLE_BRIDGE) { + $F = $self->config->get_abs_value('bridge_speed'); + } elsif ($path->role == EXTR_ROLE_FILL) { + $F = $self->config->get_abs_value('infill_speed'); + } elsif ($path->role == EXTR_ROLE_SOLIDFILL) { + $F = $self->config->get_abs_value('solid_infill_speed'); + } elsif ($path->role == EXTR_ROLE_TOPSOLIDFILL) { + $F = $self->config->get_abs_value('top_solid_infill_speed'); + } elsif ($path->role == EXTR_ROLE_GAPFILL) { + $F = $self->config->get_abs_value('gap_fill_speed'); + } else { + $F = $speed; + die "Invalid speed" if $F < 0; # $speed == -1 + } + $F *= 60; # convert mm/sec to mm/min + if ($self->layer->id == 0) { - $F = $self->print_config->get_abs_value_over('first_layer_speed', $F/60) * 60; + $F = $self->config->get_abs_value_over('first_layer_speed', $F/60) * 60; } # extrude arc or line @@ -317,7 +308,7 @@ sub extrude_path { $self->shift_x - $self->extruder->extruder_offset->x, $self->shift_y - $self->extruder->extruder_offset->y, #,, $self->_extrusion_axis, - $self->print_config->gcode_comments ? " ; $description" : ""); + $self->config->gcode_comments ? " ; $description" : ""); if ($self->enable_wipe) { $self->wipe_path($path->polyline->clone); @@ -327,14 +318,14 @@ sub extrude_path { $gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge; $self->last_pos($path->last_point); - if ($self->print_config->cooling) { + if ($self->config->cooling) { my $path_time = $path_length / $F * 60; $self->elapsed_time($self->elapsed_time + $path_time); } # reset acceleration - $gcode .= $self->set_acceleration($self->print_config->default_acceleration) - if $acceleration && $self->print_config->default_acceleration; + $gcode .= $self->set_acceleration($self->config->default_acceleration) + if $acceleration && $self->config->default_acceleration; return $gcode; } @@ -353,19 +344,17 @@ sub travel_to { # skip retraction if the travel move is contained in an island in the current layer # *and* in an island in the upper layer (so that the ooze will not be visible) if ($travel->length < scale $self->extruder->retract_before_travel - || ($self->print_config->only_retract_when_crossing_perimeters + || ($self->config->only_retract_when_crossing_perimeters && (first { $_->contains_line($travel) } @{$self->_upper_layer_islands}) && (first { $_->contains_line($travel) } @{$self->_layer_islands})) || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && (first { $_->contains_line($travel) } @{$self->layer->support_islands})) ) { $self->straight_once(0); - $self->speed('travel'); - $gcode .= $self->G0($point, undef, 0, $comment || ""); - } elsif (!$self->print_config->avoid_crossing_perimeters || $self->straight_once) { + $gcode .= $self->G0($point, undef, 0, $self->config->travel_speed*60, $comment || ""); + } elsif (!$self->config->avoid_crossing_perimeters || $self->straight_once) { $self->straight_once(0); $gcode .= $self->retract; - $self->speed('travel'); - $gcode .= $self->G0($point, undef, 0, $comment || ""); + $gcode .= $self->G0($point, undef, 0, $self->config->travel_speed*60, $comment || ""); } else { if ($self->new_object) { $self->new_object(0); @@ -394,7 +383,7 @@ sub _plan { my @travel = @{$mp->shortest_path($self->last_pos, $point)->lines}; # if the path is not contained in a single island we need to retract - my $need_retract = !$self->print_config->only_retract_when_crossing_perimeters; + my $need_retract = !$self->config->only_retract_when_crossing_perimeters; if (!$need_retract) { $need_retract = 1; foreach my $island (@{$self->_upper_layer_islands}) { @@ -410,9 +399,8 @@ sub _plan { $gcode .= $self->retract if $need_retract; # append the actual path and return - $self->speed('travel'); # use G1 because we rely on paths being straight (G0 may make round paths) - $gcode .= join '', map $self->G1($_->b, undef, 0, $comment || ""), @travel; + $gcode .= join '', map $self->G1($_->b, undef, 0, $self->config->travel_speed*60, $comment || ""), @travel; return $gcode; } @@ -434,45 +422,40 @@ sub retract { if ($self->extruder->wipe && $self->wipe_path) { my @points = @{$self->wipe_path}; $wipe_path = Slic3r::Polyline->new($self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}]); - $wipe_path->clip_end($wipe_path->length - $self->extruder->scaled_wipe_distance($self->print_config->travel_speed)); + $wipe_path->clip_end($wipe_path->length - $self->extruder->scaled_wipe_distance($self->config->travel_speed)); } # prepare moves - my $retract = [undef, undef, -$length, $comment]; + my $retract = [undef, undef, -$length, $self->extruder->retract_speed_mm_min, $comment]; my $lift = ($self->_retract_lift == 0 || defined $params{move_z}) && !$self->lifted ? undef - : [undef, $self->z + $self->_retract_lift, 0, 'lift plate during travel']; + : [undef, $self->z + $self->_retract_lift, 0, $self->config->travel_speed*60, 'lift plate during travel']; # check that we have a positive wipe length if ($wipe_path) { - $self->speed($self->speeds->{travel} * 0.8); - # subdivide the retraction my $retracted = 0; foreach my $line (@{$wipe_path->lines}) { my $segment_length = $line->length; # reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one # due to rounding - my $e = $retract->[2] * ($segment_length / $self->extruder->scaled_wipe_distance($self->print_config->travel_speed)) * 0.95; + my $e = $retract->[2] * ($segment_length / $self->extruder->scaled_wipe_distance($self->config->travel_speed)) * 0.95; $retracted += $e; - $gcode .= $self->G1($line->b, undef, $e, $retract->[3] . ";_WIPE"); + $gcode .= $self->G1($line->b, undef, $e, $self->config->travel_speed*60*0.8, $retract->[3] . ";_WIPE"); } if ($retracted > $retract->[2]) { # if we retracted less than we had to, retract the remainder # TODO: add regression test - $self->speed('retract'); - $gcode .= $self->G1(undef, undef, $retract->[2] - $retracted, $comment); + $gcode .= $self->G1(undef, undef, $retract->[2] - $retracted, $self->extruder->retract_speed_mm_min, $comment); } - } elsif ($self->print_config->use_firmware_retraction) { + } elsif ($self->config->use_firmware_retraction) { $gcode .= "G10 ; retract\n"; } else { - $self->speed('retract'); $gcode .= $self->G1(@$retract); } if (!$self->lifted) { - $self->speed('travel'); if (defined $params{move_z} && $self->_retract_lift > 0) { - my $travel = [undef, $params{move_z} + $self->_retract_lift, 0, 'move to next layer (' . $self->layer->id . ') and lift']; + my $travel = [undef, $params{move_z} + $self->_retract_lift, 0, $self->config->travel_speed*60, 'move to next layer (' . $self->layer->id . ') and lift']; $gcode .= $self->G0(@$travel); $self->lifted($self->_retract_lift); } elsif ($lift) { @@ -487,7 +470,7 @@ sub retract { # this makes sure we leave sufficient precision in the firmware $gcode .= $self->reset_e; - $gcode .= "M103 ; extruder off\n" if $self->print_config->gcode_flavor eq 'makerware'; + $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware'; return $gcode; } @@ -496,18 +479,16 @@ sub unretract { my ($self) = @_; my $gcode = ""; - $gcode .= "M101 ; extruder on\n" if $self->print_config->gcode_flavor eq 'makerware'; + $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerware'; if ($self->lifted) { - $self->speed('travel'); - $gcode .= $self->G0(undef, $self->z - $self->lifted, 0, 'restore layer Z'); + $gcode .= $self->G0(undef, $self->z - $self->lifted, 0, $self->config->travel_speed*60, 'restore layer Z'); $self->lifted(0); } my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra; if ($to_unretract) { - $self->speed('retract'); - if ($self->print_config->use_firmware_retraction) { + if ($self->config->use_firmware_retraction) { $gcode .= "G11 ; unretract\n"; } elsif ($self->_extrusion_axis) { # use G1 instead of G0 because G0 will blend the restart with the previous travel move @@ -515,7 +496,7 @@ sub unretract { $self->_extrusion_axis, $self->extruder->extrude($to_unretract), $self->extruder->retract_speed_mm_min; - $gcode .= " ; compensate retraction" if $self->print_config->gcode_comments; + $gcode .= " ; compensate retraction" if $self->config->gcode_comments; $gcode .= "\n"; } $self->extruder->set_retracted(0); @@ -527,11 +508,11 @@ sub unretract { sub reset_e { my ($self) = @_; - return "" if $self->print_config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/; + return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/; $self->extruder->set_E(0) if $self->extruder; - return sprintf "G92 %s0%s\n", $self->_extrusion_axis, ($self->print_config->gcode_comments ? ' ; reset extrusion distance' : '') - if $self->_extrusion_axis && !$self->print_config->use_relative_e_distances; + return sprintf "G92 %s0%s\n", $self->_extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') + if $self->_extrusion_axis && !$self->config->use_relative_e_distances; } sub set_acceleration { @@ -539,12 +520,12 @@ sub set_acceleration { return "" if !$acceleration; return sprintf "M204 S%s%s\n", - $acceleration, ($self->print_config->gcode_comments ? ' ; adjust acceleration' : ''); + $acceleration, ($self->config->gcode_comments ? ' ; adjust acceleration' : ''); } sub G0 { my $self = shift; - return $self->G1(@_) if !($self->print_config->g0 || $self->print_config->gcode_flavor eq 'mach3'); + return $self->G1(@_) if !($self->config->g0 || $self->config->gcode_flavor eq 'mach3'); return $self->_G0_G1("G0", @_); } @@ -554,7 +535,7 @@ sub G1 { } sub _G0_G1 { - my ($self, $gcode, $point, $z, $e, $comment) = @_; + my ($self, $gcode, $point, $z, $e, $F, $comment) = @_; if ($point) { $gcode .= sprintf " X%.3f Y%.3f", @@ -567,17 +548,12 @@ sub _G0_G1 { $gcode .= sprintf " Z%.3f", $z; } - return $self->_Gx($gcode, $e, $comment); + return $self->_Gx($gcode, $e, $F, $comment); } sub _Gx { - my ($self, $gcode, $e, $comment) = @_; + my ($self, $gcode, $e, $F, $comment) = @_; - my $F = $self->speed eq 'retract' - ? ($self->extruder->retract_speed_mm_min) - : $self->speeds->{$self->speed} // $self->speed; - $self->last_speed($self->speed); - $self->last_f($F); $gcode .= sprintf " F%.3f", $F; # output extrusion distance @@ -585,7 +561,7 @@ sub _Gx { $gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $self->extruder->extrude($e); } - $gcode .= " ; $comment" if $comment && $self->print_config->gcode_comments; + $gcode .= " ; $comment" if $comment && $self->config->gcode_comments; return "$gcode\n"; } @@ -606,8 +582,8 @@ sub set_extruder { $gcode .= $self->retract(toolchange => 1) if defined $self->extruder; # append custom toolchange G-code - if (defined $self->extruder && $self->print_config->toolchange_gcode) { - $gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->print_config->toolchange_gcode, { + if (defined $self->extruder && $self->config->toolchange_gcode) { + $gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->config->toolchange_gcode, { previous_extruder => $self->extruder->id, next_extruder => $extruder_id, }); @@ -622,24 +598,24 @@ sub set_extruder { ? $self->extruder->first_layer_temperature : $self->extruder->temperature; # we assume that heating is always slower than cooling, so no need to block - $gcode .= $self->set_temperature($temp + $self->print_config->standby_temperature_delta, 0); + $gcode .= $self->set_temperature($temp + $self->config->standby_temperature_delta, 0); } # set the new extruder $self->extruder($self->extruders->{$extruder_id}); $gcode .= sprintf "%s%d%s\n", - ($self->print_config->gcode_flavor eq 'makerware' + ($self->config->gcode_flavor eq 'makerware' ? 'M135 T' - : $self->print_config->gcode_flavor eq 'sailfish' + : $self->config->gcode_flavor eq 'sailfish' ? 'M108 T' : 'T'), $extruder_id, - ($self->print_config->gcode_comments ? ' ; change extruder' : ''); + ($self->config->gcode_comments ? ' ; change extruder' : ''); $gcode .= $self->reset_e; # set the new extruder to the operating temperature - if ($self->print_config->ooze_prevention) { + if ($self->config->ooze_prevention) { my $temp = defined $self->layer && $self->layer->id == 0 ? $self->extruder->first_layer_temperature : $self->extruder->temperature; @@ -655,18 +631,18 @@ sub set_fan { if ($self->last_fan_speed != $speed || $dont_save) { $self->last_fan_speed($speed) if !$dont_save; if ($speed == 0) { - my $code = $self->print_config->gcode_flavor eq 'teacup' + my $code = $self->config->gcode_flavor eq 'teacup' ? 'M106 S0' - : $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/ + : $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M127' : 'M107'; - return sprintf "$code%s\n", ($self->print_config->gcode_comments ? ' ; disable fan' : ''); + return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : ''); } else { - if ($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { - return sprintf "M126%s\n", ($self->print_config->gcode_comments ? ' ; enable fan' : ''); + if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { + return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : ''); } else { - return sprintf "M106 %s%d%s\n", ($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'), - (255 * $speed / 100), ($self->print_config->gcode_comments ? ' ; enable fan' : ''); + return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), + (255 * $speed / 100), ($self->config->gcode_comments ? ' ; enable fan' : ''); } } } @@ -676,17 +652,17 @@ sub set_fan { sub set_temperature { my ($self, $temperature, $wait, $tool) = @_; - return "" if $wait && $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/; + return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/; - my ($code, $comment) = ($wait && $self->print_config->gcode_flavor ne 'teacup') + my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') ? ('M109', 'wait for temperature to be reached') : ('M104', 'set temperature'); my $gcode = sprintf "$code %s%d %s; $comment\n", - ($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, - (defined $tool && ($self->multiple_extruders || $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : ""; + ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, + (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : ""; $gcode .= "M116 ; wait for temperature to be reached\n" - if $self->print_config->gcode_flavor eq 'teacup' && $wait; + if $self->config->gcode_flavor eq 'teacup' && $wait; return $gcode; } @@ -694,14 +670,14 @@ sub set_temperature { sub set_bed_temperature { my ($self, $temperature, $wait) = @_; - my ($code, $comment) = ($wait && $self->print_config->gcode_flavor ne 'teacup') - ? (($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') + my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') + ? (($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') : ('M140', 'set bed temperature'); my $gcode = sprintf "$code %s%d ; $comment\n", - ($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; + ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; $gcode .= "M116 ; wait for bed temperature to be reached\n" - if $self->print_config->gcode_flavor eq 'teacup' && $wait; + if $self->config->gcode_flavor eq 'teacup' && $wait; return $gcode; } diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 957e4b6d3..36e69a629 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -46,6 +46,7 @@ sub process_layer { my $gcode = ""; my $object = $layer->object; + $self->gcodegen->config->apply_object_config($object->config); # check whether we're going to apply spiralvase logic if (defined $self->spiralvase) { @@ -122,13 +123,13 @@ sub process_layer { if ($layer->isa('Slic3r::Layer::Support')) { if ($layer->support_interface_fills->count > 0) { $gcode .= $self->gcodegen->set_extruder($object->config->support_material_interface_extruder-1); - my %params = (speed => $object->config->support_material_speed*60); + my %params = (speed => $object->config->support_material_speed); $gcode .= $self->gcodegen->extrude_path($_, 'support material interface', %params) for @{$layer->support_interface_fills->chained_path_from($self->gcodegen->last_pos, 0)}; } if ($layer->support_fills->count > 0) { $gcode .= $self->gcodegen->set_extruder($object->config->support_material_extruder-1); - my %params = (speed => $object->config->support_material_speed*60); + my %params = (speed => $object->config->support_material_speed); $gcode .= $self->gcodegen->extrude_path($_, 'support material', %params) for @{$layer->support_fills->chained_path_from($self->gcodegen->last_pos, 0)}; } @@ -145,7 +146,7 @@ sub process_layer { foreach my $region_id (@region_ids) { my $layerm = $layer->regions->[$region_id] or next; my $region = $self->print->regions->[$region_id]; - $self->gcodegen->region($region); + $self->gcodegen->config->apply_region_config($region->config); # group extrusions by island my @perimeters_by_island = map [], 0..$#{$layer->slices}; # slice idx => @perimeters diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c381ce95c..84aab9c9b 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -857,11 +857,11 @@ sub write_gcode { # set up our helper object my $gcodegen = Slic3r::GCode->new( - print_config => $self->config, placeholder_parser => $self->placeholder_parser, layer_count => $self->layer_count, ); - $gcodegen->set_extruders($self->extruders); + $gcodegen->config->apply_print_config($self->config); + $gcodegen->set_extruders($self->extruders, $self->config); print $fh "G21 ; set units to millimeters\n" if $self->config->gcode_flavor ne 'makerware'; print $fh $gcodegen->set_fan(0, 1) if $self->config->cooling && $self->config->disable_fan_first_layers; diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index 758231930..c18ae0124 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -1394,15 +1394,6 @@ class PrintConfig : public virtual StaticPrintConfig return NULL; }; - - std::string get_extrusion_axis() { - if (this->gcode_flavor == gcfMach3) { - return std::string("A"); - } else if (this->gcode_flavor == gcfNoExtrusion) { - return std::string(""); - } - return this->extrusion_axis; - } }; class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, public PrintConfig { @@ -1414,6 +1405,15 @@ class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, publ if ((opt = PrintConfig::option(opt_key, create)) != NULL) return opt; return NULL; }; + + std::string get_extrusion_axis() { + if (this->gcode_flavor == gcfMach3) { + return std::string("A"); + } else if (this->gcode_flavor == gcfNoExtrusion) { + return std::string(""); + } + return this->extrusion_axis; + } }; } diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 7c89a9bff..7fe554e4f 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -45,7 +45,6 @@ %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; - std::string get_extrusion_axis(); }; %name{Slic3r::Config::PrintRegion} class PrintRegionConfig { @@ -103,10 +102,17 @@ double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); + void apply_print_config(PrintConfig* other) + %code{% THIS->apply(*other, true); %}; + void apply_object_config(PrintObjectConfig* other) + %code{% THIS->apply(*other, true); %}; + void apply_region_config(PrintRegionConfig* other) + %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; + std::string get_extrusion_axis(); }; %package{Slic3r::Config}; From dd1183f19a2a3ce8eae80a22d4d8be8b91528d24 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 15 May 2014 15:54:16 +0200 Subject: [PATCH 07/48] Some fixed after the recent Slic3r::GCode refactoring --- lib/Slic3r/GCode.pm | 41 +++++++++++++++------------------------ lib/Slic3r/GCode/Layer.pm | 6 ++---- lib/Slic3r/Print.pm | 2 +- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 5920e6eac..6fd99d4c8 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -22,8 +22,6 @@ has '_upper_layer_islands' => (is => 'rw'); has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); -has '_extrusion_axis' => (is => 'rw'); -has '_retract_lift' => (is => 'rw'); has 'extruders' => (is => 'ro', default => sub {{}}); has 'multiple_extruders' => (is => 'rw', default => sub {0}); has 'extruder' => (is => 'rw'); @@ -37,13 +35,6 @@ has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0 has 'last_fan_speed' => (is => 'rw', default => sub {0}); has 'wipe_path' => (is => 'rw'); -sub BUILD { - my ($self) = @_; - - $self->_extrusion_axis($self->config->get_extrusion_axis); - $self->_retract_lift($self->config->retract_lift->[0]); -} - sub set_extruders { my ($self, $extruder_ids, $print_config) = @_; @@ -272,7 +263,7 @@ sub extrude_path { # calculate extrusion length per distance unit my $e = $self->extruder->e_per_mm3 * $path->mm3_per_mm; - $e = 0 if !$self->_extrusion_axis; + $e = 0 if !$self->config->get_extrusion_axis; # set speed my $F; @@ -291,7 +282,7 @@ sub extrude_path { } elsif ($path->role == EXTR_ROLE_GAPFILL) { $F = $self->config->get_abs_value('gap_fill_speed'); } else { - $F = $speed; + $F = $speed // -1; die "Invalid speed" if $F < 0; # $speed == -1 } $F *= 60; # convert mm/sec to mm/min @@ -307,7 +298,7 @@ sub extrude_path { $gcode .= $path->gcode($self->extruder, $e, $F, $self->shift_x - $self->extruder->extruder_offset->x, $self->shift_y - $self->extruder->extruder_offset->y, #,, - $self->_extrusion_axis, + $self->config->get_extrusion_axis, $self->config->gcode_comments ? " ; $description" : ""); if ($self->enable_wipe) { @@ -427,9 +418,9 @@ sub retract { # prepare moves my $retract = [undef, undef, -$length, $self->extruder->retract_speed_mm_min, $comment]; - my $lift = ($self->_retract_lift == 0 || defined $params{move_z}) && !$self->lifted + my $lift = ($self->config->retract_lift->[0] == 0 || defined $params{move_z}) && !$self->lifted ? undef - : [undef, $self->z + $self->_retract_lift, 0, $self->config->travel_speed*60, 'lift plate during travel']; + : [undef, $self->z + $self->config->retract_lift->[0], 0, $self->config->travel_speed*60, 'lift plate during travel']; # check that we have a positive wipe length if ($wipe_path) { @@ -454,17 +445,17 @@ sub retract { $gcode .= $self->G1(@$retract); } if (!$self->lifted) { - if (defined $params{move_z} && $self->_retract_lift > 0) { - my $travel = [undef, $params{move_z} + $self->_retract_lift, 0, $self->config->travel_speed*60, 'move to next layer (' . $self->layer->id . ') and lift']; + if (defined $params{move_z} && $self->config->retract_lift->[0] > 0) { + my $travel = [undef, $params{move_z} + $self->config->retract_lift->[0], 0, $self->config->travel_speed*60, 'move to next layer (' . $self->layer->id . ') and lift']; $gcode .= $self->G0(@$travel); - $self->lifted($self->_retract_lift); + $self->lifted($self->config->retract_lift->[0]); } elsif ($lift) { $gcode .= $self->G1(@$lift); } } $self->extruder->set_retracted($self->extruder->retracted + $length); $self->extruder->set_restart_extra($restart_extra); - $self->lifted($self->_retract_lift) if $lift; + $self->lifted($self->config->retract_lift->[0]) if $lift; # reset extrusion distance during retracts # this makes sure we leave sufficient precision in the firmware @@ -490,10 +481,10 @@ sub unretract { if ($to_unretract) { if ($self->config->use_firmware_retraction) { $gcode .= "G11 ; unretract\n"; - } elsif ($self->_extrusion_axis) { + } elsif ($self->config->get_extrusion_axis) { # use G1 instead of G0 because G0 will blend the restart with the previous travel move $gcode .= sprintf "G1 %s%.5f F%.3f", - $self->_extrusion_axis, + $self->config->get_extrusion_axis, $self->extruder->extrude($to_unretract), $self->extruder->retract_speed_mm_min; $gcode .= " ; compensate retraction" if $self->config->gcode_comments; @@ -511,8 +502,8 @@ sub reset_e { return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/; $self->extruder->set_E(0) if $self->extruder; - return sprintf "G92 %s0%s\n", $self->_extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') - if $self->_extrusion_axis && !$self->config->use_relative_e_distances; + return sprintf "G92 %s0%s\n", $self->config->get_extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') + if $self->config->get_extrusion_axis && !$self->config->use_relative_e_distances; } sub set_acceleration { @@ -553,12 +544,12 @@ sub _G0_G1 { sub _Gx { my ($self, $gcode, $e, $F, $comment) = @_; - + use XXX; ZZZ "here" if $F =~ /move/i; $gcode .= sprintf " F%.3f", $F; # output extrusion distance - if ($e && $self->_extrusion_axis) { - $gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $self->extruder->extrude($e); + if ($e && $self->config->get_extrusion_axis) { + $gcode .= sprintf " %s%.5f", $self->config->get_extrusion_axis, $self->extruder->extrude($e); } $gcode .= " ; $comment" if $comment && $self->config->gcode_comments; diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 36e69a629..659ddfe0a 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -123,14 +123,12 @@ sub process_layer { if ($layer->isa('Slic3r::Layer::Support')) { if ($layer->support_interface_fills->count > 0) { $gcode .= $self->gcodegen->set_extruder($object->config->support_material_interface_extruder-1); - my %params = (speed => $object->config->support_material_speed); - $gcode .= $self->gcodegen->extrude_path($_, 'support material interface', %params) + $gcode .= $self->gcodegen->extrude_path($_, 'support material interface', $object->config->support_material_speed) for @{$layer->support_interface_fills->chained_path_from($self->gcodegen->last_pos, 0)}; } if ($layer->support_fills->count > 0) { $gcode .= $self->gcodegen->set_extruder($object->config->support_material_extruder-1); - my %params = (speed => $object->config->support_material_speed); - $gcode .= $self->gcodegen->extrude_path($_, 'support material', %params) + $gcode .= $self->gcodegen->extrude_path($_, 'support material', $object->config->support_material_speed) for @{$layer->support_fills->chained_path_from($self->gcodegen->last_pos, 0)}; } } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 84aab9c9b..43aecf17e 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -960,7 +960,7 @@ sub write_gcode { if ($finished_objects > 0) { $gcodegen->set_shift(map unscale $copy->[$_], X,Y); print $fh $gcodegen->retract; - print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, 'move to origin position for next object'); + print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, $gcodegen->config->travel_speed*60, 'move to origin position for next object'); } my $buffer = Slic3r::GCode::CoolingBuffer->new( From ad99b2a0fdb42ece84785d055b48e7606856b36f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 15 May 2014 16:37:18 +0200 Subject: [PATCH 08/48] Fixed one more regression introduced with Model refactoring. Includes regression test --- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 2 +- xs/MANIFEST | 1 + xs/t/19_model.t | 18 ++++++++++++++++++ xs/xsp/Model.xsp | 8 ++++---- 4 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 xs/t/19_model.t diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 5713acaba..e61ccfcce 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -197,7 +197,7 @@ sub on_btn_load { foreach my $object (@{$model->objects}) { foreach my $volume (@{$object->volumes}) { my $new_volume = $self->{model_object}->add_volume($volume); - $new_volume->modifier($is_modifier); + $new_volume->set_modifier($is_modifier); if (!defined $new_volume->material_id) { # it looks like this block is never entered because all input volumes seem to have an assigned material # TODO: check we can assume that for any input format diff --git a/xs/MANIFEST b/xs/MANIFEST index d8334bcd8..7cfc00572 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1732,6 +1732,7 @@ t/14_geometry.t t/15_config.t t/16_flow.t t/17_boundingbox.t +t/19_model.t xsp/BoundingBox.xsp xsp/Clipper.xsp xsp/Config.xsp diff --git a/xs/t/19_model.t b/xs/t/19_model.t new file mode 100644 index 000000000..ad0962de9 --- /dev/null +++ b/xs/t/19_model.t @@ -0,0 +1,18 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Slic3r::XS; +use Test::More tests => 3; + +{ + my $model = Slic3r::Model->new; + my $object = $model->_add_object; + isa_ok $object, 'Slic3r::Model::Object::Ref'; + isa_ok $object->origin_translation, 'Slic3r::Pointf::Ref'; + $object->origin_translation->translate(10,0); + is_deeply \@{$object->origin_translation}, [10,0], 'origin_translation is modified by ref'; +} + +__END__ diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 5903994ca..798169cc5 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -151,8 +151,8 @@ ModelMaterial::attributes() void set_layer_height_ranges(t_layer_height_ranges ranges) %code%{ THIS->layer_height_ranges = ranges; %}; - Clone origin_translation() - %code%{ RETVAL = THIS->origin_translation; %}; + Ref origin_translation() + %code%{ RETVAL = &THIS->origin_translation; %}; void set_origin_translation(Pointf* point) %code%{ THIS->origin_translation = *point; %}; }; @@ -185,8 +185,8 @@ ModelMaterial::attributes() %code%{ RETVAL = THIS->rotation; %}; double scaling_factor() %code%{ RETVAL = THIS->scaling_factor; %}; - Clone offset() - %code%{ RETVAL = THIS->offset; %}; + Ref offset() + %code%{ RETVAL = &THIS->offset; %}; void set_rotation(double val) %code%{ THIS->rotation = val; %}; From ac0a91a162ba8ec67e8c07a0613b5820db130c4e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 15 May 2014 18:49:11 +0200 Subject: [PATCH 09/48] Move many speed settings to PrintRegionConfig --- lib/Slic3r/Layer/Region.pm | 2 +- xs/src/PrintConfig.hpp | 64 +++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 60d024fbc..9a3f17c2a 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -125,7 +125,7 @@ sub make_perimeters { )}; # look for gaps - if ($self->print->config->gap_fill_speed > 0 && $self->config->fill_density > 0) { + 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 diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index c18ae0124..a253cf6db 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -105,6 +105,7 @@ class PrintConfigDef Options["bridge_speed"].type = coFloat; Options["bridge_speed"].label = "Bridges"; + Options["bridge_speed"].category = "Speed"; Options["bridge_speed"].tooltip = "Speed for printing bridges."; Options["bridge_speed"].sidetext = "mm/s"; Options["bridge_speed"].cli = "bridge-speed=f"; @@ -161,6 +162,7 @@ class PrintConfigDef Options["external_perimeter_speed"].type = coFloatOrPercent; Options["external_perimeter_speed"].label = "External perimeters"; + Options["external_perimeter_speed"].category = "Speed"; Options["external_perimeter_speed"].tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above."; Options["external_perimeter_speed"].sidetext = "mm/s or %"; Options["external_perimeter_speed"].cli = "external-perimeter-speed=s"; @@ -357,6 +359,7 @@ class PrintConfigDef Options["gap_fill_speed"].type = coFloat; Options["gap_fill_speed"].label = "Gap fill"; + Options["gap_fill_speed"].category = "Speed"; Options["gap_fill_speed"].tooltip = "Speed for filling small gaps using short zigzag moves. Keep this reasonably low to avoid too much shaking and resonance issues. Set zero to disable gaps filling."; Options["gap_fill_speed"].sidetext = "mm/s"; Options["gap_fill_speed"].cli = "gap-fill-speed=f"; @@ -431,6 +434,7 @@ class PrintConfigDef Options["infill_speed"].type = coFloat; Options["infill_speed"].label = "Infill"; + Options["infill_speed"].category = "Speed"; Options["infill_speed"].tooltip = "Speed for printing the internal fill."; Options["infill_speed"].sidetext = "mm/s"; Options["infill_speed"].cli = "infill-speed=f"; @@ -546,6 +550,7 @@ class PrintConfigDef Options["perimeter_speed"].type = coFloat; Options["perimeter_speed"].label = "Perimeters"; + Options["perimeter_speed"].category = "Speed"; Options["perimeter_speed"].tooltip = "Speed for perimeters (contours, aka vertical shells)."; Options["perimeter_speed"].sidetext = "mm/s"; Options["perimeter_speed"].cli = "perimeter-speed=f"; @@ -666,6 +671,7 @@ class PrintConfigDef Options["small_perimeter_speed"].type = coFloatOrPercent; Options["small_perimeter_speed"].label = "Small perimeters"; + Options["small_perimeter_speed"].category = "Speed"; Options["small_perimeter_speed"].tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above."; Options["small_perimeter_speed"].sidetext = "mm/s or %"; Options["small_perimeter_speed"].cli = "small-perimeter-speed=s"; @@ -712,6 +718,7 @@ class PrintConfigDef Options["solid_infill_speed"].type = coFloatOrPercent; Options["solid_infill_speed"].label = "Solid infill"; + Options["solid_infill_speed"].category = "Speed"; Options["solid_infill_speed"].tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above."; Options["solid_infill_speed"].sidetext = "mm/s or %"; Options["solid_infill_speed"].cli = "solid-infill-speed=s"; @@ -889,6 +896,7 @@ class PrintConfigDef Options["top_solid_infill_speed"].type = coFloatOrPercent; Options["top_solid_infill_speed"].label = "Top solid infill"; + Options["top_solid_infill_speed"].category = "Speed"; Options["top_solid_infill_speed"].tooltip = "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above."; Options["top_solid_infill_speed"].sidetext = "mm/s or %"; Options["top_solid_infill_speed"].cli = "top-solid-infill-speed=s"; @@ -1047,70 +1055,98 @@ class PrintRegionConfig : public virtual StaticPrintConfig { public: ConfigOptionInt bottom_solid_layers; + ConfigOptionFloat bridge_speed; + ConfigOptionFloatOrPercent external_perimeter_speed; ConfigOptionBool extra_perimeters; ConfigOptionInt fill_angle; ConfigOptionPercent fill_density; ConfigOptionEnum fill_pattern; + ConfigOptionFloat gap_fill_speed; ConfigOptionInt infill_extruder; ConfigOptionFloatOrPercent infill_extrusion_width; ConfigOptionInt infill_every_layers; + ConfigOptionFloat infill_speed; ConfigOptionBool overhangs; ConfigOptionInt perimeter_extruder; ConfigOptionFloatOrPercent perimeter_extrusion_width; + ConfigOptionFloat perimeter_speed; ConfigOptionInt perimeters; + ConfigOptionFloatOrPercent small_perimeter_speed; ConfigOptionEnum solid_fill_pattern; ConfigOptionFloat solid_infill_below_area; ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionInt solid_infill_every_layers; + ConfigOptionFloatOrPercent solid_infill_speed; ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; + ConfigOptionFloatOrPercent top_solid_infill_speed; PrintRegionConfig() : StaticPrintConfig() { this->bottom_solid_layers.value = 3; + this->bridge_speed.value = 60; + this->external_perimeter_speed.value = 70; + this->external_perimeter_speed.percent = true; this->extra_perimeters.value = true; this->fill_angle.value = 45; this->fill_density.value = 40; this->fill_pattern.value = ipHoneycomb; + this->gap_fill_speed.value = 20; this->infill_extruder.value = 1; this->infill_extrusion_width.value = 0; this->infill_extrusion_width.percent = false; this->infill_every_layers.value = 1; + this->infill_speed.value = 60; this->overhangs.value = true; this->perimeter_extruder.value = 1; this->perimeter_extrusion_width.value = 0; this->perimeter_extrusion_width.percent = false; + this->perimeter_speed.value = 30; this->perimeters.value = 3; + this->small_perimeter_speed.value = 30; + this->small_perimeter_speed.percent = false; this->solid_fill_pattern.value = ipRectilinear; this->solid_infill_below_area.value = 70; this->solid_infill_extrusion_width.value = 0; this->solid_infill_extrusion_width.percent = false; this->solid_infill_every_layers.value = 0; + this->solid_infill_speed.value = 60; + this->solid_infill_speed.percent = false; this->thin_walls.value = true; this->top_infill_extrusion_width.value = 0; this->top_infill_extrusion_width.percent = false; + this->top_solid_infill_speed.value = 50; + this->top_solid_infill_speed.percent = false; this->top_solid_layers.value = 3; }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { if (opt_key == "bottom_solid_layers") return &this->bottom_solid_layers; + if (opt_key == "bridge_speed") return &this->bridge_speed; + if (opt_key == "external_perimeter_speed") return &this->external_perimeter_speed; if (opt_key == "extra_perimeters") return &this->extra_perimeters; if (opt_key == "fill_angle") return &this->fill_angle; if (opt_key == "fill_density") return &this->fill_density; if (opt_key == "fill_pattern") return &this->fill_pattern; + if (opt_key == "gap_fill_speed") return &this->gap_fill_speed; if (opt_key == "infill_extruder") return &this->infill_extruder; if (opt_key == "infill_extrusion_width") return &this->infill_extrusion_width; if (opt_key == "infill_every_layers") return &this->infill_every_layers; + if (opt_key == "infill_speed") return &this->infill_speed; if (opt_key == "overhangs") return &this->overhangs; if (opt_key == "perimeter_extruder") return &this->perimeter_extruder; if (opt_key == "perimeter_extrusion_width") return &this->perimeter_extrusion_width; + if (opt_key == "perimeter_speed") return &this->perimeter_speed; if (opt_key == "perimeters") return &this->perimeters; + if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed; if (opt_key == "solid_fill_pattern") return &this->solid_fill_pattern; if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area; if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width; if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers; + if (opt_key == "solid_infill_speed") return &this->solid_infill_speed; if (opt_key == "thin_walls") return &this->thin_walls; if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width; + if (opt_key == "top_solid_infill_speed") return &this->top_solid_infill_speed; if (opt_key == "top_solid_layers") return &this->top_solid_layers; return NULL; @@ -1126,7 +1162,6 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionFloat bridge_acceleration; ConfigOptionInt bridge_fan_speed; ConfigOptionFloat bridge_flow_ratio; - ConfigOptionFloat bridge_speed; ConfigOptionFloat brim_width; ConfigOptionBool complete_objects; ConfigOptionBool cooling; @@ -1134,7 +1169,6 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionInt disable_fan_first_layers; ConfigOptionFloat duplicate_distance; ConfigOptionString end_gcode; - ConfigOptionFloatOrPercent external_perimeter_speed; ConfigOptionBool external_perimeters_first; ConfigOptionFloat extruder_clearance_height; ConfigOptionFloat extruder_clearance_radius; @@ -1150,13 +1184,11 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionFloatOrPercent first_layer_speed; ConfigOptionInts first_layer_temperature; ConfigOptionBool g0; - ConfigOptionFloat gap_fill_speed; ConfigOptionBool gcode_arcs; ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; ConfigOptionFloat infill_acceleration; ConfigOptionBool infill_first; - ConfigOptionFloat infill_speed; ConfigOptionString layer_gcode; ConfigOptionInt max_fan_speed; ConfigOptionInt min_fan_speed; @@ -1168,7 +1200,6 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionBool ooze_prevention; ConfigOptionString output_filename_format; ConfigOptionFloat perimeter_acceleration; - ConfigOptionFloat perimeter_speed; ConfigOptionStrings post_process; ConfigOptionPoint print_center; ConfigOptionBool randomize_start; @@ -1185,8 +1216,6 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionInt skirt_height; ConfigOptionInt skirts; ConfigOptionInt slowdown_below_layer_time; - ConfigOptionFloatOrPercent small_perimeter_speed; - ConfigOptionFloatOrPercent solid_infill_speed; ConfigOptionBool spiral_vase; ConfigOptionInt standby_temperature_delta; ConfigOptionString start_gcode; @@ -1195,7 +1224,6 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionInts temperature; ConfigOptionInt threads; ConfigOptionString toolchange_gcode; - ConfigOptionFloatOrPercent top_solid_infill_speed; ConfigOptionFloat travel_speed; ConfigOptionBool use_firmware_retraction; ConfigOptionBool use_relative_e_distances; @@ -1210,7 +1238,6 @@ class PrintConfig : public virtual StaticPrintConfig this->bridge_acceleration.value = 0; this->bridge_fan_speed.value = 100; this->bridge_flow_ratio.value = 1; - this->bridge_speed.value = 60; this->brim_width.value = 0; this->complete_objects.value = false; this->cooling.value = true; @@ -1218,8 +1245,6 @@ class PrintConfig : public virtual StaticPrintConfig 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->external_perimeter_speed.value = 70; - this->external_perimeter_speed.percent = true; this->external_perimeters_first.value = false; this->extruder_clearance_height.value = 20; this->extruder_clearance_radius.value = 20; @@ -1241,13 +1266,11 @@ class PrintConfig : public virtual StaticPrintConfig this->first_layer_temperature.values.resize(1); this->first_layer_temperature.values[0] = 200; this->g0.value = false; - this->gap_fill_speed.value = 20; this->gcode_arcs.value = false; this->gcode_comments.value = false; this->gcode_flavor.value = gcfRepRap; this->infill_acceleration.value = 0; this->infill_first.value = false; - this->infill_speed.value = 60; this->layer_gcode.value = ""; this->max_fan_speed.value = 100; this->min_fan_speed.value = 35; @@ -1260,7 +1283,6 @@ class PrintConfig : public virtual StaticPrintConfig this->ooze_prevention.value = false; this->output_filename_format.value = "[input_filename_base].gcode"; this->perimeter_acceleration.value = 0; - this->perimeter_speed.value = 30; this->print_center.point = Pointf(100,100); this->randomize_start.value = false; this->resolution.value = 0; @@ -1284,10 +1306,6 @@ class PrintConfig : public virtual StaticPrintConfig this->skirt_height.value = 1; this->skirts.value = 1; this->slowdown_below_layer_time.value = 30; - this->small_perimeter_speed.value = 30; - this->small_perimeter_speed.percent = false; - this->solid_infill_speed.value = 60; - this->solid_infill_speed.percent = false; 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"; @@ -1297,8 +1315,6 @@ class PrintConfig : public virtual StaticPrintConfig this->temperature.values[0] = 200; this->threads.value = 2; this->toolchange_gcode.value = ""; - this->top_solid_infill_speed.value = 50; - this->top_solid_infill_speed.percent = false; this->travel_speed.value = 130; this->use_firmware_retraction.value = false; this->use_relative_e_distances.value = false; @@ -1315,7 +1331,6 @@ class PrintConfig : public virtual StaticPrintConfig if (opt_key == "bridge_acceleration") return &this->bridge_acceleration; if (opt_key == "bridge_fan_speed") return &this->bridge_fan_speed; if (opt_key == "bridge_flow_ratio") return &this->bridge_flow_ratio; - if (opt_key == "bridge_speed") return &this->bridge_speed; if (opt_key == "brim_width") return &this->brim_width; if (opt_key == "complete_objects") return &this->complete_objects; if (opt_key == "cooling") return &this->cooling; @@ -1323,7 +1338,6 @@ class PrintConfig : public virtual StaticPrintConfig 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 == "external_perimeter_speed") return &this->external_perimeter_speed; if (opt_key == "external_perimeters_first") return &this->external_perimeters_first; if (opt_key == "extruder_clearance_height") return &this->extruder_clearance_height; if (opt_key == "extruder_clearance_radius") return &this->extruder_clearance_radius; @@ -1339,13 +1353,11 @@ class PrintConfig : public virtual StaticPrintConfig if (opt_key == "first_layer_speed") return &this->first_layer_speed; if (opt_key == "first_layer_temperature") return &this->first_layer_temperature; if (opt_key == "g0") return &this->g0; - if (opt_key == "gap_fill_speed") return &this->gap_fill_speed; if (opt_key == "gcode_arcs") return &this->gcode_arcs; if (opt_key == "gcode_comments") return &this->gcode_comments; if (opt_key == "gcode_flavor") return &this->gcode_flavor; if (opt_key == "infill_acceleration") return &this->infill_acceleration; if (opt_key == "infill_first") return &this->infill_first; - if (opt_key == "infill_speed") return &this->infill_speed; 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; @@ -1357,7 +1369,6 @@ class PrintConfig : public virtual StaticPrintConfig if (opt_key == "ooze_prevention") return &this->ooze_prevention; if (opt_key == "output_filename_format") return &this->output_filename_format; if (opt_key == "perimeter_acceleration") return &this->perimeter_acceleration; - if (opt_key == "perimeter_speed") return &this->perimeter_speed; if (opt_key == "post_process") return &this->post_process; if (opt_key == "print_center") return &this->print_center; if (opt_key == "randomize_start") return &this->randomize_start; @@ -1374,8 +1385,6 @@ class PrintConfig : public virtual StaticPrintConfig if (opt_key == "skirt_height") return &this->skirt_height; if (opt_key == "skirts") return &this->skirts; if (opt_key == "slowdown_below_layer_time") return &this->slowdown_below_layer_time; - if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed; - if (opt_key == "solid_infill_speed") return &this->solid_infill_speed; 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; @@ -1384,7 +1393,6 @@ class PrintConfig : public virtual StaticPrintConfig 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 == "top_solid_infill_speed") return &this->top_solid_infill_speed; 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; From 58ffaca2dffe65975cd27008262b6a6870aa4495 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 15 May 2014 19:22:41 +0200 Subject: [PATCH 10/48] Bugfix: ooze_prevention brought the extruder too far. Includes regression test --- lib/Slic3r/GCode.pm | 10 ++++++++-- t/multi.t | 14 +++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 6fd99d4c8..f83f4ea5f 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -544,7 +544,7 @@ sub _G0_G1 { sub _Gx { my ($self, $gcode, $e, $F, $comment) = @_; - use XXX; ZZZ "here" if $F =~ /move/i; + $gcode .= sprintf " F%.3f", $F; # output extrusion distance @@ -583,7 +583,13 @@ sub set_extruder { # set the current extruder to the standby temperature if ($self->standby_points && defined $self->extruder) { # move to the nearest standby point - $gcode .= $self->travel_to($self->last_pos->nearest_point($self->standby_points)); + { + my $last_pos = $self->last_pos->clone; + $last_pos->translate(scale +$self->shift_x, scale +$self->shift_y); + my $standby_point = $last_pos->nearest_point($self->standby_points); + $standby_point->translate(scale -$self->shift_x, scale -$self->shift_y); + $gcode .= $self->travel_to($standby_point); + } my $temp = defined $self->layer && $self->layer->id == 0 ? $self->extruder->first_layer_temperature diff --git a/t/multi.t b/t/multi.t index fe1f81848..b57415d46 100644 --- a/t/multi.t +++ b/t/multi.t @@ -1,4 +1,4 @@ -use Test::More tests => 12; +use Test::More tests => 13; use strict; use warnings; @@ -10,6 +10,7 @@ BEGIN { use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(scale convex_hull); +use Slic3r::Geometry::Clipper qw(offset); use Slic3r::Test; { @@ -39,9 +40,11 @@ use Slic3r::Test; : $config->temperature->[$tool]; die 'standby temperature was not set before toolchange' if $tool_temp[$tool] != $expected_temp + $config->standby_temperature_delta; + + # ignore initial toolchange + push @toolchange_points, Slic3r::Point->new_scale($self->X, $self->Y); } $tool = $1; - push @toolchange_points, Slic3r::Point->new_scale($self->X, $self->Y); } elsif ($cmd eq 'M104' || $cmd eq 'M109') { my $t = $args->{T} // $tool; if ($tool_temp[$t] == 0) { @@ -55,7 +58,12 @@ use Slic3r::Test; } }); my $convex_hull = convex_hull(\@extrusion_points); - ok !(first { $convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen outside skirt'; + ok !(defined first { $convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen outside skirt'; + + # offset the skirt by the maximum displacement between extruders plus a safety extra margin + my $delta = scale(20 * sqrt(2) + 1); + my $outer_convex_hull = offset([$convex_hull], +$delta)->[0]; + ok !(defined first { !$outer_convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen within expected area'; } { From 0ddcefe9561586c5af291dbf5d3d803fc047d852 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 May 2014 17:02:18 +0200 Subject: [PATCH 11/48] Use support material speed for skirt and brim instead of perimeter speed so that perimeter speed can be set on a per-region basis --- lib/Slic3r/GCode.pm | 8 ++++---- lib/Slic3r/GCode/Layer.pm | 5 +++-- t/skirt_brim.t | 5 +++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index f83f4ea5f..d515573c2 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -141,7 +141,7 @@ sub extrude { } sub extrude_loop { - my ($self, $loop, $description) = @_; + my ($self, $loop, $description, $speed) = @_; # make a copy; don't modify the orientation of the original loop object otherwise # next copies (if any) would not detect the correct orientation @@ -199,10 +199,10 @@ sub extrude_loop { return '' if !@paths; # apply the small perimeter speed - my $speed = -1; if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) { - $speed = $self->config->get_abs_value('small_perimeter_speed'); + $speed //= $self->config->get_abs_value('small_perimeter_speed'); } + $speed //= -1; # extrude along the path my $gcode = join '', map $self->extrude_path($_, $description, $speed), @paths; @@ -267,7 +267,7 @@ sub extrude_path { # set speed my $F; - if ($path->role == EXTR_ROLE_PERIMETER || $path->role == EXTR_ROLE_SKIRT) { + if ($path->role == EXTR_ROLE_PERIMETER) { $F = $self->config->get_abs_value('perimeter_speed'); } elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) { $F = $self->config->get_abs_value('external_perimeter_speed'); diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 659ddfe0a..69cb7b45d 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -96,7 +96,7 @@ sub process_layer { my $extruder_id = $extruder_ids[($i/@extruder_ids) % @extruder_ids]; $gcode .= $self->gcodegen->set_extruder($extruder_id) if $layer->id == 0; - $gcode .= $self->gcodegen->extrude_loop($skirt_loops[$i], 'skirt'); + $gcode .= $self->gcodegen->extrude_loop($skirt_loops[$i], 'skirt', $object->config->support_material_speed); } } $self->skirt_done->{$layer->print_z} = 1; @@ -107,7 +107,8 @@ sub process_layer { if (!$self->brim_done) { $gcode .= $self->gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1); $self->gcodegen->set_shift(@{$self->shift}); - $gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim}; + $gcode .= $self->gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) + for @{$self->print->brim}; $self->brim_done(1); $self->gcodegen->straight_once(1); } diff --git a/t/skirt_brim.t b/t/skirt_brim.t index 8c616d98a..b62c9a8bb 100644 --- a/t/skirt_brim.t +++ b/t/skirt_brim.t @@ -16,7 +16,7 @@ use Slic3r::Test; $config->set('skirts', 1); $config->set('skirt_height', 2); $config->set('perimeters', 0); - $config->set('perimeter_speed', 99); + $config->set('support_material_speed', 99); $config->set('cooling', 0); # to prevent speeds to be altered $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered @@ -33,7 +33,7 @@ use Slic3r::Test; if (defined $self->Z) { $layers_with_skirt{$self->Z} //= 0; $layers_with_skirt{$self->Z} = 1 - if $info->{extruding} && ($args->{F} // $self->F) == $config->perimeter_speed*60; + if $info->{extruding} && ($args->{F} // $self->F) == $config->support_material_speed*60; } }); fail "wrong number of layers with skirt" @@ -50,6 +50,7 @@ use Slic3r::Test; $config->set('top_solid_layers', 0); # to prevent solid shells and their speeds $config->set('bottom_solid_layers', 0); # to prevent solid shells and their speeds $config->set('brim_width', 5); + $config->set('support_material_speed', 99); $config->set('cooling', 0); # to prevent speeds to be altered $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered From 3f29a9292a291419bfe6603fbc1360cf6e8cb9da Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 May 2014 18:47:16 +0200 Subject: [PATCH 12/48] Remove unused method SkeinPanel::init_print() --- lib/Slic3r/GUI/SkeinPanel.pm | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index cfb1755d0..13b532635 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -235,15 +235,6 @@ sub extra_variables { return { %extra_variables }; } -sub init_print { - my $self = shift; - - return Slic3r::Print->new( - config => $self->config, - extra_variables => $self->extra_variables, - ); -} - sub export_config { my $self = shift; From 038076e0401d86f10484933317539e10158a6eda Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 May 2014 23:36:30 +0200 Subject: [PATCH 13/48] Bugfix: first layer extrusion width wasn't affecting infill. Includes regression test. #2042 --- lib/Slic3r/Fill.pm | 1 + t/flow.t | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 t/flow.t diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 6d1fae502..93012ae99 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -207,6 +207,7 @@ sub make_fill { $h, $is_bridge, $layerm->id == 0, + undef, $layerm->object, ); diff --git a/t/flow.t b/t/flow.t new file mode 100644 index 000000000..0d436f5a6 --- /dev/null +++ b/t/flow.t @@ -0,0 +1,41 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use List::Util qw(first sum); +use Slic3r; +use Slic3r::Geometry qw(scale); +use Slic3r::Test; + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('skirts', 1); + $config->set('brim_width', 2); + $config->set('perimeters', 3); + $config->set('fill_density', 0.4); + $config->set('bottom_solid_layers', 1); + $config->set('first_layer_extrusion_width', 2); + $config->set('first_layer_height', '100%'); + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my @E_per_mm = (); + Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($self->Z == $config->layer_height) { # only consider first layer + if ($info->{extruding} && $info->{dist_XY} > 0) { + push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; + } + } + }); + my $E_per_mm_avg = sum(@E_per_mm) / @E_per_mm; + ok !(defined first { abs($_ - $E_per_mm_avg) > 0.01 } @E_per_mm), + 'first_layer_extrusion_width applies to everything on first layer'; +} + +__END__ From a00f6c72ed818242c46733d4457bf8143a640e70 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 May 2014 23:56:00 +0200 Subject: [PATCH 14/48] Don't emit temperature commands if standby temperature delta is zero --- lib/Slic3r/GCode.pm | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index d515573c2..d3b026f19 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -591,11 +591,13 @@ sub set_extruder { $gcode .= $self->travel_to($standby_point); } - my $temp = defined $self->layer && $self->layer->id == 0 - ? $self->extruder->first_layer_temperature - : $self->extruder->temperature; - # we assume that heating is always slower than cooling, so no need to block - $gcode .= $self->set_temperature($temp + $self->config->standby_temperature_delta, 0); + if ($self->config->standby_temperature_delta != 0) { + my $temp = defined $self->layer && $self->layer->id == 0 + ? $self->extruder->first_layer_temperature + : $self->extruder->temperature; + # we assume that heating is always slower than cooling, so no need to block + $gcode .= $self->set_temperature($temp + $self->config->standby_temperature_delta, 0); + } } # set the new extruder @@ -612,7 +614,7 @@ sub set_extruder { $gcode .= $self->reset_e; # set the new extruder to the operating temperature - if ($self->config->ooze_prevention) { + if ($self->config->ooze_prevention && $self->config->standby_temperature_delta != 0) { my $temp = defined $self->layer && $self->layer->id == 0 ? $self->extruder->first_layer_temperature : $self->extruder->temperature; From 63d56c666bd511431781a92fb8d7d7b57af8bec1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 20 May 2014 23:25:12 +0200 Subject: [PATCH 15/48] Include full config in G-code files. #2047 #2032 --- lib/Slic3r/Print.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 43aecf17e..a0bba00f5 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -1046,9 +1046,11 @@ sub write_gcode { # append full config print $fh "\n"; - foreach my $opt_key (sort @{$self->config->get_keys}) { - next if $Slic3r::Config::Options->{$opt_key}{shortcut}; - printf $fh "; %s = %s\n", $opt_key, $self->config->serialize($opt_key); + foreach my $config ($self->config, $self->default_object_config, $self->default_region_config) { + foreach my $opt_key (sort @{$config->get_keys}) { + next if $Slic3r::Config::Options->{$opt_key}{shortcut}; + printf $fh "; %s = %s\n", $opt_key, $config->serialize($opt_key); + } } # close our gcode file From 0ba685f55673128bf4f706e8f0c7036aebebe5df Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 20 May 2014 23:29:43 +0200 Subject: [PATCH 16/48] Fix regression causing config validation to be ignored when using the Export G-code button in plater. #2046 --- lib/Slic3r/GUI/Plater.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 6b818ed68..84e17ebe9 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -692,6 +692,7 @@ sub export_gcode { $self->{print}->apply_config($config); $self->{print}->validate; }; + Slic3r::GUI::catch_error($self) and return; # apply extra variables { From 85b0a4376a1c7fdf42d5c70ab9e3612ceb81ee6e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 20 May 2014 23:37:17 +0200 Subject: [PATCH 17/48] Use last extruder's settings when adding new ones. #1997 --- lib/Slic3r/GUI/Tab.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 7ebc22e11..431d66b19 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -743,7 +743,13 @@ sub _build_extruder_pages { # extend options foreach my $opt_key ($self->_extruder_options) { my $values = $self->{config}->get($opt_key); - $values->[$extruder_idx] //= $default_config->get_at($opt_key, 0); + if (!defined $values) { + $values = [ $default_config->get_at($opt_key, 0) ]; + } else { + # use last extruder's settings for the new one + my $last_value = $values->[-1]; + $values->[$extruder_idx] //= $last_value; + } $self->{config}->set($opt_key, $values) or die "Unable to extend $opt_key"; } From a8b6e327671237f516023ba5bd0b75bd998d6cbe Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 21 May 2014 11:38:42 +0200 Subject: [PATCH 18/48] Gracefully handle loading config files having empty strings for multi-value options (like wipe). #2003 --- lib/Slic3r/GUI/OptionsGroup.pm | 4 +++- xs/t/15_config.t | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index f6e63fab8..69c60114b 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -231,7 +231,9 @@ sub _build_field { }); } else { $field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style); - $self->_setters->{$opt_key} = sub { $field->ChangeValue($_[0]) }; + # value supplied to the setter callback might be undef in case user loads a config + # that has empty string for multi-value options like 'wipe' + $self->_setters->{$opt_key} = sub { $field->ChangeValue($_[0]) if defined $_[0] }; EVT_TEXT($self->parent, $field, $on_change); EVT_KILL_FOCUS($field, $on_kill_focus); } diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 57603ed1a..282670080 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 112; +use Test::More tests => 114; foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { $config->set('layer_height', 0.3); @@ -101,6 +101,8 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { is $config->serialize('wipe'), '1,0', 'serialize bools'; $config->set_deserialize('wipe', '0,1,1'); is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools'; + $config->set_deserialize('wipe', ''); + is_deeply $config->get('wipe'), [], 'deserialize bools from empty string'; $config->set_deserialize('retract_layer_change', 0); is_deeply $config->get('retract_layer_change'), [0], 'deserialize bools from non-string value'; { From 08279ec5d8b4ae01b6c09fca8b8e02b146d0de7d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 21 May 2014 15:03:31 +0200 Subject: [PATCH 19/48] Bugfix: thin walls forming a closed loop had overlapping segments at their endpoints. #1948 #1875 --- t/thin.t | 6 ++++-- xs/src/ClipperUtils.cpp | 42 +++++++++++++++++++++-------------------- xs/src/ExPolygon.cpp | 2 ++ xs/src/Geometry.cpp | 15 +++++++++++++++ 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/t/thin.t b/t/thin.t index eb31b6e29..e02e1b33d 100644 --- a/t/thin.t +++ b/t/thin.t @@ -1,4 +1,4 @@ -use Test::More tests => 11; +use Test::More tests => 13; use strict; use warnings; @@ -62,7 +62,9 @@ if (0) { ); my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); my $res = $expolygon->medial_axis(scale 1, scale 0.5); - is scalar(@$res), 1, 'medial axis of a square shape is a single closed loop'; + is scalar(@$res), 1, 'medial axis of a square shape is a single path'; + isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline'; + ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop'; ok $res->[0]->length > $hole_in_square->length && $res->[0]->length < $square->length, 'medial axis loop has reasonable length'; } diff --git a/xs/src/ClipperUtils.cpp b/xs/src/ClipperUtils.cpp index 3f2e48d50..d49aba529 100644 --- a/xs/src/ClipperUtils.cpp +++ b/xs/src/ClipperUtils.cpp @@ -339,36 +339,25 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_) { + + /* Clipper will remove a polyline segment if first point coincides with last one. + Until that bug is not fixed upstream, we move one of those points slightly. */ + Slic3r::Polylines polylines = subject; // temp copy to avoid dropping the const qualifier + for (Slic3r::Polylines::iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) + polyline->points.front().translate(1, 0); + // perform operation ClipperLib::PolyTree polytree; - _clipper_do(clipType, subject, clip, polytree, ClipperLib::pftNonZero, safety_offset_); + _clipper_do(clipType, polylines, clip, polytree, ClipperLib::pftNonZero, safety_offset_); // convert into Polylines ClipperLib::Paths output; ClipperLib::PolyTreeToPaths(polytree, output); ClipperPaths_to_Slic3rMultiPoints(output, retval); -} - -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_) -{ - // transform input polygons into polylines - Slic3r::Polylines polylines; - polylines.reserve(subject.size()); - for (Slic3r::Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) - polylines.push_back(*polygon); // implicit call to split_at_first_point() - - /* Clipper will remove a polyline segment if first point coincides with last one. - Until that bug is not fixed upstream, we move one of those points slightly. */ - for (Slic3r::Polylines::iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) - polyline->points.front().translate(1, 0); - - // perform clipping - _clipper(clipType, polylines, clip, retval, safety_offset_); // compensate for the above hack for (Slic3r::Polylines::iterator polyline = retval.begin(); polyline != retval.end(); ++polyline) { - for (Slic3r::Polylines::iterator subj_polyline = polylines.begin(); subj_polyline != polylines.end(); ++subj_polyline) { + for (Slic3r::Polylines::const_iterator subj_polyline = polylines.begin(); subj_polyline != polylines.end(); ++subj_polyline) { // if first point of clipped line coincides with first point of subject line, compensate for hack if (polyline->points.front().coincides_with(subj_polyline->points.front())) { polyline->points.front().translate(-1, 0); @@ -381,6 +370,19 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, } } } +} + +void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, + const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_) +{ + // transform input polygons into polylines + Slic3r::Polylines polylines; + polylines.reserve(subject.size()); + for (Slic3r::Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) + polylines.push_back(*polygon); // implicit call to split_at_first_point() + + // perform clipping + _clipper(clipType, polylines, clip, retval, safety_offset_); /* If the split_at_first_point() call above happens to split the polygon inside the clipping area we would get two consecutive polylines instead of a single one, so we go through them in order diff --git a/xs/src/ExPolygon.cpp b/xs/src/ExPolygon.cpp index e2ee364a8..78a658bd6 100644 --- a/xs/src/ExPolygon.cpp +++ b/xs/src/ExPolygon.cpp @@ -155,7 +155,9 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) ma.build(polylines); // extend initial and final segments of each polyline (they will be clipped) + // unless they represent closed loops for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) { + if (polyline->points.front().coincides_with(polyline->points.back())) continue; polyline->extend_start(max_width); polyline->extend_end(max_width); } diff --git a/xs/src/Geometry.cpp b/xs/src/Geometry.cpp index c0d9e66cd..0295d54de 100644 --- a/xs/src/Geometry.cpp +++ b/xs/src/Geometry.cpp @@ -123,6 +123,21 @@ MedialAxis::build(Polylines* polylines) construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd); + /* + // DEBUG: dump all Voronoi edges + { + for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { + if (edge->is_infinite()) continue; + + Polyline polyline; + polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); + polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); + polylines->push_back(polyline); + } + return; + } + */ + // collect valid edges (i.e. prune those not belonging to MAT) // note: this keeps twins, so it contains twice the number of the valid edges this->edges.clear(); From 8ca352eb62b19a27e983bd433a34e81e22fed64f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 21 May 2014 15:21:20 +0200 Subject: [PATCH 20/48] Separate speed option for support material interface. #2009 --- README.md | 3 +++ lib/Slic3r/GCode/Layer.pm | 4 ++-- lib/Slic3r/GUI/Tab.pm | 2 +- slic3r.pl | 3 +++ xs/src/PrintConfig.hpp | 11 +++++++++++ 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a979afba8..d471f6f16 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,9 @@ The author of the Silk icon set is Mark James. (default: 50) --support-material-speed Speed of support material print moves in mm/s (default: 60) + --support-material-interface-speed + Speed of support material interface print moves in mm/s or % over support material + speed (default: 100%) --bridge-speed Speed of bridge print moves in mm/s (default: 60) --gap-fill-speed Speed of gap fill print moves in mm/s (default: 20) --first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 69cb7b45d..9bac7901c 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -124,12 +124,12 @@ sub process_layer { if ($layer->isa('Slic3r::Layer::Support')) { if ($layer->support_interface_fills->count > 0) { $gcode .= $self->gcodegen->set_extruder($object->config->support_material_interface_extruder-1); - $gcode .= $self->gcodegen->extrude_path($_, 'support material interface', $object->config->support_material_speed) + $gcode .= $self->gcodegen->extrude_path($_, 'support material interface', $object->config->get_abs_value('support_material_interface_speed')) for @{$layer->support_interface_fills->chained_path_from($self->gcodegen->last_pos, 0)}; } if ($layer->support_fills->count > 0) { $gcode .= $self->gcodegen->set_extruder($object->config->support_material_extruder-1); - $gcode .= $self->gcodegen->extrude_path($_, 'support material', $object->config->support_material_speed) + $gcode .= $self->gcodegen->extrude_path($_, 'support material', $object->config->get_abs_value('support_material_speed')) for @{$layer->support_fills->chained_path_from($self->gcodegen->last_pos, 0)}; } } diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 431d66b19..9f68b2cd1 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -454,7 +454,7 @@ sub build { $self->add_options_page('Speed', 'time.png', optgroups => [ { title => 'Speed for print moves', - options => [qw(perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed support_material_speed bridge_speed gap_fill_speed)], + options => [qw(perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed support_material_speed support_material_interface_speed bridge_speed gap_fill_speed)], }, { title => 'Speed for non-print moves', diff --git a/slic3r.pl b/slic3r.pl index ed25c6b8d..938f4ed93 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -293,6 +293,9 @@ $j (default: $config->{top_solid_infill_speed}) --support-material-speed Speed of support material print moves in mm/s (default: $config->{support_material_speed}) + --support-material-interface-speed + Speed of support material interface print moves in mm/s or % over support material + speed (default: $config->{support_material_interface_speed}) --bridge-speed Speed of bridge print moves in mm/s (default: $config->{bridge_speed}) --gap-fill-speed Speed of gap fill print moves in mm/s (default: $config->{gap_fill_speed}) --first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index a253cf6db..d775b5ff9 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -819,6 +819,13 @@ class PrintConfigDef Options["support_material_interface_spacing"].sidetext = "mm"; Options["support_material_interface_spacing"].cli = "support-material-interface-spacing=f"; + Options["support_material_interface_speed"].type = coFloatOrPercent; + Options["support_material_interface_speed"].label = "Support material interface"; + Options["support_material_interface_speed"].category = "Support material"; + Options["support_material_interface_speed"].tooltip = "Speed for printing support material interface layers. If expressed as percentage (for example 50%) it will be calculated over support material speed."; + Options["support_material_interface_speed"].sidetext = "mm/s or %"; + Options["support_material_interface_speed"].cli = "support-material-interface-speed=s"; + Options["support_material_pattern"].type = coEnum; Options["support_material_pattern"].label = "Pattern"; Options["support_material_pattern"].category = "Support material"; @@ -996,6 +1003,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; ConfigOptionFloat support_material_interface_spacing; + ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionEnum support_material_pattern; ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; @@ -1020,6 +1028,8 @@ class PrintObjectConfig : public virtual StaticPrintConfig this->support_material_interface_extruder.value = 1; this->support_material_interface_layers.value = 3; this->support_material_interface_spacing.value = 0; + this->support_material_interface_speed.value = 100; + this->support_material_interface_speed.percent = true; this->support_material_pattern.value = smpPillars; this->support_material_spacing.value = 2.5; this->support_material_speed.value = 60; @@ -1042,6 +1052,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig if (opt_key == "support_material_interface_extruder") return &this->support_material_interface_extruder; if (opt_key == "support_material_interface_layers") return &this->support_material_interface_layers; if (opt_key == "support_material_interface_spacing") return &this->support_material_interface_spacing; + if (opt_key == "support_material_interface_speed") return &this->support_material_interface_speed; if (opt_key == "support_material_pattern") return &this->support_material_pattern; if (opt_key == "support_material_spacing") return &this->support_material_spacing; if (opt_key == "support_material_speed") return &this->support_material_speed; From 5e6ff952dfb4850878665509727e944a3c749112 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 21 May 2014 15:45:16 +0200 Subject: [PATCH 21/48] Fix more regressions in test suite regarding the workaround for Clipper bug --- t/clipper.t | 7 +------ xs/src/ClipperUtils.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/t/clipper.t b/t/clipper.t index 0d3933e90..a898a53f6 100644 --- a/t/clipper.t +++ b/t/clipper.t @@ -82,12 +82,7 @@ use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex diff_pl); my $res = diff_pl([$square_pl], []); is scalar(@$res), 1, 'no-op diff_pl returns the right number of polylines'; isa_ok $res->[0], 'Slic3r::Polyline', 'no-op diff_pl result'; - - ### NOTE: this test will fail when a bug in Clipper is fixed that is currently - ### causing loss of one segment when input polyline has coinciding endpoints. - ### When the bug is fixed in Clipper, this test should be reverted from isnt() to is() - ### and workarounds in Slic3r::Polygon::clip_as_polyline() should be removed. - isnt scalar(@{$res->[0]}), scalar(@$square_pl), 'no-op diff_pl returns the unmodified input polyline'; + is scalar(@{$res->[0]}), scalar(@$square_pl), 'no-op diff_pl returns the unmodified input polyline'; } __END__ diff --git a/xs/src/ClipperUtils.cpp b/xs/src/ClipperUtils.cpp index d49aba529..e26b159da 100644 --- a/xs/src/ClipperUtils.cpp +++ b/xs/src/ClipperUtils.cpp @@ -343,8 +343,13 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, /* Clipper will remove a polyline segment if first point coincides with last one. Until that bug is not fixed upstream, we move one of those points slightly. */ Slic3r::Polylines polylines = subject; // temp copy to avoid dropping the const qualifier - for (Slic3r::Polylines::iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) - polyline->points.front().translate(1, 0); + std::vector translated(subject.size(), false); // keep track of polylines we applied the hack to + for (Slic3r::Polylines::iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { + if (polyline->points.front().coincides_with(polyline->points.back())) { + polyline->points.front().translate(1, 0); + translated[ polyline - polylines.begin() ] = true; + } + } // perform operation ClipperLib::PolyTree polytree; @@ -358,6 +363,7 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, // compensate for the above hack for (Slic3r::Polylines::iterator polyline = retval.begin(); polyline != retval.end(); ++polyline) { for (Slic3r::Polylines::const_iterator subj_polyline = polylines.begin(); subj_polyline != polylines.end(); ++subj_polyline) { + if (!translated[ subj_polyline - polylines.begin() ]) continue; // if first point of clipped line coincides with first point of subject line, compensate for hack if (polyline->points.front().coincides_with(subj_polyline->points.front())) { polyline->points.front().translate(-1, 0); From 874c7a6e8bd508afe991265d2819814b14b28472 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 21 May 2014 15:53:41 +0200 Subject: [PATCH 22/48] One line missing for support_material_interface_speed declaration --- xs/src/PrintConfig.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index d775b5ff9..d3ced798a 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -825,6 +825,7 @@ class PrintConfigDef Options["support_material_interface_speed"].tooltip = "Speed for printing support material interface layers. If expressed as percentage (for example 50%) it will be calculated over support material speed."; Options["support_material_interface_speed"].sidetext = "mm/s or %"; Options["support_material_interface_speed"].cli = "support-material-interface-speed=s"; + Options["support_material_interface_speed"].ratio_over = "support_material_speed"; Options["support_material_pattern"].type = coEnum; Options["support_material_pattern"].label = "Pattern"; From 254ab29a97e92045e065de213fd9ad750798b47e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 21 May 2014 20:08:21 +0200 Subject: [PATCH 23/48] New Point::projection_onto() methods --- xs/src/Point.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ xs/src/Point.hpp | 6 +++++ xs/t/03_point.t | 14 +++++++++++- xs/xsp/Point.xsp | 6 +++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/xs/src/Point.cpp b/xs/src/Point.cpp index 7849ca055..9a4d6cb51 100644 --- a/xs/src/Point.cpp +++ b/xs/src/Point.cpp @@ -1,5 +1,6 @@ #include "Point.hpp" #include "Line.hpp" +#include "MultiPoint.hpp" #include #include @@ -138,6 +139,63 @@ Point::ccw(const Line &line) const return this->ccw(line.a, line.b); } +Point +Point::projection_onto(const MultiPoint &poly) const +{ + Point running_projection = poly.first_point(); + double running_min = this->distance_to(running_projection); + + Lines lines = poly.lines(); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { + Point point_temp = this->projection_onto(*line); + if (this->distance_to(point_temp) < running_min) { + running_projection = point_temp; + running_min = this->distance_to(running_projection); + } + } + return running_projection; +} + +Point +Point::projection_onto(const Line &line) const +{ + if (line.a.coincides_with(line.b)) return line.a; + + /* + (Ported from VisiLibity by Karl J. Obermeyer) + The projection of point_temp onto the line determined by + line_segment_temp can be represented as an affine combination + expressed in the form projection of + Point = theta*line_segment_temp.first + (1.0-theta)*line_segment_temp.second. + If theta is outside the interval [0,1], then one of the Line_Segment's endpoints + must be closest to calling Point. + */ + double theta = ( (double)(line.b.x - this->x)*(double)(line.b.x - line.a.x) + (double)(line.b.y- this->y)*(double)(line.b.y - line.a.y) ) + / ( (double)pow(line.b.x - line.a.x, 2) + (double)pow(line.b.y - line.a.y, 2) ); + + if (0.0 <= theta && theta <= 1.0) + return theta * line.a + (1.0-theta) * line.b; + + // Else pick closest endpoint. + if (this->distance_to(line.a) < this->distance_to(line.b)) { + return line.a; + } else { + return line.b; + } +} + +Point +operator+(const Point& point1, const Point& point2) +{ + return Point(point1.x + point2.x, point1.y + point2.y); +} + +Point +operator*(double scalar, const Point& point2) +{ + return Point(scalar * point2.x, scalar * point2.y); +} + #ifdef SLIC3RXS REGISTER_CLASS(Point, "Point"); diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index 0d50bf376..330073a61 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -9,6 +9,7 @@ namespace Slic3r { class Line; +class MultiPoint; class Point; class Pointf; typedef Point Vector; @@ -37,6 +38,8 @@ class Point double distance_to(const Line &line) const; double ccw(const Point &p1, const Point &p2) const; double ccw(const Line &line) const; + Point projection_onto(const MultiPoint &poly) const; + Point projection_onto(const Line &line) const; #ifdef SLIC3RXS void from_SV(SV* point_sv); @@ -45,6 +48,9 @@ class Point #endif }; +Point operator+(const Point& point1, const Point& point2); +Point operator*(double scalar, const Point& point2); + class Point3 : public Point { public: diff --git a/xs/t/03_point.t b/xs/t/03_point.t index e1c88977f..44e99ad26 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 10; +use Test::More tests => 13; my $point = Slic3r::Point->new(10, 15); is_deeply [ @$point ], [10, 15], 'point roundtrip'; @@ -46,4 +46,16 @@ ok !$point->coincides_with($point2), 'coincides_with'; ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow'; } +{ + my $point = Slic3r::Point->new(15,15); + my $line = Slic3r::Line->new([10,10], [20,10]); + is_deeply $point->projection_onto_line($line)->pp, [15,10], 'project_onto_line'; + + $point = Slic3r::Point->new(0, 15); + is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; + + $point = Slic3r::Point->new(25, 15); + is_deeply $point->projection_onto_line($line)->pp, [20,10], 'project_onto_line'; +} + __END__ diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 4335c6eb8..12d1928b8 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -29,6 +29,12 @@ %code{% RETVAL = THIS->distance_to(*line); %}; double ccw(Point* p1, Point* p2) %code{% RETVAL = THIS->ccw(*p1, *p2); %}; + Clone projection_onto_polygon(Polygon* polygon) + %code{% RETVAL = new Point(THIS->projection_onto(*polygon)); %}; + Clone projection_onto_polyline(Polyline* polyline) + %code{% RETVAL = new Point(THIS->projection_onto(*polyline)); %}; + Clone projection_onto_line(Line* line) + %code{% RETVAL = new Point(THIS->projection_onto(*line)); %}; %{ From f2c5e799b109a30ed9caa5319d69c3692128d264 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 22 May 2014 12:28:12 +0200 Subject: [PATCH 24/48] Enforce seam alignment and blend in spiral vase. #2023 --- lib/Slic3r/GCode.pm | 6 ++- lib/Slic3r/GCode/SpiralVase.pm | 11 ++++-- xs/src/ExtrusionEntity.cpp | 39 +++++++++++++++++-- xs/src/ExtrusionEntity.hpp | 1 + xs/src/Point.cpp | 6 +++ xs/src/Point.hpp | 1 + xs/src/Polygon.cpp | 2 +- xs/src/Polygon.hpp | 2 +- xs/src/Polyline.cpp | 36 +++++++++++++++++ xs/src/Polyline.hpp | 1 + xs/t/03_point.t | 8 +++- xs/t/06_polygon.t | 2 +- xs/t/08_extrusionloop.t | 70 +++++++++++++++++++++++++--------- xs/t/09_polyline.t | 14 ++++++- xs/xsp/ExtrusionLoop.xsp | 2 + xs/xsp/Polygon.xsp | 4 +- xs/xsp/Polyline.xsp | 2 + 17 files changed, 174 insertions(+), 33 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index d3b026f19..5ad1cf1ea 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -185,7 +185,11 @@ sub extrude_loop { } # split the loop at the starting point - $loop->split_at($last_pos->nearest_point(\@candidates)); + if ($self->config->spiral_vase) { + $loop->split_at($last_pos); + } else { + $loop->split_at_vertex($last_pos->nearest_point(\@candidates)); + } # clip the path to avoid the extruder to get exactly on the first point of the loop; # if polyline was shorter than the clipping distance we'd get a null polyline, so diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm index 0f511a6cd..4c0f9a467 100644 --- a/lib/Slic3r/GCode/SpiralVase.pm +++ b/lib/Slic3r/GCode/SpiralVase.pm @@ -43,7 +43,7 @@ sub process_layer { } }); - #use XXX; YYY [ $gcode, $layer_height, $z, $total_layer_length ]; + #use XXX; XXX [ $gcode, $layer_height, $z, $total_layer_length ]; # remove layer height from initial Z $z -= $layer_height; @@ -57,16 +57,19 @@ sub process_layer { my $line = $info->{raw}; $line =~ s/ Z[.0-9]+/ Z$z/; $new_gcode .= "$line\n"; - } elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{dist_XY}) { + } elsif ($cmd eq 'G1' && !exists($args->{Z}) && $info->{dist_XY}) { # horizontal move my $line = $info->{raw}; if ($info->{extruding}) { $z += $info->{dist_XY} * $layer_height / $total_layer_length; $line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e; $new_gcode .= "$line\n"; - } else { - $new_gcode .= "$line\n"; } + # skip travel moves: the move to first perimeter point will + # cause a visible seam when loops are not aligned in XY; by skipping + # it we blend the first loop move in the XY plane (although the smoothness + # of such blend depend on how long the first segment is; maybe we should + # enforce some minimum length?) } else { $new_gcode .= "$info->{raw}\n"; } diff --git a/xs/src/ExtrusionEntity.cpp b/xs/src/ExtrusionEntity.cpp index 0abab7510..fd39fdc9f 100644 --- a/xs/src/ExtrusionEntity.cpp +++ b/xs/src/ExtrusionEntity.cpp @@ -221,7 +221,7 @@ ExtrusionLoop::length() const } void -ExtrusionLoop::split_at(const Point &point) +ExtrusionLoop::split_at_vertex(const Point &point) { for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) { int idx = path->polyline.find_point(point); @@ -239,7 +239,7 @@ ExtrusionLoop::split_at(const Point &point) { ExtrusionPath p = *path; p.polyline.points.erase(p.polyline.points.begin(), p.polyline.points.begin() + idx); - if (!p.polyline.points.empty()) new_paths.push_back(p); + if (p.polyline.is_valid()) new_paths.push_back(p); } // then we add all paths until the end of current path list @@ -252,7 +252,7 @@ ExtrusionLoop::split_at(const Point &point) { ExtrusionPath p = *path; p.polyline.points.erase(p.polyline.points.begin() + idx + 1, p.polyline.points.end()); - if (!p.polyline.points.empty()) new_paths.push_back(p); + if (p.polyline.is_valid()) new_paths.push_back(p); } // we can now override the old path list with the new one and stop looping this->paths = new_paths; @@ -263,6 +263,39 @@ ExtrusionLoop::split_at(const Point &point) CONFESS("Point not found"); } +void +ExtrusionLoop::split_at(const Point &point) +{ + if (this->paths.empty()) return; + + // find the closest path and closest point + size_t path_idx = 0; + Point p = this->paths.front().first_point(); + double min = point.distance_to(p); + for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { + Point p_tmp = point.projection_onto(path->polyline); + double dist = point.distance_to(p_tmp); + if (dist < min) { + p = p_tmp; + min = dist; + path_idx = path - this->paths.begin(); + } + } + + // now split path_idx in two parts + ExtrusionPath p1 = this->paths[path_idx]; + ExtrusionPath p2 = p1; + this->paths[path_idx].polyline.split_at(p, &p1.polyline, &p2.polyline); + + // install the two paths + this->paths.erase(this->paths.begin() + path_idx); + if (p2.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p2); + if (p1.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p1); + + // split at the new vertex + this->split_at_vertex(p); +} + void ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const { diff --git a/xs/src/ExtrusionEntity.hpp b/xs/src/ExtrusionEntity.hpp index c1cffa612..ccd85a689 100644 --- a/xs/src/ExtrusionEntity.hpp +++ b/xs/src/ExtrusionEntity.hpp @@ -93,6 +93,7 @@ class ExtrusionLoop : public ExtrusionEntity Point last_point() const; void polygon(Polygon* polygon) const; double length() const; + void split_at_vertex(const Point &point); void split_at(const Point &point); void clip_end(double distance, ExtrusionPaths* paths) const; bool has_overhang_point(const Point &point) const; diff --git a/xs/src/Point.cpp b/xs/src/Point.cpp index 9a4d6cb51..7d0013e22 100644 --- a/xs/src/Point.cpp +++ b/xs/src/Point.cpp @@ -6,6 +6,12 @@ namespace Slic3r { +Point::Point(double x, double y) +{ + this->x = lrint(x); + this->y = lrint(y); +} + bool Point::operator==(const Point& rhs) const { diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index 330073a61..39e9979d4 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -24,6 +24,7 @@ class Point coord_t x; coord_t y; explicit Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {}; + Point(double x, double y); bool operator==(const Point& rhs) const; std::string wkt() const; void scale(double factor); diff --git a/xs/src/Polygon.cpp b/xs/src/Polygon.cpp index 303d6fd75..948ea2301 100644 --- a/xs/src/Polygon.cpp +++ b/xs/src/Polygon.cpp @@ -56,7 +56,7 @@ Polygon::lines(Lines* lines) const } void -Polygon::split_at(const Point &point, Polyline* polyline) const +Polygon::split_at_vertex(const Point &point, Polyline* polyline) const { // find index of point for (Points::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { diff --git a/xs/src/Polygon.hpp b/xs/src/Polygon.hpp index 4761cf566..b5859e821 100644 --- a/xs/src/Polygon.hpp +++ b/xs/src/Polygon.hpp @@ -21,7 +21,7 @@ class Polygon : public MultiPoint { Point last_point() const; Lines lines() const; void lines(Lines* lines) const; - void split_at(const Point &point, Polyline* polyline) const; + void split_at_vertex(const Point &point, Polyline* polyline) const; void split_at_index(int index, Polyline* polyline) const; void split_at_first_point(Polyline* polyline) const; void equally_spaced_points(double distance, Points* points) const; diff --git a/xs/src/Polyline.cpp b/xs/src/Polyline.cpp index 63778d5a5..9d49c23e1 100644 --- a/xs/src/Polyline.cpp +++ b/xs/src/Polyline.cpp @@ -117,6 +117,42 @@ Polyline::simplify(double tolerance) this->points = MultiPoint::_douglas_peucker(this->points, tolerance); } +void +Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const +{ + if (this->points.empty()) return; + + // find the line to split at + size_t line_idx = 0; + Point p = this->first_point(); + double min = point.distance_to(p); + Lines lines = this->lines(); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { + Point p_tmp = point.projection_onto(*line); + if (point.distance_to(p_tmp) < min) { + p = p_tmp; + min = point.distance_to(p); + line_idx = line - lines.begin(); + } + } + + // create first half + p1->points.clear(); + for (Lines::const_iterator line = lines.begin(); line != lines.begin() + line_idx + 1; ++line) { + if (!line->a.coincides_with(p)) p1->points.push_back(line->a); + } + // we add point instead of p because they might differ because of numerical issues + // and caller might want to rely on point belonging to result polylines + p1->points.push_back(point); + + // create second half + 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); + } +} + #ifdef SLIC3RXS REGISTER_CLASS(Polyline, "Polyline"); diff --git a/xs/src/Polyline.hpp b/xs/src/Polyline.hpp index 829beeaf7..5462425cf 100644 --- a/xs/src/Polyline.hpp +++ b/xs/src/Polyline.hpp @@ -21,6 +21,7 @@ class Polyline : public MultiPoint { void extend_start(double distance); void equally_spaced_points(double distance, Points* points) const; void simplify(double tolerance); + void split_at(const Point &point, Polyline* p1, Polyline* p2) const; #ifdef SLIC3RXS void from_SV_check(SV* poly_sv); diff --git a/xs/t/03_point.t b/xs/t/03_point.t index 44e99ad26..0ec9df552 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 13; +use Test::More tests => 15; my $point = Slic3r::Point->new(10, 15); is_deeply [ @$point ], [10, 15], 'point roundtrip'; @@ -56,6 +56,12 @@ ok !$point->coincides_with($point2), 'coincides_with'; $point = Slic3r::Point->new(25, 15); is_deeply $point->projection_onto_line($line)->pp, [20,10], 'project_onto_line'; + + $point = Slic3r::Point->new(10,10); + is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; + + $point = Slic3r::Point->new(12, 10); + is_deeply $point->projection_onto_line($line)->pp, [12,10], 'project_onto_line'; } __END__ diff --git a/xs/t/06_polygon.t b/xs/t/06_polygon.t index f58aa539d..858638889 100644 --- a/xs/t/06_polygon.t +++ b/xs/t/06_polygon.t @@ -36,7 +36,7 @@ is_deeply [ map $_->pp, @$lines ], [ is_deeply $polygon->split_at_first_point->pp, [ @$square[0,1,2,3,0] ], 'split_at_first_point'; is_deeply $polygon->split_at_index(2)->pp, [ @$square[2,3,0,1,2] ], 'split_at_index'; -is_deeply $polygon->split_at(Slic3r::Point->new(@{$square->[2]}))->pp, [ @$square[2,3,0,1,2] ], 'split_at'; +is_deeply $polygon->split_at_vertex(Slic3r::Point->new(@{$square->[2]}))->pp, [ @$square[2,3,0,1,2] ], 'split_at'; is $polygon->area, 100*100, 'area'; ok $polygon->is_counter_clockwise, 'is_counter_clockwise'; diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t index 0eff87c77..dd657d156 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 => 30; +use Test::More tests => 45; { my $square = [ @@ -39,7 +39,7 @@ use Test::More tests => 30; is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_FILL, 'modify role'; } - $loop->split_at($square_p->[2]); + $loop->split_at_vertex($square_p->[2]); is scalar(@$loop), 1, 'splitting a single-path loop results in a single path'; is scalar(@{$loop->[0]->polyline}), 5, 'path has correct number of points'; ok $loop->[0]->polyline->[0]->coincides_with($square_p->[2]), 'expected point order'; @@ -65,24 +65,58 @@ use Test::More tests => 30; mm3_per_mm => 1, ), ); - is $loop->length, sum($polyline1->length, $polyline2->length), 'length'; + my $tot_len = sum($polyline1->length, $polyline2->length); + is $loop->length, $tot_len, 'length'; is scalar(@$loop), 2, 'loop contains two paths'; - $loop->split_at($polyline1->[1]); - is $loop->length, sum($polyline1->length, $polyline2->length), 'length after splitting'; - is scalar(@$loop), 3, 'loop contains three paths after splitting'; - ok $loop->[0]->polyline->[0]->coincides_with($polyline1->[1]), 'expected starting point'; - ok $loop->[-1]->polyline->[-1]->coincides_with($polyline1->[1]), 'expected ending point'; - ok $loop->[0]->polyline->[-1]->coincides_with($loop->[1]->polyline->[0]), 'paths have common point'; - ok $loop->[1]->polyline->[-1]->coincides_with($loop->[2]->polyline->[0]), 'paths have common point'; - is $loop->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; - is $loop->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; - is $loop->[2]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; - is scalar(@{$loop->[0]->polyline}), 2, 'path has correct number of points'; - is scalar(@{$loop->[1]->polyline}), 3, 'path has correct number of points'; - is scalar(@{$loop->[2]->polyline}), 2, 'path has correct number of points'; - my @paths = @{$loop->clip_end(3)}; - is sum(map $_->length, @paths), $loop->length - 3, 'returned paths have expected length'; + { + # check splitting at intermediate point + my $loop2 = $loop->clone; + isa_ok $loop2, 'Slic3r::ExtrusionLoop'; + $loop2->split_at_vertex($polyline1->[1]); + is $loop2->length, $tot_len, 'length after splitting is unchanged'; + is scalar(@$loop2), 3, 'loop contains three paths after splitting'; + ok $loop2->[0]->polyline->[0]->coincides_with($polyline1->[1]), 'expected starting point'; + ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline1->[1]), 'expected ending point'; + ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; + ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[2]->polyline->[0]), 'paths have common point'; + is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; + is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; + is $loop2->[2]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; + is scalar(@{$loop2->[0]->polyline}), 2, 'path has correct number of points'; + is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; + is scalar(@{$loop2->[2]->polyline}), 2, 'path has correct number of points'; + + my @paths = @{$loop2->clip_end(3)}; + is sum(map $_->length, @paths), $loop2->length - 3, 'returned paths have expected length'; + } + + { + # check splitting at endpoint + my $loop2 = $loop->clone; + $loop2->split_at_vertex($polyline2->[0]); + is $loop2->length, $tot_len, 'length after splitting is unchanged'; + is scalar(@$loop2), 2, 'loop contains two paths after splitting'; + ok $loop2->[0]->polyline->[0]->coincides_with($polyline2->[0]), 'expected starting point'; + ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline2->[0]), 'expected ending point'; + ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; + ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[0]->polyline->[0]), 'paths have common point'; + is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; + is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; + is scalar(@{$loop2->[0]->polyline}), 3, 'path has correct number of points'; + is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; + } + + { + my $loop2 = $loop->clone; + my $point = Slic3r::Point->new(250,150); + $loop2->split_at($point); + is $loop2->length, $tot_len, 'length after splitting is unchanged'; + is scalar(@$loop2), 3, 'loop contains three paths after splitting'; + my $expected_start_point = Slic3r::Point->new(200,150); + ok $loop2->[0]->polyline->[0]->coincides_with($expected_start_point), 'expected starting point'; + ok $loop2->[-1]->polyline->[-1]->coincides_with($expected_start_point), 'expected ending point'; + } } __END__ diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t index 4f7c21941..5ce8d87cb 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 => 10; +use Test::More tests => 14; my $points = [ [100, 100], @@ -51,4 +51,16 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; is $polyline->length, 100*2 + 50 + 50, 'extend_start'; } +{ + my $polyline = Slic3r::Polyline->new(@$points); + my $p1 = Slic3r::Polyline->new; + my $p2 = Slic3r::Polyline->new; + my $point = Slic3r::Point->new(150, 100); + $polyline->split_at($point, $p1, $p2); + is scalar(@$p1), 2, 'split_at'; + is scalar(@$p2), 3, 'split_at'; + ok $p1->last_point->coincides_with($point), 'split_at'; + ok $p2->first_point->coincides_with($point), 'split_at'; +} + __END__ diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp index 3ab469bc5..c6f0db1dc 100644 --- a/xs/xsp/ExtrusionLoop.xsp +++ b/xs/xsp/ExtrusionLoop.xsp @@ -20,6 +20,8 @@ void append(ExtrusionPath* path) %code{% THIS->paths.push_back(*path); %}; double length(); + void split_at_vertex(Point* point) + %code{% THIS->split_at_vertex(*point); %}; void split_at(Point* point) %code{% THIS->split_at(*point); %}; ExtrusionPaths clip_end(double distance) diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index f74d5e2da..c7b10846f 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -17,8 +17,8 @@ void translate(double x, double y); void reverse(); Lines lines(); - Polyline* split_at(Point* point) - %code{% RETVAL = new Polyline(); THIS->split_at(*point, RETVAL); %}; + Polyline* split_at_vertex(Point* point) + %code{% RETVAL = new Polyline(); THIS->split_at_vertex(*point, RETVAL); %}; Polyline* split_at_index(int index) %code{% RETVAL = new Polyline(); THIS->split_at_index(index, RETVAL); %}; Polyline* split_at_first_point() diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index fb2530307..31f1fd222 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -31,6 +31,8 @@ void extend_end(double distance); void extend_start(double distance); void simplify(double tolerance); + void split_at(Point* point, Polyline* p1, Polyline* p2) + %code{% THIS->split_at(*point, p1, p2); %}; %{ Polyline* From c63bd8165d3707f06bd2ab9bbbb82df95e4c2bc7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 22 May 2014 13:47:30 +0200 Subject: [PATCH 25/48] Fixed minor compilation issue --- xs/src/Point.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index 39e9979d4..b83a2a238 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -23,7 +23,9 @@ class Point public: coord_t x; coord_t y; - explicit Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {}; + Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {}; + Point(int _x, int _y): x(_x), y(_y) {}; + Point(long long _x, long long _y): x(_x), y(_y) {}; // for Clipper Point(double x, double y); bool operator==(const Point& rhs) const; std::string wkt() const; From a3bd1b53024bb5fe2564a08ef8f9a9f99f11c5b8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 22 May 2014 19:34:49 +0200 Subject: [PATCH 26/48] New seal_position option that replaces randomize_start, start_perimeters_at_concave_points and start_perimeters_at_non_overhang. The two latter options are now always on by default. A new "Aligned" seal position value has been added, that forces starting points to be aligned when not randomized. #1741 #925 --- README.md | 6 +--- lib/Slic3r/Config.pm | 7 ++++- lib/Slic3r/GCode.pm | 61 +++++++++++++++++--------------------- lib/Slic3r/GUI/Tab.pm | 8 ++--- lib/Slic3r/Layer/Region.pm | 11 +++++-- lib/Slic3r/Polygon.pm | 29 ++++++++++++++---- lib/Slic3r/Polyline.pm | 2 +- slic3r.pl | 6 +--- t/perimeters.t | 28 ++--------------- xs/src/MultiPoint.cpp | 12 ++++++++ xs/src/MultiPoint.hpp | 10 +++++-- xs/src/Polygon.cpp | 18 +++++++++++ xs/src/Polygon.hpp | 1 + xs/src/PrintConfig.hpp | 52 +++++++++++++++++--------------- xs/t/06_polygon.t | 6 +++- xs/xsp/ExtrusionLoop.xsp | 2 +- xs/xsp/Polygon.xsp | 6 ++++ 17 files changed, 153 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index d471f6f16..13bd1300d 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ The author of the Silk icon set is Mark James. home X axis [G28 X], disable motors [M84]). --layer-gcode Load layer-change G-code from the supplied file (default: nothing). --toolchange-gcode Load tool-change G-code from the supplied file (default: nothing). - --randomize-start Randomize starting point across layers (default: yes) + --seal-position Position of loop starting points (random/nearest/aligned, default: aligned). --external-perimeters-first Reverse perimeter order. (default: no) --spiral-vase Experimental option to raise Z gradually when printing single-walled vases (default: no) @@ -236,10 +236,6 @@ The author of the Silk icon set is Mark James. Quality options (slower slicing): --extra-perimeters Add more perimeters when needed (default: yes) --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no) - --start-perimeters-at-concave-points - Try to start perimeters at concave points if any (default: no) - --start-perimeters-at-non-overhang - Try to start perimeters at non-overhang points if any (default: no) --thin-walls Detect single-width walls (default: yes) --overhangs Experimental option to use bridge flow, speed and fan for overhangs (default: yes) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 92a63a61a..625e1f8fd 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -8,7 +8,8 @@ use List::Util qw(first max); # cemetery of old config settings our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid - rotate scale duplicate_grid); + rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang + randomize_start); our $Options = print_config_def(); @@ -140,6 +141,10 @@ sub _handle_legacy { $value *= 100; $value = "$value"; # force update of the PV value, workaround for bug https://rt.cpan.org/Ticket/Display.html?id=94110 } + if ($opt_key eq 'randomize_start' && $value) { + $opt_key = 'seal_position'; + $value = 'random'; + } # For historical reasons, the world's full of configs having these very low values; # to avoid unexpected behavior we need to ignore them. Banning these two hard-coded diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 5ad1cf1ea..73a787d51 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -19,6 +19,7 @@ has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter has 'layer' => (is => 'rw'); has '_layer_islands' => (is => 'rw'); has '_upper_layer_islands' => (is => 'rw'); +has '_seal_position' => (is => 'ro', default => sub { {} }); # $object => pos has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); @@ -150,45 +151,37 @@ sub extrude_loop { # extrude all loops ccw my $was_clockwise = $loop->make_counter_clockwise; - # find candidate starting points - # start looking for concave vertices not being overhangs - my $polygon = $loop->polygon; - my @concave = (); - if ($self->config->start_perimeters_at_concave_points) { - @concave = $polygon->concave_points; - } - my @candidates = (); - if ($self->config->start_perimeters_at_non_overhang) { - @candidates = grep !$loop->has_overhang_point($_), @concave; - } - if (!@candidates) { - # if none, look for any concave vertex - @candidates = @concave; - if (!@candidates) { - # if none, look for any non-overhang vertex - if ($self->config->start_perimeters_at_non_overhang) { - @candidates = grep !$loop->has_overhang_point($_), @$polygon; - } - if (!@candidates) { - # if none, all points are valid candidates - @candidates = @$polygon; - } - } - } - # find the point of the loop that is closest to the current extruder position # or randomize if requested my $last_pos = $self->last_pos; - if ($self->config->randomize_start && $loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { - $last_pos = Slic3r::Point->new(scale $self->config->print_center->[X], scale $self->config->bed_size->[Y]); - $last_pos->rotate(rand(2*PI), $self->config->print_center); - } - - # split the loop at the starting point if ($self->config->spiral_vase) { $loop->split_at($last_pos); - } else { - $loop->split_at_vertex($last_pos->nearest_point(\@candidates)); + } elsif ($self->config->seal_position eq 'nearest' || $self->config->seal_position eq 'aligned') { + my $polygon = $loop->polygon; + my @candidates = @{$polygon->concave_points(PI*4/3)}; + @candidates = @{$polygon->convex_points(PI*2/3)} if !@candidates; + @candidates = @{$polygon} if !@candidates; + + my @non_overhang = grep !$loop->has_overhang_point($_), @candidates; + @candidates = @non_overhang if @non_overhang; + + if ($self->config->seal_position eq 'nearest') { + $loop->split_at_vertex($last_pos->nearest_point(\@candidates)); + } elsif ($self->config->seal_position eq 'aligned') { + if (defined $self->layer && defined $self->_seal_position->{$self->layer->object}) { + $last_pos = $self->_seal_position->{$self->layer->object}; + } + my $point = $self->_seal_position->{$self->layer->object} = $last_pos->nearest_point(\@candidates); + $loop->split_at_vertex($point); + } + } elsif ($self->config->seal_position eq 'random') { + if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { + my $polygon = $loop->polygon; + my $centroid = $polygon->centroid; + $last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y); #)) + $last_pos->rotate(rand(2*PI), $centroid); + } + $loop->split_at($last_pos); } # clip the path to avoid the extruder to get exactly on the first point of the loop; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 9f68b2cd1..1d4945f01 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -417,21 +417,17 @@ sub build { }, { title => 'Quality (slower slicing)', - options => [qw(extra_perimeters avoid_crossing_perimeters start_perimeters_at_concave_points start_perimeters_at_non_overhang thin_walls overhangs)], + options => [qw(extra_perimeters avoid_crossing_perimeters thin_walls overhangs)], lines => [ Slic3r::GUI::OptionsGroup->single_option_line('extra_perimeters'), Slic3r::GUI::OptionsGroup->single_option_line('avoid_crossing_perimeters'), - { - label => 'Start perimeters at', - options => [qw(start_perimeters_at_concave_points start_perimeters_at_non_overhang)], - }, Slic3r::GUI::OptionsGroup->single_option_line('thin_walls'), Slic3r::GUI::OptionsGroup->single_option_line('overhangs'), ], }, { title => 'Advanced', - options => [qw(randomize_start external_perimeters_first)], + options => [qw(seal_position external_perimeters_first)], }, ]); diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 9a3f17c2a..39cec3d61 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -260,12 +260,19 @@ sub make_perimeters { my $role = EXTR_ROLE_PERIMETER; my $loop_role = EXTRL_ROLE_DEFAULT; - if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) { + + 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_external) { # external perimeters are root level in case of contours # and items with no children in case of holes $role = EXTR_ROLE_EXTERNAL_PERIMETER; $loop_role = EXTRL_ROLE_EXTERNAL_PERIMETER; - } elsif ($depth == 1 && $is_contour) { + } elsif ($is_contour && $is_internal) { + # internal perimeters are root level in case of holes + # and items with no children in case of contours $loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER; } diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index ee0ec96b9..c12d3dcaf 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -40,15 +40,34 @@ sub subdivide { return Slic3r::Polygon->new(@new_points); } -# for cw polygons this will return convex points! +# angle is checked on the internal side of the polygon sub concave_points { - my $self = shift; + my ($self, $angle) = @_; + + $angle //= PI; my @points = @$self; my @points_pp = @{$self->pp}; - return map $points[$_], - grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) < PI - epsilon, - -1 .. ($#points-1); + return [ + map $points[$_], + grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) < $angle, + -1 .. ($#points-1) + ]; +} + +# angle is checked on the internal side of the polygon +sub convex_points { + my ($self, $angle) = @_; + + $angle //= PI; + + my @points = @$self; + my @points_pp = @{$self->pp}; + return [ + map $points[$_], + grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) > $angle, + -1 .. ($#points-1) + ]; } 1; \ No newline at end of file diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index eeda93047..4e3045cdf 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -3,7 +3,7 @@ use strict; use warnings; use List::Util qw(first); -use Slic3r::Geometry qw(X Y epsilon); +use Slic3r::Geometry qw(X Y PI epsilon); use Slic3r::Geometry::Clipper qw(JT_SQUARE); sub new_scale { diff --git a/slic3r.pl b/slic3r.pl index 938f4ed93..aa3a43700 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -342,7 +342,7 @@ $j home X axis [G28 X], disable motors [M84]). --layer-gcode Load layer-change G-code from the supplied file (default: nothing). --toolchange-gcode Load tool-change G-code from the supplied file (default: nothing). - --randomize-start Randomize starting point across layers (default: yes) + --seal-position Position of loop starting points (random/nearest/aligned, default: $config->{seal_position}). --external-perimeters-first Reverse perimeter order. (default: no) --spiral-vase Experimental option to raise Z gradually when printing single-walled vases (default: no) @@ -359,10 +359,6 @@ $j Quality options (slower slicing): --extra-perimeters Add more perimeters when needed (default: yes) --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no) - --start-perimeters-at-concave-points - Try to start perimeters at concave points if any (default: no) - --start-perimeters-at-non-overhang - Try to start perimeters at non-overhang points if any (default: no) --thin-walls Detect single-width walls (default: yes) --overhangs Experimental option to use bridge flow, speed and fan for overhangs (default: yes) diff --git a/t/perimeters.t b/t/perimeters.t index 58ebc3ed1..239f80f2f 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -1,4 +1,4 @@ -use Test::More tests => 10; +use Test::More tests => 9; use strict; use warnings; @@ -85,28 +85,6 @@ use Slic3r::Test; ok !$has_outwards_move, 'move inwards after completing external loop'; } - { - $config->set('start_perimeters_at_concave_points', 1); - my $print = Slic3r::Test::init_print('L', config => $config); - my $loop_starts_from_convex_point = 0; - my $cur_loop; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - $cur_loop ||= [ [$self->X, $self->Y] ]; - push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; - } else { - if ($cur_loop) { - $loop_starts_from_convex_point = 1 - if Slic3r::Geometry::angle3points(@$cur_loop[0,-1,1]) >= PI; - $cur_loop = undef; - } - } - }); - ok !$loop_starts_from_convex_point, 'avoid starting from convex points'; - } - { $config->set('perimeters', 1); $config->set('perimeter_speed', 77); @@ -267,9 +245,9 @@ use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; - $config->set('randomize_start', 1); + $config->set('seal_position', 'random'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'successful generation of G-code with randomize_start option'; + ok Slic3r::Test::gcode($print), 'successful generation of G-code with seal_position = random'; } __END__ diff --git a/xs/src/MultiPoint.cpp b/xs/src/MultiPoint.cpp index 51567c5e9..47830ce2f 100644 --- a/xs/src/MultiPoint.cpp +++ b/xs/src/MultiPoint.cpp @@ -1,7 +1,13 @@ #include "MultiPoint.hpp" +#include "BoundingBox.hpp" namespace Slic3r { +MultiPoint::operator Points() const +{ + return this->points; +} + void MultiPoint::scale(double factor) { @@ -64,6 +70,12 @@ MultiPoint::find_point(const Point &point) const return -1; // not found } +void +MultiPoint::bounding_box(BoundingBox* bb) const +{ + *bb = BoundingBox(this->points); +} + Points MultiPoint::_douglas_peucker(const Points &points, const double tolerance) { diff --git a/xs/src/MultiPoint.hpp b/xs/src/MultiPoint.hpp index c1278aa75..075b0dbdb 100644 --- a/xs/src/MultiPoint.hpp +++ b/xs/src/MultiPoint.hpp @@ -2,17 +2,21 @@ #define slic3r_MultiPoint_hpp_ #include -#include "Line.hpp" -#include "Point.hpp" #include #include +#include "Line.hpp" +#include "Point.hpp" namespace Slic3r { +class BoundingBox; + class MultiPoint { public: Points points; + + operator Points() const; void scale(double factor); void translate(double x, double y); void rotate(double angle, const Point ¢er); @@ -23,6 +27,8 @@ class MultiPoint double length() const; bool is_valid() const; int find_point(const Point &point) const; + void bounding_box(BoundingBox* bb) const; + static Points _douglas_peucker(const Points &points, const double tolerance); #ifdef SLIC3RXS diff --git a/xs/src/Polygon.cpp b/xs/src/Polygon.cpp index 948ea2301..3fe16bc70 100644 --- a/xs/src/Polygon.cpp +++ b/xs/src/Polygon.cpp @@ -191,6 +191,24 @@ Polygon::triangulate_convex(Polygons* polygons) const } } +// center of mass +Point +Polygon::centroid() const +{ + double area_temp = this->area(); + double x_temp = 0; + double y_temp = 0; + + Polyline polyline; + this->split_at_first_point(&polyline); + for (Points::const_iterator point = polyline.points.begin(); point != polyline.points.end() - 1; ++point) { + x_temp += (double)( point->x + (point+1)->x ) * ( (double)point->x*(point+1)->y - (double)(point+1)->x*point->y ); + y_temp += (double)( point->y + (point+1)->y ) * ( (double)point->x*(point+1)->y - (double)(point+1)->x*point->y ); + } + + return Point(x_temp/(6*area_temp), y_temp/(6*area_temp)); +} + #ifdef SLIC3RXS REGISTER_CLASS(Polygon, "Polygon"); diff --git a/xs/src/Polygon.hpp b/xs/src/Polygon.hpp index b5859e821..816b6be18 100644 --- a/xs/src/Polygon.hpp +++ b/xs/src/Polygon.hpp @@ -35,6 +35,7 @@ class Polygon : public MultiPoint { Polygons simplify(double tolerance) const; void simplify(double tolerance, Polygons &polygons) const; void triangulate_convex(Polygons* polygons) const; + Point centroid() const; #ifdef SLIC3RXS void from_SV_check(SV* poly_sv); diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index d3ced798a..913d81de2 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -18,6 +18,10 @@ enum SupportMaterialPattern { smpRectilinear, smpRectilinearGrid, smpHoneycomb, smpPillars, }; +enum SealPosition { + spRandom, spNearest, spAligned +}; + template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["reprap"] = gcfRepRap; @@ -50,6 +54,14 @@ template<> inline t_config_enum_values ConfigOptionEnum: return keys_map; } +template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { + t_config_enum_values keys_map; + keys_map["random"] = spRandom; + keys_map["nearest"] = spNearest; + keys_map["aligned"] = spAligned; + return keys_map; +} + class PrintConfigDef { public: @@ -584,11 +596,6 @@ class PrintConfigDef Options["raft_layers"].sidetext = "layers"; Options["raft_layers"].cli = "raft-layers=i"; - Options["randomize_start"].type = coBool; - Options["randomize_start"].label = "Randomize starting points"; - Options["randomize_start"].tooltip = "Start each layer from a different vertex to prevent plastic build-up on the same corner."; - Options["randomize_start"].cli = "randomize-start!"; - Options["resolution"].type = coFloat; Options["resolution"].label = "Resolution"; Options["resolution"].tooltip = "Minimum detail resolution, used to simplify the input file for speeding up the slicing job and reducing memory usage. High-resolution models often carry more detail than printers can render. Set to zero to disable any simplification and use full resolution from input."; @@ -644,6 +651,19 @@ class PrintConfigDef Options["retract_speed"].cli = "retract-speed=f@"; Options["retract_speed"].max = 1000; + Options["seal_position"].type = coEnum; + Options["seal_position"].label = "Seal position"; + Options["seal_position"].category = "Layers and perimeters"; + Options["seal_position"].tooltip = "Position of perimeters starting points."; + Options["seal_position"].cli = "seal-position=s"; + Options["seal_position"].enum_keys_map = ConfigOptionEnum::get_enum_values(); + Options["seal_position"].enum_values.push_back("random"); + Options["seal_position"].enum_values.push_back("nearest"); + Options["seal_position"].enum_values.push_back("aligned"); + Options["seal_position"].enum_labels.push_back("Random"); + Options["seal_position"].enum_labels.push_back("Nearest"); + Options["seal_position"].enum_labels.push_back("Aligned"); + Options["skirt_distance"].type = coFloat; Options["skirt_distance"].label = "Distance from object"; Options["skirt_distance"].tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion."; @@ -753,16 +773,6 @@ class PrintConfigDef Options["start_gcode"].full_width = true; Options["start_gcode"].height = 120; - Options["start_perimeters_at_concave_points"].type = coBool; - Options["start_perimeters_at_concave_points"].label = "Concave points"; - Options["start_perimeters_at_concave_points"].tooltip = "Prefer to start perimeters at a concave point."; - Options["start_perimeters_at_concave_points"].cli = "start-perimeters-at-concave-points!"; - - Options["start_perimeters_at_non_overhang"].type = coBool; - Options["start_perimeters_at_non_overhang"].label = "Non-overhang points"; - Options["start_perimeters_at_non_overhang"].tooltip = "Prefer to start perimeters at non-overhanging points."; - Options["start_perimeters_at_non_overhang"].cli = "start-perimeters-at-non-overhang!"; - Options["support_material"].type = coBool; Options["support_material"].label = "Generate support material"; Options["support_material"].category = "Support material"; @@ -996,6 +1006,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig ConfigOptionBool interface_shells; ConfigOptionFloat layer_height; ConfigOptionInt raft_layers; + ConfigOptionEnum seal_position; ConfigOptionBool support_material; ConfigOptionInt support_material_angle; ConfigOptionInt support_material_enforce_layers; @@ -1020,6 +1031,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig this->interface_shells.value = false; this->layer_height.value = 0.4; this->raft_layers.value = 0; + this->seal_position.value = spAligned; this->support_material.value = false; this->support_material_angle.value = 0; this->support_material_enforce_layers.value = 0; @@ -1045,6 +1057,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig if (opt_key == "interface_shells") return &this->interface_shells; if (opt_key == "layer_height") return &this->layer_height; if (opt_key == "raft_layers") return &this->raft_layers; + if (opt_key == "seal_position") return &this->seal_position; if (opt_key == "support_material") return &this->support_material; if (opt_key == "support_material_angle") return &this->support_material_angle; if (opt_key == "support_material_enforce_layers") return &this->support_material_enforce_layers; @@ -1214,7 +1227,6 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionFloat perimeter_acceleration; ConfigOptionStrings post_process; ConfigOptionPoint print_center; - ConfigOptionBool randomize_start; ConfigOptionFloat resolution; ConfigOptionFloats retract_before_travel; ConfigOptionBools retract_layer_change; @@ -1231,8 +1243,6 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOptionBool spiral_vase; ConfigOptionInt standby_temperature_delta; ConfigOptionString start_gcode; - ConfigOptionBool start_perimeters_at_concave_points; - ConfigOptionBool start_perimeters_at_non_overhang; ConfigOptionInts temperature; ConfigOptionInt threads; ConfigOptionString toolchange_gcode; @@ -1296,7 +1306,6 @@ class PrintConfig : public virtual StaticPrintConfig this->output_filename_format.value = "[input_filename_base].gcode"; this->perimeter_acceleration.value = 0; this->print_center.point = Pointf(100,100); - this->randomize_start.value = false; this->resolution.value = 0; this->retract_before_travel.values.resize(1); this->retract_before_travel.values[0] = 2; @@ -1321,8 +1330,6 @@ class PrintConfig : public virtual StaticPrintConfig 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->start_perimeters_at_concave_points.value = false; - this->start_perimeters_at_non_overhang.value = false; this->temperature.values.resize(1); this->temperature.values[0] = 200; this->threads.value = 2; @@ -1383,7 +1390,6 @@ class PrintConfig : public virtual StaticPrintConfig if (opt_key == "perimeter_acceleration") return &this->perimeter_acceleration; if (opt_key == "post_process") return &this->post_process; if (opt_key == "print_center") return &this->print_center; - if (opt_key == "randomize_start") return &this->randomize_start; if (opt_key == "resolution") return &this->resolution; if (opt_key == "retract_before_travel") return &this->retract_before_travel; if (opt_key == "retract_layer_change") return &this->retract_layer_change; @@ -1400,8 +1406,6 @@ class PrintConfig : public virtual StaticPrintConfig 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 == "start_perimeters_at_concave_points") return &this->start_perimeters_at_concave_points; - if (opt_key == "start_perimeters_at_non_overhang") return &this->start_perimeters_at_non_overhang; if (opt_key == "temperature") return &this->temperature; if (opt_key == "threads") return &this->threads; if (opt_key == "toolchange_gcode") return &this->toolchange_gcode; diff --git a/xs/t/06_polygon.t b/xs/t/06_polygon.t index 858638889..116919a74 100644 --- a/xs/t/06_polygon.t +++ b/xs/t/06_polygon.t @@ -5,7 +5,7 @@ use warnings; use List::Util qw(first); use Slic3r::XS; -use Test::More tests => 19; +use Test::More tests => 20; use constant PI => 4 * atan2(1, 1); @@ -69,6 +69,10 @@ ok $cw_polygon->contains_point(Slic3r::Point->new(150,150)), 'cw contains_point' ok !(defined first { $_->is_clockwise } @$triangles), 'all triangles are ccw'; } +{ + is_deeply $polygon->centroid->pp, [150,150], 'centroid'; +} + # this is not a test: this just demonstrates bad usage, where $polygon->clone gets # DESTROY'ed before the derived object ($point), causing bad memory access if (0) { diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp index c6f0db1dc..489bca03a 100644 --- a/xs/xsp/ExtrusionLoop.xsp +++ b/xs/xsp/ExtrusionLoop.xsp @@ -64,7 +64,7 @@ _constant() ALIAS: EXTRL_ROLE_DEFAULT = elrDefault EXTRL_ROLE_EXTERNAL_PERIMETER = elrExternalPerimeter - EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER = erInternalInfill + EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER = elrContourInternalPerimeter PROTOTYPE: CODE: RETVAL = ix; diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index c7b10846f..11fd04360 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -38,6 +38,12 @@ Polygons simplify(double tolerance); Polygons triangulate_convex() %code{% THIS->triangulate_convex(&RETVAL); %}; + Clone centroid(); + BoundingBox* bounding_box() + %code{% + RETVAL = new BoundingBox(); + THIS->bounding_box(RETVAL); + %}; %{ Polygon* From 70ceb853f1bc227859fbeba330e3993ab658f0f1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 23 May 2014 23:58:43 +0200 Subject: [PATCH 27/48] Update Clipper to last trunk to fix a couple Clipper bugs causing empty intersection results and failure to process polylines with coinciding endpoints. This also caused crashed in some rare circumstances --- xs/src/ClipperUtils.cpp | 31 +-- xs/src/clipper.cpp | 524 +++++++++++++++------------------------- xs/src/clipper.hpp | 29 +-- 3 files changed, 205 insertions(+), 379 deletions(-) diff --git a/xs/src/ClipperUtils.cpp b/xs/src/ClipperUtils.cpp index e26b159da..2989783ee 100644 --- a/xs/src/ClipperUtils.cpp +++ b/xs/src/ClipperUtils.cpp @@ -339,43 +339,14 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_) { - - /* Clipper will remove a polyline segment if first point coincides with last one. - Until that bug is not fixed upstream, we move one of those points slightly. */ - Slic3r::Polylines polylines = subject; // temp copy to avoid dropping the const qualifier - std::vector translated(subject.size(), false); // keep track of polylines we applied the hack to - for (Slic3r::Polylines::iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { - if (polyline->points.front().coincides_with(polyline->points.back())) { - polyline->points.front().translate(1, 0); - translated[ polyline - polylines.begin() ] = true; - } - } - // perform operation ClipperLib::PolyTree polytree; - _clipper_do(clipType, polylines, clip, polytree, ClipperLib::pftNonZero, safety_offset_); + _clipper_do(clipType, subject, clip, polytree, ClipperLib::pftNonZero, safety_offset_); // convert into Polylines ClipperLib::Paths output; ClipperLib::PolyTreeToPaths(polytree, output); ClipperPaths_to_Slic3rMultiPoints(output, retval); - - // compensate for the above hack - for (Slic3r::Polylines::iterator polyline = retval.begin(); polyline != retval.end(); ++polyline) { - for (Slic3r::Polylines::const_iterator subj_polyline = polylines.begin(); subj_polyline != polylines.end(); ++subj_polyline) { - if (!translated[ subj_polyline - polylines.begin() ]) continue; - // if first point of clipped line coincides with first point of subject line, compensate for hack - if (polyline->points.front().coincides_with(subj_polyline->points.front())) { - polyline->points.front().translate(-1, 0); - break; - } - // since Clipper does not preserve orientation of polylines, check last point too - if (polyline->points.back().coincides_with(subj_polyline->points.front())) { - polyline->points.back().translate(-1, 0); - break; - } - } - } } void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, diff --git a/xs/src/clipper.cpp b/xs/src/clipper.cpp index 4a720b71b..936e2524d 100644 --- a/xs/src/clipper.cpp +++ b/xs/src/clipper.cpp @@ -1,8 +1,8 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.1.3a * -* Date : 22 January 2014 * +* Version : 6.1.5 * +* Date : 22 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * @@ -50,15 +50,6 @@ namespace ClipperLib { -#ifdef use_int32 - static cInt const loRange = 46340; - static cInt const hiRange = 46340; -#else - static cInt const loRange = 0x3FFFFFFF; - static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; - typedef unsigned long long ulong64; -#endif - static double const pi = 3.141592653589793238; static double const two_pi = pi *2; static double const def_arc_tolerance = 0.25; @@ -240,8 +231,8 @@ bool PolyNode::IsOpen() const //------------------------------------------------------------------------------ // Int128 class (enables safe math on signed 64bit integers) -// eg Int128 val1((cInt)9223372036854775807); //ie 2^63 -1 -// Int128 val2((cInt)9223372036854775807); +// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 +// Int128 val2((long64)9223372036854775807); // Int128 val3 = val1 * val2; // val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) //------------------------------------------------------------------------------ @@ -249,22 +240,21 @@ bool PolyNode::IsOpen() const class Int128 { public: + ulong64 lo; + long64 hi; - cUInt lo; - cInt hi; - - Int128(cInt _lo = 0) + Int128(long64 _lo = 0) { - lo = (cUInt)_lo; + lo = (ulong64)_lo; if (_lo < 0) hi = -1; else hi = 0; } Int128(const Int128 &val): lo(val.lo), hi(val.hi){} - Int128(const cInt& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} - Int128& operator = (const cInt &val) + Int128& operator = (const long64 &val) { lo = (ulong64)val; if (val < 0) hi = -1; else hi = 0; @@ -335,85 +325,10 @@ class Int128 return Int128(~hi,~lo +1); } - Int128 operator/ (const Int128 &rhs) const - { - if (rhs.lo == 0 && rhs.hi == 0) - throw "Int128 operator/: divide by zero"; - - bool negate = (rhs.hi < 0) != (hi < 0); - Int128 dividend = *this; - Int128 divisor = rhs; - if (dividend.hi < 0) dividend = -dividend; - if (divisor.hi < 0) divisor = -divisor; - - if (divisor < dividend) - { - Int128 result = Int128(0); - Int128 cntr = Int128(1); - while (divisor.hi >= 0 && !(divisor > dividend)) - { - divisor.hi <<= 1; - if ((cInt)divisor.lo < 0) divisor.hi++; - divisor.lo <<= 1; - - cntr.hi <<= 1; - if ((cInt)cntr.lo < 0) cntr.hi++; - cntr.lo <<= 1; - } - divisor.lo >>= 1; - if ((divisor.hi & 1) == 1) - divisor.lo |= 0x8000000000000000LL; - divisor.hi = (ulong64)divisor.hi >> 1; - - cntr.lo >>= 1; - if ((cntr.hi & 1) == 1) - cntr.lo |= 0x8000000000000000LL; - cntr.hi >>= 1; - - while (cntr.hi != 0 || cntr.lo != 0) - { - if (!(dividend < divisor)) - { - dividend -= divisor; - result.hi |= cntr.hi; - result.lo |= cntr.lo; - } - divisor.lo >>= 1; - if ((divisor.hi & 1) == 1) - divisor.lo |= 0x8000000000000000LL; - divisor.hi >>= 1; - - cntr.lo >>= 1; - if ((cntr.hi & 1) == 1) - cntr.lo |= 0x8000000000000000LL; - cntr.hi >>= 1; - } - if (negate) result = -result; - return result; - } - else if (rhs.hi == this->hi && rhs.lo == this->lo) - return Int128(negate ? -1: 1); - else - return Int128(0); - } - - double AsDouble() const - { - const double shift64 = 18446744073709551616.0; //2^64 - if (hi < 0) - { - cUInt lo_ = ~lo + 1; - if (lo_ == 0) return (double)hi * shift64; - else return -(double)(lo_ + ~hi * shift64); - } - else - return (double)(lo + hi * shift64); - } - }; //------------------------------------------------------------------------------ -Int128 Int128Mul (cInt lhs, cInt rhs) +Int128 Int128Mul (long64 lhs, long64 rhs) { bool negate = (lhs < 0) != (rhs < 0); @@ -431,9 +346,9 @@ Int128 Int128Mul (cInt lhs, cInt rhs) ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; Int128 tmp; - tmp.hi = cInt(a + (c >> 32)); - tmp.lo = cInt(c << 32); - tmp.lo += cInt(b); + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); if (tmp.lo < b) tmp.hi++; if (negate) tmp = -tmp; return tmp; @@ -444,6 +359,13 @@ Int128 Int128Mul (cInt lhs, cInt rhs) // Miscellaneous global functions //------------------------------------------------------------------------------ +void Swap(cInt& val1, cInt& val2) +{ + cInt tmp = val1; + val1 = val2; + val2 = tmp; +} +//------------------------------------------------------------------------------ bool Orientation(const Path &poly) { return Area(poly) >= 0; @@ -494,6 +416,7 @@ bool PointIsVertex(const IntPoint &Pt, OutPt *pp) int PointInPolygon (const IntPoint &pt, const Path &path) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf int result = 0; size_t cnt = path.size(); @@ -539,7 +462,6 @@ int PointInPolygon (const IntPoint &pt, const Path &path) int PointInPolygon (const IntPoint &pt, OutPt *op) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary - //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf int result = 0; OutPt* startOp = op; for(;;) @@ -674,20 +596,18 @@ inline cInt TopX(TEdge &edge, const cInt currentY) } //------------------------------------------------------------------------------ -bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, - IntPoint &ip, bool UseFullInt64Range) +void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) { #ifdef use_xyz ip.Z = 0; #endif + double b1, b2; - //nb: with very large coordinate values, it's possible for SlopesEqual() to - //return false but for the edge.Dx value be equal due to double precision rounding. - if (SlopesEqual(Edge1, Edge2, UseFullInt64Range) || Edge1.Dx == Edge2.Dx) + if (Edge1.Dx == Edge2.Dx) { - if (Edge2.Bot.Y > Edge1.Bot.Y) ip = Edge2.Bot; - else ip = Edge1.Bot; - return false; + ip.Y = Edge1.Curr.Y; + ip.X = TopX(Edge1, ip.Y); + return; } else if (Edge1.Delta.X == 0) { @@ -734,7 +654,15 @@ bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, else ip.X = TopX(Edge2, ip.Y); } - return true; + //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... + if (ip.Y > Edge1.Curr.Y) + { + ip.Y = Edge1.Curr.Y; + //use the more vertical edge to derive X ... + if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) + ip.X = TopX(Edge2, ip.Y); else + ip.X = TopX(Edge1, ip.Y); + } } //------------------------------------------------------------------------------ @@ -807,13 +735,9 @@ inline void ReverseHorizontal(TEdge &e) //swap horizontal edges' Top and Bottom x's so they follow the natural //progression of the bounds - ie so their xbots will align with the //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - cInt tmp = e.Top.X; - e.Top.X = e.Bot.X; - e.Bot.X = tmp; + Swap(e.Top.X, e.Bot.X); #ifdef use_xyz - tmp = e.Top.Z; - e.Top.Z = e.Bot.Z; - e.Bot.Z = tmp; + Swap(e.Top.Z, e.Bot.Z); #endif } //------------------------------------------------------------------------------ @@ -905,26 +829,6 @@ OutPt* GetBottomPt(OutPt *pp) } //------------------------------------------------------------------------------ -bool FindSegment(OutPt* &pp, bool UseFullInt64Range, - IntPoint &pt1, IntPoint &pt2) -{ - //OutPt1 & OutPt2 => the overlap segment (if the function returns true) - if (!pp) return false; - OutPt* pp2 = pp; - IntPoint pt1a = pt1, pt2a = pt2; - do - { - if (SlopesEqual(pt1a, pt2a, pp->Pt, pp->Prev->Pt, UseFullInt64Range) && - SlopesEqual(pt1a, pt2a, pp->Pt, UseFullInt64Range) && - GetOverlapSegment(pt1a, pt2a, pp->Pt, pp->Prev->Pt, pt1, pt2)) - return true; - pp = pp->Next; - } - while (pp != pp2); - return false; -} -//------------------------------------------------------------------------------ - bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3) { @@ -937,41 +841,12 @@ bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, } //------------------------------------------------------------------------------ -OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint Pt) +bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) { - if (p1 == p2) throw "JoinError"; - OutPt* result = new OutPt; - result->Pt = Pt; - if (p2 == p1->Next) - { - p1->Next = result; - p2->Prev = result; - result->Next = p2; - result->Prev = p1; - } else - { - p2->Next = result; - p1->Prev = result; - result->Next = p1; - result->Prev = p2; - } - return result; + if (seg1a > seg1b) Swap(seg1a, seg1b); + if (seg2a > seg2b) Swap(seg2a, seg2b); + return (seg1a < seg2b) && (seg2a < seg1b); } -//------------------------------------------------------------------------------ - -bool HorzSegmentsOverlap(const IntPoint& pt1a, const IntPoint& pt1b, - const IntPoint& pt2a, const IntPoint& pt2b) -{ - //precondition: both segments are horizontal - if ((pt1a.X > pt2a.X) == (pt1a.X < pt2b.X)) return true; - else if ((pt1b.X > pt2a.X) == (pt1b.X < pt2b.X)) return true; - else if ((pt2a.X > pt1a.X) == (pt2a.X < pt1b.X)) return true; - else if ((pt2b.X > pt1a.X) == (pt2b.X < pt1b.X)) return true; - else if ((pt1a.X == pt2a.X) && (pt1b.X == pt2b.X)) return true; - else if ((pt1a.X == pt2b.X) && (pt1b.X == pt2a.X)) return true; - else return false; -} - //------------------------------------------------------------------------------ // ClipperBase class methods ... @@ -1030,20 +905,20 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) cInt StartX; if (IsHorizontal(*E)) { - //first we need to be careful here with open paths because this - //may not be a true local minima (ie may be following a skip edge). - //also, watch for adjacent horz edges to start heading left - //before finishing right ... - if (IsClockwise) - { - if (E->Prev->Bot.Y == E->Bot.Y) StartX = E->Prev->Bot.X; - else StartX = E->Prev->Top.X; - } - else - { - if (E->Next->Bot.Y == E->Bot.Y) StartX = E->Next->Bot.X; - else StartX = E->Next->Top.X; - } + //first we need to be careful here with open paths because this + //may not be a true local minima (ie may be following a skip edge). + //also, watch for adjacent horz edges to start heading left + //before finishing right ... + if (IsClockwise) + { + if (E->Prev->Bot.Y == E->Bot.Y) StartX = E->Prev->Bot.X; + else StartX = E->Prev->Top.X; + } + else + { + if (E->Next->Bot.Y == E->Bot.Y) StartX = E->Next->Bot.X; + else StartX = E->Next->Top.X; + } if (E->Bot.X != StartX) ReverseHorizontal(*E); } @@ -1189,7 +1064,8 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) TEdge *E = eStart, *eLoopStop = eStart; for (;;) { - if ((E->Curr == E->Next->Curr)) + //nb: allows matching start and end points when not Closed ... + if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) { if (E == E->Next) break; if (E == eStart) eStart = E->Next; @@ -1215,7 +1091,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) continue; } E = E->Next; - if (E == eLoopStop) break; + if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; } if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) @@ -1274,6 +1150,11 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) m_edges.push_back(edges); bool clockwise; TEdge* EMin = 0; + + //workaround to avoid an endless loop in the while loop below when + //open paths have matching start and end points ... + if (E->Prev->Bot == E->Prev->Top) E = E->Next; + for (;;) { E = FindNextLocMin(E); @@ -2028,7 +1909,7 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) Join* jr = m_GhostJoins[i]; //if the horizontal Rb and a 'ghost' horizontal overlap, then convert //the 'ghost' join to a real join ready for later ... - if (HorzSegmentsOverlap(jr->OutPt1->Pt, jr->OffPt, rb->Bot, rb->Top)) + if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X)) AddJoin(jr->OutPt1, Op1, jr->OffPt); } } @@ -2098,45 +1979,34 @@ void Clipper::DeleteFromSEL(TEdge *e) //------------------------------------------------------------------------------ #ifdef use_xyz - -void Clipper::SetZ(IntPoint& pt, TEdge& e) +void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) { - pt.Z = 0; - if (m_ZFill) - { - //put the 'preferred' point as first parameter ... - if (e.OutIdx < 0) - (*m_ZFill)(e.Bot, e.Top, pt); //outside a path so presume entering - else - (*m_ZFill)(e.Top, e.Bot, pt); //inside a path so presume exiting - } + if (pt.Z != 0 || !m_ZFill) return; + else if (pt == e1.Bot) pt.Z = e1.Bot.Z; + else if (pt == e1.Top) pt.Z = e1.Top.Z; + else if (pt == e2.Bot) pt.Z = e2.Bot.Z; + else if (pt == e2.Top) pt.Z = e2.Top.Z; + else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); } //------------------------------------------------------------------------------ #endif -void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, - const IntPoint &Pt, bool protect) +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) { - //e1 will be to the Left of e2 BELOW the intersection. Therefore e1 is before - //e2 in AEL except when e1 is being inserted at the intersection point ... - bool e1stops = !protect && !e1->NextInLML && - e1->Top.X == Pt.X && e1->Top.Y == Pt.Y; - bool e2stops = !protect && !e2->NextInLML && - e2->Top.X == Pt.X && e2->Top.Y == Pt.Y; bool e1Contributing = ( e1->OutIdx >= 0 ); bool e2Contributing = ( e2->OutIdx >= 0 ); +#ifdef use_xyz + SetZ(Pt, *e1, *e2); +#endif + #ifdef use_lines //if either edge is on an OPEN path ... if (e1->WindDelta == 0 || e2->WindDelta == 0) { //ignore subject-subject open path intersections UNLESS they //are both open paths, AND they are both 'contributing maximas' ... - if (e1->WindDelta == 0 && e2->WindDelta == 0) - { - if ((e1stops || e2stops) && e1Contributing && e2Contributing) - AddLocalMaxPoly(e1, e2, Pt); - } + if (e1->WindDelta == 0 && e2->WindDelta == 0) return; //if intersecting a subj line with a subj poly ... else if (e1->PolyTyp == e2->PolyTyp && @@ -2175,13 +2045,6 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, if (e2Contributing) e2->OutIdx = Unassigned; } } - - if (e1stops) - if (e1->OutIdx < 0) DeleteFromAEL(e1); - else throw clipperException("Error intersecting polylines"); - if (e2stops) - if (e2->OutIdx < 0) DeleteFromAEL(e2); - else throw clipperException("Error intersecting polylines"); return; } #endif @@ -2246,10 +2109,11 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, if ( e1Contributing && e2Contributing ) { - if ( e1stops || e2stops || - (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) - AddLocalMaxPoly(e1, e2, Pt); + { + AddLocalMaxPoly(e1, e2, Pt); + } else { AddOutPt(e1, Pt); @@ -2276,8 +2140,7 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, SwapPolyIndexes(*e1, *e2); } } - else if ( (e1Wc == 0 || e1Wc == 1) && - (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) { //neither edge is currently contributing ... @@ -2296,7 +2159,9 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, } if (e1->PolyTyp != e2->PolyTyp) - AddLocalMinPoly(e1, e2, Pt); + { + AddLocalMinPoly(e1, e2, Pt); + } else if (e1Wc == 1 && e2Wc == 1) switch( m_ClipType ) { case ctIntersection: @@ -2318,17 +2183,6 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, else SwapSides( *e1, *e2 ); } - - if( (e1stops != e2stops) && - ( (e1stops && (e1->OutIdx >= 0)) || (e2stops && (e2->OutIdx >= 0)) ) ) - { - SwapSides( *e1, *e2 ); - SwapPolyIndexes( *e1, *e2 ); - } - - //finally, delete any non-contributing maxima edges ... - if( e1stops ) DeleteFromAEL( e1 ); - if( e2stops ) DeleteFromAEL( e2 ); } //------------------------------------------------------------------------------ @@ -2519,12 +2373,7 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) newOp->Prev = newOp; if (!outRec->IsOpen) SetHoleState(e, outRec); -#ifdef use_xyz - if (pt == e->Bot) newOp->Pt = e->Bot; - else if (pt == e->Top) newOp->Pt = e->Top; - else SetZ(newOp->Pt, *e); -#endif - e->OutIdx = outRec->Idx; //nb: do this after SetZ ! + e->OutIdx = outRec->Idx; return newOp; } else { @@ -2543,11 +2392,6 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) newOp->Prev->Next = newOp; op->Prev = newOp; if (ToFront) outRec->Pts = newOp; -#ifdef use_xyz - if (pt == e->Bot) newOp->Pt = e->Bot; - else if (pt == e->Top) newOp->Pt = e->Top; - else SetZ(newOp->Pt, *e); -#endif return newOp; } } @@ -2714,37 +2558,6 @@ void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) } //------------------------------------------------------------------------ -void Clipper::PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam) -{ - //get the last Op for this horizontal edge - //the point may be anywhere along the horizontal ... - OutPt* outPt = m_PolyOuts[horzEdge->OutIdx]->Pts; - if (horzEdge->Side != esLeft) outPt = outPt->Prev; - - //First, match up overlapping horizontal edges (eg when one polygon's - //intermediate horz edge overlaps an intermediate horz edge of another, or - //when one polygon sits on top of another) ... - //for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) - //{ - // Join* j = m_GhostJoins[i]; - // if (HorzSegmentsOverlap(j->OutPt1->Pt, j->OffPt, horzEdge->Bot, horzEdge->Top)) - // AddJoin(j->OutPt1, outPt, j->OffPt); - //} - - //Also, since horizontal edges at the top of one SB are often removed from - //the AEL before we process the horizontal edges at the bottom of the next, - //we need to create 'ghost' Join records of 'contrubuting' horizontals that - //we can compare with horizontals at the bottom of the next SB. - if (isTopOfScanbeam) - { - if (outPt->Pt == horzEdge->Top) - AddGhostJoin(outPt, horzEdge->Bot); - else - AddGhostJoin(outPt, horzEdge->Top); - } -} -//------------------------------------------------------------------------------ - /******************************************************************************* * Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * * Bottom of a scanbeam) are processed as if layered. The order in which HEs * @@ -2784,28 +2597,42 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) if ((dir == dLeftToRight && e->Curr.X <= horzRight) || (dir == dRightToLeft && e->Curr.X >= horzLeft)) { - if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) - PrepareHorzJoins(horzEdge, isTopOfScanbeam); //so far we're still in range of the horizontal Edge but make sure //we're at the last of consec. horizontals when matching with eMaxPair if(e == eMaxPair && IsLastHorz) { - if (dir == dLeftToRight) - IntersectEdges(horzEdge, e, e->Top); - else - IntersectEdges(e, horzEdge, e->Top); - if (eMaxPair->OutIdx >= 0) throw clipperException("ProcessHorizontal error"); + + if (horzEdge->OutIdx >= 0) + { + OutPt* op1 = AddOutPt(horzEdge, horzEdge->Top); + TEdge* eNextHorz = m_SortedEdges; + while (eNextHorz) + { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, + horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) + { + OutPt* op2 = AddOutPt(eNextHorz, eNextHorz->Bot); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Bot); + AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); + } + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); return; } else if(dir == dLeftToRight) { IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); - IntersectEdges(horzEdge, e, Pt, true); + IntersectEdges(horzEdge, e, Pt); } else { IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); - IntersectEdges( e, horzEdge, Pt, true); + IntersectEdges( e, horzEdge, Pt); } SwapPositionsInAEL( horzEdge, e ); } @@ -2814,9 +2641,6 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) e = eNext; } //end while - if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) - PrepareHorzJoins(horzEdge, isTopOfScanbeam); - if (horzEdge->NextInLML && IsHorizontal(*horzEdge->NextInLML)) { UpdateEdgeIntoAEL(horzEdge); @@ -2831,6 +2655,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) if(horzEdge->OutIdx >= 0) { OutPt* op1 = AddOutPt( horzEdge, horzEdge->Top); + if (isTopOfScanbeam) AddGhostJoin(op1, horzEdge->Bot); UpdateEdgeIntoAEL(horzEdge); if (horzEdge->WindDelta == 0) return; //nb: HorzEdge is no longer horizontal here @@ -2856,22 +2681,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) else UpdateEdgeIntoAEL(horzEdge); } - else if (eMaxPair) - { - if (eMaxPair->OutIdx >= 0) - { - if (dir == dLeftToRight) - IntersectEdges(horzEdge, eMaxPair, horzEdge->Top); - else - IntersectEdges(eMaxPair, horzEdge, horzEdge->Top); - if (eMaxPair->OutIdx >= 0) - throw clipperException("ProcessHorizontal error"); - } else - { - DeleteFromAEL(horzEdge); - DeleteFromAEL(eMaxPair); - } - } else + else { if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); DeleteFromAEL(horzEdge); @@ -2958,16 +2768,7 @@ void Clipper::BuildIntersectList(const cInt botY, const cInt topY) IntPoint Pt; if(e->Curr.X > eNext->Curr.X) { - if (!IntersectPoint(*e, *eNext, Pt, m_UseFullRange) && e->Curr.X > eNext->Curr.X +1) - throw clipperException("Intersection error"); - if (Pt.Y > botY) - { - Pt.Y = botY; - if (std::fabs(e->Dx) > std::fabs(eNext->Dx)) - Pt.X = TopX(*eNext, botY); else - Pt.X = TopX(*e, botY); - } - + IntersectPoint(*e, *eNext, Pt); IntersectNode * newNode = new IntersectNode; newNode->Edge1 = e; newNode->Edge2 = eNext; @@ -2995,7 +2796,7 @@ void Clipper::ProcessIntersectList() { IntersectNode* iNode = m_IntersectList[i]; { - IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt, true); + IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt); SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); } delete iNode; @@ -3054,7 +2855,7 @@ void Clipper::DoMaxima(TEdge *e) TEdge* eNext = e->NextInAEL; while(eNext && eNext != eMaxPair) { - IntersectEdges(e, eNext, e->Top, true); + IntersectEdges(e, eNext, e->Top); SwapPositionsInAEL(e, eNext); eNext = e->NextInAEL; } @@ -3066,7 +2867,9 @@ void Clipper::DoMaxima(TEdge *e) } else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) { - IntersectEdges( e, eMaxPair, e->Top); + if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); } #ifdef use_lines else if (e->WindDelta == 0) @@ -3134,9 +2937,13 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) { - OutPt* op = AddOutPt(ePrev, e->Curr); - OutPt* op2 = AddOutPt(e, e->Curr); - AddJoin(op, op2, e->Curr); //StrictlySimple (type-3) join + IntPoint pt = e->Curr; +#ifdef use_xyz + SetZ(pt, *ePrev, *e); +#endif + OutPt* op = AddOutPt(ePrev, pt); + OutPt* op2 = AddOutPt(e, pt); + AddJoin(op, op2, pt); //StrictlySimple (type-3) join } } @@ -3518,6 +3325,7 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) (j->OffPt == j->OutPt2->Pt)) { //Strictly Simple join ... + if (outRec1 != outRec2) return false; op1b = j->OutPt1->Next; while (op1b != op1 && (op1b->Pt == j->OffPt)) op1b = op1b->Next; @@ -3858,8 +3666,7 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType (path[i].Y == newNode->Contour[k].Y && path[i].X < newNode->Contour[k].X)) k = j; } - if ((endType == etClosedPolygon && j < 2) || - (endType != etClosedPolygon && j < 0)) + if (endType == etClosedPolygon && j < 2) { delete newNode; return; @@ -3869,7 +3676,7 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType //if this path's lowest pt is lower than all the others then update m_lowest if (endType != etClosedPolygon) return; if (m_lowest.X < 0) - m_lowest = IntPoint(0, k); + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); else { IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; @@ -4159,8 +3966,20 @@ void ClipperOffset::DoOffset(double delta) void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) { + //cross product ... m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); - if (m_sinA < 0.00005 && m_sinA > -0.00005) return; + if (std::fabs(m_sinA * m_delta) < 1.0) + { + //dot product ... + double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); + if (cosA > 0) // angle => 0 degrees + { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + return; + } + //else angle => 180 degrees + } else if (m_sinA > 1.0) m_sinA = 1.0; else if (m_sinA < -1.0) m_sinA = -1.0; @@ -4358,7 +4177,27 @@ double DistanceFromLineSqrd( bool SlopesNearCollinear(const IntPoint& pt1, const IntPoint& pt2, const IntPoint& pt3, double distSqrd) { - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + //this function is more accurate when the point that's geometrically + //between the other 2 points is the one that's tested for distance. + //ie makes it more likely to pick up 'spikes' ... + if (std::abs(pt1.X - pt2.X) > std::abs(pt1.Y - pt2.Y)) + { + if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } + else + { + if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } } //------------------------------------------------------------------------------ @@ -4486,8 +4325,8 @@ void Minkowski(const Path& poly, const Path& path, pp.push_back(p); } - Paths quads; - quads.reserve((pathCnt + delta) * (polyCnt + 1)); + solution.clear(); + solution.reserve((pathCnt + delta) * (polyCnt + 1)); for (size_t i = 0; i < pathCnt - 1 + delta; ++i) for (size_t j = 0; j < polyCnt; ++j) { @@ -4498,23 +4337,30 @@ void Minkowski(const Path& poly, const Path& path, quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); if (!Orientation(quad)) ReversePath(quad); - quads.push_back(quad); + solution.push_back(quad); } - - Clipper c; - c.AddPaths(quads, ptSubject, true); - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) { Minkowski(pattern, path, solution, true, pathIsClosed); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ -void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, - PolyFillType pathFillType, bool pathIsClosed) +void TranslatePath(const Path& input, Path& output, IntPoint delta) +{ + //precondition: input != output + output.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) { Clipper c; for (size_t i = 0; i < paths.size(); ++i) @@ -4522,15 +4368,23 @@ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, Paths tmp; Minkowski(pattern, paths[i], tmp, true, pathIsClosed); c.AddPaths(tmp, ptSubject, true); + if (pathIsClosed) + { + Path tmp2; + TranslatePath(paths[i], tmp2, pattern[0]); + c.AddPath(tmp2, ptClip, true); + } } - if (pathIsClosed) c.AddPaths(paths, ptClip, true); - c.Execute(ctUnion, solution, pathFillType, pathFillType); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) { Minkowski(poly1, poly2, solution, false, true); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp index 20791c78c..7922abe18 100644 --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -1,8 +1,8 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.1.3a * -* Date : 22 January 2014 * +* Version : 6.1.5 * +* Date : 22 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * @@ -34,7 +34,7 @@ #ifndef clipper_hpp #define clipper_hpp -#define CLIPPER_VERSION "6.1.3" +#define CLIPPER_VERSION "6.1.5" //use_int32: When enabled 32bit ints are used instead of 64bit ints. This //improve performance but coordinate values are limited to the range +/- 46340 @@ -69,11 +69,15 @@ enum PolyType { ptSubject, ptClip }; enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; #ifdef use_int32 -typedef int cInt; -typedef unsigned int cUInt; + typedef int cInt; + static cInt const loRange = 46340; + static cInt const hiRange = 46340; #else -typedef signed long long cInt; -typedef unsigned long long cUInt; + typedef signed long long cInt; + typedef signed long long long64; //used by Int128 class + typedef unsigned long long ulong64; + static cInt const loRange = 0x3FFFFFFF; + static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; #endif struct IntPoint { @@ -117,7 +121,7 @@ struct DoublePoint //------------------------------------------------------------------------------ #ifdef use_xyz -typedef void (*TZFillCallback)(IntPoint& z1, IntPoint& z2, IntPoint& pt); +typedef void (*TZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); #endif enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; @@ -183,8 +187,7 @@ void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1. void CleanPolygons(Paths& polys, double distance = 1.415); void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); -void MinkowskiSum(const Path& pattern, const Paths& paths, - Paths& solution, PolyFillType pathFillType, bool pathIsClosed); +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed); void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); @@ -308,15 +311,13 @@ private: bool IsTopHorz(const cInt XPos); void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); void DoMaxima(TEdge *e); - void PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam); void ProcessHorizontals(bool IsTopOfScanbeam); void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam); void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutRec* GetOutRec(int idx); void AppendPolygon(TEdge *e1, TEdge *e2); - void IntersectEdges(TEdge *e1, TEdge *e2, - const IntPoint &pt, bool protect = false); + void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); OutRec* CreateOutRec(); OutPt* AddOutPt(TEdge *e, const IntPoint &pt); void DisposeAllOutRecs(); @@ -344,7 +345,7 @@ private: void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); #ifdef use_xyz - void SetZ(IntPoint& pt, TEdge& e); + void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); #endif }; //------------------------------------------------------------------------------ From fcdb462abe0199979329ac6010bb7a1fd1f4dd0e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 12 May 2014 22:28:26 +0200 Subject: [PATCH 28/48] Failing test cases for Clipper bug returning empty result set. #2028 --- lib/Slic3r/Layer/Region.pm | 2 +- lib/Slic3r/Polygon.pm | 5 +++++ xs/t/11_clipper.t | 24 +++++++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 39cec3d61..3d089cc82 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -302,7 +302,7 @@ sub make_perimeters { 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 diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index c12d3dcaf..124d9ae99 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -15,6 +15,11 @@ sub wkt { return sprintf "POLYGON((%s))", join ',', map "$_->[0] $_->[1]", @$self; } +sub dump_perl { + my $self = shift; + return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self; +} + sub grow { my $self = shift; return $self->split_at_first_point->grow(@_); diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index ca00bc6e3..f2f7686a8 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 17; +use Test::More tests => 19; my $square = Slic3r::Polygon->new( # ccw [200, 100], @@ -155,4 +155,26 @@ if (0) { # Clipper does not preserve polyline orientation } } +{ + my $subject = Slic3r::Polyline->new( + [44735000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000],[44730000,31936670] + ); + my $clip = [ + Slic3r::Polygon->new([75200000,45200000],[54800000,45200000],[54800000,24800000],[75200000,24800000]), + ]; + my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip); + is scalar(@$result), 1, 'intersection_pl - result is not empty'; +} + +{ + my $subject = Slic3r::Polygon->new( + [44730000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000] + ); + my $clip = [ + Slic3r::Polygon->new([75200000,45200000],[54800000,45200000],[54800000,24800000],[75200000,24800000]), + ]; + my $result = Slic3r::Geometry::Clipper::intersection_ppl([$subject], $clip); + is scalar(@$result), 1, 'intersection_ppl - result is not empty'; +} + __END__ From bf2af85da591b61d679b15726e6a9c029e9cc548 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 24 May 2014 00:01:27 +0200 Subject: [PATCH 29/48] Fix bad test --- xs/t/11_clipper.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index f2f7686a8..eb7e3d17e 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -163,7 +163,7 @@ if (0) { # Clipper does not preserve polyline orientation Slic3r::Polygon->new([75200000,45200000],[54800000,45200000],[54800000,24800000],[75200000,24800000]), ]; my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip); - is scalar(@$result), 1, 'intersection_pl - result is not empty'; + is scalar(@$result), 2, 'intersection_pl - result is not empty'; } { From ed58f35fe52919fcc7d0cb2bc57d48a57c7b0912 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 24 May 2014 00:09:04 +0200 Subject: [PATCH 30/48] Revert "Fix bad test". Test was actually correct. Clipper's still slightly buggy since it splits polylines This reverts commit bf2af85da591b61d679b15726e6a9c029e9cc548. --- xs/t/11_clipper.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index eb7e3d17e..f2f7686a8 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -163,7 +163,7 @@ if (0) { # Clipper does not preserve polyline orientation Slic3r::Polygon->new([75200000,45200000],[54800000,45200000],[54800000,24800000],[75200000,24800000]), ]; my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip); - is scalar(@$result), 2, 'intersection_pl - result is not empty'; + is scalar(@$result), 1, 'intersection_pl - result is not empty'; } { From 3df2488eca56bcafac8ec92ef99b246b30eaf7ac Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 24 May 2014 00:10:37 +0200 Subject: [PATCH 31/48] Disable test until Clipper bug 96 is fixed #2028 --- xs/t/11_clipper.t | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index f2f7686a8..efa051fe7 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 19; +use Test::More tests => 18; my $square = Slic3r::Polygon->new( # ccw [200, 100], @@ -155,7 +155,8 @@ if (0) { # Clipper does not preserve polyline orientation } } -{ +# Disabled until Clipper bug #96 (our issue #2028) is fixed +if (0) { my $subject = Slic3r::Polyline->new( [44735000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000],[44730000,31936670] ); From 88a2e5c791d6dbd8f6626e1077acc001bd40a3d3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 24 May 2014 22:10:28 +0200 Subject: [PATCH 32/48] Typo seal -> seam :-) --- README.md | 2 +- lib/Slic3r/Config.pm | 4 ++-- lib/Slic3r/GCode.pm | 16 ++++++++-------- lib/Slic3r/GUI/Tab.pm | 2 +- slic3r.pl | 2 +- t/perimeters.t | 4 ++-- xs/src/PrintConfig.hpp | 34 +++++++++++++++++----------------- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 13bd1300d..46c19db72 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ The author of the Silk icon set is Mark James. home X axis [G28 X], disable motors [M84]). --layer-gcode Load layer-change G-code from the supplied file (default: nothing). --toolchange-gcode Load tool-change G-code from the supplied file (default: nothing). - --seal-position Position of loop starting points (random/nearest/aligned, default: aligned). + --seam-position Position of loop starting points (random/nearest/aligned, default: aligned). --external-perimeters-first Reverse perimeter order. (default: no) --spiral-vase Experimental option to raise Z gradually when printing single-walled vases (default: no) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 625e1f8fd..113d8ce74 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -9,7 +9,7 @@ use List::Util qw(first max); our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang - randomize_start); + randomize_start seal_position); our $Options = print_config_def(); @@ -142,7 +142,7 @@ sub _handle_legacy { $value = "$value"; # force update of the PV value, workaround for bug https://rt.cpan.org/Ticket/Display.html?id=94110 } if ($opt_key eq 'randomize_start' && $value) { - $opt_key = 'seal_position'; + $opt_key = 'seam_position'; $value = 'random'; } diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 73a787d51..f085a3a3d 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -19,7 +19,7 @@ has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter has 'layer' => (is => 'rw'); has '_layer_islands' => (is => 'rw'); has '_upper_layer_islands' => (is => 'rw'); -has '_seal_position' => (is => 'ro', default => sub { {} }); # $object => pos +has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); @@ -156,7 +156,7 @@ sub extrude_loop { my $last_pos = $self->last_pos; if ($self->config->spiral_vase) { $loop->split_at($last_pos); - } elsif ($self->config->seal_position eq 'nearest' || $self->config->seal_position eq 'aligned') { + } elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') { my $polygon = $loop->polygon; my @candidates = @{$polygon->concave_points(PI*4/3)}; @candidates = @{$polygon->convex_points(PI*2/3)} if !@candidates; @@ -165,16 +165,16 @@ sub extrude_loop { my @non_overhang = grep !$loop->has_overhang_point($_), @candidates; @candidates = @non_overhang if @non_overhang; - if ($self->config->seal_position eq 'nearest') { + if ($self->config->seam_position eq 'nearest') { $loop->split_at_vertex($last_pos->nearest_point(\@candidates)); - } elsif ($self->config->seal_position eq 'aligned') { - if (defined $self->layer && defined $self->_seal_position->{$self->layer->object}) { - $last_pos = $self->_seal_position->{$self->layer->object}; + } elsif ($self->config->seam_position eq 'aligned') { + if (defined $self->layer && defined $self->_seam_position->{$self->layer->object}) { + $last_pos = $self->_seam_position->{$self->layer->object}; } - my $point = $self->_seal_position->{$self->layer->object} = $last_pos->nearest_point(\@candidates); + my $point = $self->_seam_position->{$self->layer->object} = $last_pos->nearest_point(\@candidates); $loop->split_at_vertex($point); } - } elsif ($self->config->seal_position eq 'random') { + } elsif ($self->config->seam_position eq 'random') { if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { my $polygon = $loop->polygon; my $centroid = $polygon->centroid; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 1d4945f01..6cc637d71 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -427,7 +427,7 @@ sub build { }, { title => 'Advanced', - options => [qw(seal_position external_perimeters_first)], + options => [qw(seam_position external_perimeters_first)], }, ]); diff --git a/slic3r.pl b/slic3r.pl index aa3a43700..7a249043a 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -342,7 +342,7 @@ $j home X axis [G28 X], disable motors [M84]). --layer-gcode Load layer-change G-code from the supplied file (default: nothing). --toolchange-gcode Load tool-change G-code from the supplied file (default: nothing). - --seal-position Position of loop starting points (random/nearest/aligned, default: $config->{seal_position}). + --seam-position Position of loop starting points (random/nearest/aligned, default: $config->{seam_position}). --external-perimeters-first Reverse perimeter order. (default: no) --spiral-vase Experimental option to raise Z gradually when printing single-walled vases (default: no) diff --git a/t/perimeters.t b/t/perimeters.t index 239f80f2f..d432f480d 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -245,9 +245,9 @@ use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; - $config->set('seal_position', 'random'); + $config->set('seam_position', 'random'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'successful generation of G-code with seal_position = random'; + ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random'; } __END__ diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index 913d81de2..cd31d899b 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -18,7 +18,7 @@ enum SupportMaterialPattern { smpRectilinear, smpRectilinearGrid, smpHoneycomb, smpPillars, }; -enum SealPosition { +enum SeamPosition { spRandom, spNearest, spAligned }; @@ -54,7 +54,7 @@ template<> inline t_config_enum_values ConfigOptionEnum: return keys_map; } -template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { +template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["random"] = spRandom; keys_map["nearest"] = spNearest; @@ -651,18 +651,18 @@ class PrintConfigDef Options["retract_speed"].cli = "retract-speed=f@"; Options["retract_speed"].max = 1000; - Options["seal_position"].type = coEnum; - Options["seal_position"].label = "Seal position"; - Options["seal_position"].category = "Layers and perimeters"; - Options["seal_position"].tooltip = "Position of perimeters starting points."; - Options["seal_position"].cli = "seal-position=s"; - Options["seal_position"].enum_keys_map = ConfigOptionEnum::get_enum_values(); - Options["seal_position"].enum_values.push_back("random"); - Options["seal_position"].enum_values.push_back("nearest"); - Options["seal_position"].enum_values.push_back("aligned"); - Options["seal_position"].enum_labels.push_back("Random"); - Options["seal_position"].enum_labels.push_back("Nearest"); - Options["seal_position"].enum_labels.push_back("Aligned"); + Options["seam_position"].type = coEnum; + Options["seam_position"].label = "Seam position"; + Options["seam_position"].category = "Layers and perimeters"; + Options["seam_position"].tooltip = "Position of perimeters starting points."; + Options["seam_position"].cli = "seam-position=s"; + Options["seam_position"].enum_keys_map = ConfigOptionEnum::get_enum_values(); + Options["seam_position"].enum_values.push_back("random"); + Options["seam_position"].enum_values.push_back("nearest"); + Options["seam_position"].enum_values.push_back("aligned"); + Options["seam_position"].enum_labels.push_back("Random"); + Options["seam_position"].enum_labels.push_back("Nearest"); + Options["seam_position"].enum_labels.push_back("Aligned"); Options["skirt_distance"].type = coFloat; Options["skirt_distance"].label = "Distance from object"; @@ -1006,7 +1006,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig ConfigOptionBool interface_shells; ConfigOptionFloat layer_height; ConfigOptionInt raft_layers; - ConfigOptionEnum seal_position; + ConfigOptionEnum seam_position; ConfigOptionBool support_material; ConfigOptionInt support_material_angle; ConfigOptionInt support_material_enforce_layers; @@ -1031,7 +1031,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig this->interface_shells.value = false; this->layer_height.value = 0.4; this->raft_layers.value = 0; - this->seal_position.value = spAligned; + this->seam_position.value = spAligned; this->support_material.value = false; this->support_material_angle.value = 0; this->support_material_enforce_layers.value = 0; @@ -1057,7 +1057,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig if (opt_key == "interface_shells") return &this->interface_shells; if (opt_key == "layer_height") return &this->layer_height; if (opt_key == "raft_layers") return &this->raft_layers; - if (opt_key == "seal_position") return &this->seal_position; + if (opt_key == "seam_position") return &this->seam_position; if (opt_key == "support_material") return &this->support_material; if (opt_key == "support_material_angle") return &this->support_material_angle; if (opt_key == "support_material_enforce_layers") return &this->support_material_enforce_layers; From a62457d6b5e9b03ef01245b58f9d7c528c9b5107 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 May 2014 22:48:58 +0200 Subject: [PATCH 33/48] Updated Clipper to r467 but it doesn't seem to fix #2028 yet --- xs/src/ClipperUtils.cpp | 8 ++ xs/src/clipper.cpp | 186 ++++++++++++++++++++-------------------- xs/src/clipper.hpp | 2 +- xs/t/11_clipper.t | 2 +- 4 files changed, 104 insertions(+), 94 deletions(-) diff --git a/xs/src/ClipperUtils.cpp b/xs/src/ClipperUtils.cpp index 2989783ee..12c90996f 100644 --- a/xs/src/ClipperUtils.cpp +++ b/xs/src/ClipperUtils.cpp @@ -1,6 +1,14 @@ #include "ClipperUtils.hpp" #include "Geometry.hpp" +#ifndef use_lines + #error "use_lines directive is not enabled in clipper.hpp" +#endif + +#ifdef use_deprecated + #error "use_deprecated is not disabled in clipper.hpp" +#endif + namespace Slic3r { //----------------------------------------------------------- diff --git a/xs/src/clipper.cpp b/xs/src/clipper.cpp index 936e2524d..9070ecb1b 100644 --- a/xs/src/clipper.cpp +++ b/xs/src/clipper.cpp @@ -2,7 +2,7 @@ * * * Author : Angus Johnson * * Version : 6.1.5 * -* Date : 22 May 2014 * +* Date : 24 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * @@ -898,112 +898,39 @@ TEdge* FindNextLocMin(TEdge* E) } //------------------------------------------------------------------------------ -TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) +TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) { TEdge *EStart = E, *Result = E; TEdge *Horz = 0; cInt StartX; - if (IsHorizontal(*E)) - { - //first we need to be careful here with open paths because this - //may not be a true local minima (ie may be following a skip edge). - //also, watch for adjacent horz edges to start heading left - //before finishing right ... - if (IsClockwise) - { - if (E->Prev->Bot.Y == E->Bot.Y) StartX = E->Prev->Bot.X; - else StartX = E->Prev->Top.X; - } - else - { - if (E->Next->Bot.Y == E->Bot.Y) StartX = E->Next->Bot.X; - else StartX = E->Next->Top.X; - } - if (E->Bot.X != StartX) ReverseHorizontal(*E); - } - - if (Result->OutIdx != Skip) - { - if (IsClockwise) - { - while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) - Result = Result->Next; - if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) - { - //nb: at the top of a bound, horizontals are added to the bound - //only when the preceding edge attaches to the horizontal's left vertex - //unless a Skip edge is encountered when that becomes the top divide - Horz = Result; - while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X == Result->Next->Top.X) - { - if (!IsClockwise) Result = Horz->Prev; - } - else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; - } - while (E != Result) - { - E->NextInLML = E->Next; - if (IsHorizontal(*E) && E != EStart && - E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - E = E->Next; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) - ReverseHorizontal(*E); - Result = Result->Next; //move to the edge just beyond current bound - } else - { - while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) - Result = Result->Prev; - if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) - { - Horz = Result; - while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X) - { - if (!IsClockwise) Result = Horz->Next; - } - else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; - } - while (E != Result) - { - E->NextInLML = E->Prev; - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - E = E->Prev; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - Result = Result->Prev; //move to the edge just beyond current bound - } - } - - if (Result->OutIdx == Skip) + if (E->OutIdx == Skip) { //if edges still remain in the current bound beyond the skip edge then //create another LocMin and call ProcessBound once more - E = Result; - if (IsClockwise) + if (NextIsForward) { while (E->Top.Y == E->Next->Bot.Y) E = E->Next; //don't include top horizontals when parsing a bound a second time, //they will be contained in the opposite bound ... while (E != Result && IsHorizontal(*E)) E = E->Prev; - } else + } + else { while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; while (E != Result && IsHorizontal(*E)) E = E->Next; } + if (E == Result) { - if (IsClockwise) Result = E->Next; + if (NextIsForward) Result = E->Next; else Result = E->Prev; - } else + } + else { //there are more edges in the bound beyond result starting with E - if (IsClockwise) - E = Result->Next; + if (NextIsForward) + E = Result->Next; else E = Result->Prev; LocalMinima* locMin = new LocalMinima; @@ -1012,10 +939,85 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) locMin->LeftBound = 0; locMin->RightBound = E; locMin->RightBound->WindDelta = 0; - Result = ProcessBound(locMin->RightBound, IsClockwise); + Result = ProcessBound(locMin->RightBound, NextIsForward); InsertLocalMinima(locMin); } + return Result; } + + if (IsHorizontal(*E)) + { + //we need to be careful with open paths because this may not be + //a true local minima (ie may be following a skip edge). + //Also, watch for adjacent horz edges that can head left + //before finishing right ... + if (NextIsForward) + { + if (E->Prev->Bot.Y == E->Bot.Y) StartX = E->Prev->Bot.X; + else StartX = E->Prev->Top.X; + } + else + { + if (E->Next->Bot.Y == E->Bot.Y) StartX = E->Next->Bot.X; + else StartX = E->Next->Top.X; + } + if (E->Bot.X != StartX) ReverseHorizontal(*E); + } + + if (NextIsForward) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X == Result->Next->Top.X) + { + if (!NextIsForward) Result = Horz->Prev; + } + else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X) + { + if (!NextIsForward) Result = Horz->Next; + } + else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } + + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + return Result; } //------------------------------------------------------------------------------ @@ -1148,7 +1150,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) } m_edges.push_back(edges); - bool clockwise; + bool nextIsForward; TEdge* EMin = 0; //workaround to avoid an endless loop in the while loop below when @@ -1170,12 +1172,12 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { locMin->LeftBound = E->Prev; locMin->RightBound = E; - clockwise = false; //Q.nextInLML = Q.prev + nextIsForward = false; //Q.nextInLML = Q.prev } else { locMin->LeftBound = E; locMin->RightBound = E->Prev; - clockwise = true; //Q.nextInLML = Q.next + nextIsForward = true; //Q.nextInLML = Q.next } locMin->LeftBound->Side = esLeft; locMin->RightBound->Side = esRight; @@ -1186,15 +1188,15 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) else locMin->LeftBound->WindDelta = 1; locMin->RightBound->WindDelta = -locMin->LeftBound->WindDelta; - E = ProcessBound(locMin->LeftBound, clockwise); - TEdge* E2 = ProcessBound(locMin->RightBound, !clockwise); + E = ProcessBound(locMin->LeftBound, nextIsForward); + TEdge* E2 = ProcessBound(locMin->RightBound, !nextIsForward); if (locMin->LeftBound->OutIdx == Skip) locMin->LeftBound = 0; else if (locMin->RightBound->OutIdx == Skip) locMin->RightBound = 0; InsertLocalMinima(locMin); - if (!clockwise) E = E2; + if (!nextIsForward) E = E2; } return true; } diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp index 7922abe18..9927a97ad 100644 --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -2,7 +2,7 @@ * * * Author : Angus Johnson * * Version : 6.1.5 * -* Date : 22 May 2014 * +* Date : 24 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index efa051fe7..89d173e39 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -155,7 +155,7 @@ if (0) { # Clipper does not preserve polyline orientation } } -# Disabled until Clipper bug #96 (our issue #2028) is fixed +# Clipper bug #96 (our GH #2028) if (0) { my $subject = Slic3r::Polyline->new( [44735000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000],[44730000,31936670] From 4e5f7d74ff6d90a00a50a190c0cffd48e86cfbaa Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 May 2014 23:17:00 +0200 Subject: [PATCH 34/48] Bugfix: wrong handling of large number of raft layers. #2041 --- lib/Slic3r/Print/Object.pm | 9 ++++----- t/support.t | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 61b3da837..127bf5a82 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -308,11 +308,10 @@ sub slice { } # remove empty layers from bottom - my $first_object_layer_id = $self->config->raft_layers; - while (@{$self->layers} && !@{$self->layers->[$first_object_layer_id]->slices}) { - splice @{$self->layers}, $first_object_layer_id, 1; - for (my $i = $first_object_layer_id; $i <= $#{$self->layers}; $i++) { - $self->layers->[$i]->id($i); + while (@{$self->layers} && !@{$self->layers->[0]->slices}) { + shift @{$self->layers}; + for (my $i = 0; $i <= $#{$self->layers}; $i++) { + $self->layers->[$i]->id( $self->layers->[$i]->id-1 ); } } diff --git a/t/support.t b/t/support.t index 8186ec691..8939848bb 100644 --- a/t/support.t +++ b/t/support.t @@ -1,4 +1,4 @@ -use Test::More tests => 15; +use Test::More tests => 16; use strict; use warnings; @@ -130,10 +130,10 @@ use Slic3r::Test; 'first object layer is completely supported by raft'; } -{ +foreach my $raft_layers (2, 70) { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); - $config->set('raft_layers', 2); + $config->set('raft_layers', $raft_layers); $config->set('layer_height', 0.35); $config->set('first_layer_height', 0.3); $config->set('nozzle_diameter', [0.5]); From 0decbbf910766bb815ec6160ebf5242eef96be96 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 May 2014 23:27:26 +0200 Subject: [PATCH 35/48] Revert "Updated Clipper to r467 but it doesn't seem to fix #2028 yet" This reverts commit a62457d6b5e9b03ef01245b58f9d7c528c9b5107. --- xs/src/ClipperUtils.cpp | 8 -- xs/src/clipper.cpp | 186 ++++++++++++++++++++-------------------- xs/src/clipper.hpp | 2 +- xs/t/11_clipper.t | 2 +- 4 files changed, 94 insertions(+), 104 deletions(-) diff --git a/xs/src/ClipperUtils.cpp b/xs/src/ClipperUtils.cpp index 12c90996f..2989783ee 100644 --- a/xs/src/ClipperUtils.cpp +++ b/xs/src/ClipperUtils.cpp @@ -1,14 +1,6 @@ #include "ClipperUtils.hpp" #include "Geometry.hpp" -#ifndef use_lines - #error "use_lines directive is not enabled in clipper.hpp" -#endif - -#ifdef use_deprecated - #error "use_deprecated is not disabled in clipper.hpp" -#endif - namespace Slic3r { //----------------------------------------------------------- diff --git a/xs/src/clipper.cpp b/xs/src/clipper.cpp index 9070ecb1b..936e2524d 100644 --- a/xs/src/clipper.cpp +++ b/xs/src/clipper.cpp @@ -2,7 +2,7 @@ * * * Author : Angus Johnson * * Version : 6.1.5 * -* Date : 24 May 2014 * +* Date : 22 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * @@ -898,39 +898,112 @@ TEdge* FindNextLocMin(TEdge* E) } //------------------------------------------------------------------------------ -TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) +TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) { TEdge *EStart = E, *Result = E; TEdge *Horz = 0; cInt StartX; + if (IsHorizontal(*E)) + { + //first we need to be careful here with open paths because this + //may not be a true local minima (ie may be following a skip edge). + //also, watch for adjacent horz edges to start heading left + //before finishing right ... + if (IsClockwise) + { + if (E->Prev->Bot.Y == E->Bot.Y) StartX = E->Prev->Bot.X; + else StartX = E->Prev->Top.X; + } + else + { + if (E->Next->Bot.Y == E->Bot.Y) StartX = E->Next->Bot.X; + else StartX = E->Next->Top.X; + } + if (E->Bot.X != StartX) ReverseHorizontal(*E); + } + + if (Result->OutIdx != Skip) + { + if (IsClockwise) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X == Result->Next->Top.X) + { + if (!IsClockwise) Result = Horz->Prev; + } + else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X) + { + if (!IsClockwise) Result = Horz->Next; + } + else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } - if (E->OutIdx == Skip) + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + } + + if (Result->OutIdx == Skip) { //if edges still remain in the current bound beyond the skip edge then //create another LocMin and call ProcessBound once more - if (NextIsForward) + E = Result; + if (IsClockwise) { while (E->Top.Y == E->Next->Bot.Y) E = E->Next; //don't include top horizontals when parsing a bound a second time, //they will be contained in the opposite bound ... while (E != Result && IsHorizontal(*E)) E = E->Prev; - } - else + } else { while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; while (E != Result && IsHorizontal(*E)) E = E->Next; } - if (E == Result) { - if (NextIsForward) Result = E->Next; + if (IsClockwise) Result = E->Next; else Result = E->Prev; - } - else + } else { //there are more edges in the bound beyond result starting with E - if (NextIsForward) - E = Result->Next; + if (IsClockwise) + E = Result->Next; else E = Result->Prev; LocalMinima* locMin = new LocalMinima; @@ -939,85 +1012,10 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) locMin->LeftBound = 0; locMin->RightBound = E; locMin->RightBound->WindDelta = 0; - Result = ProcessBound(locMin->RightBound, NextIsForward); + Result = ProcessBound(locMin->RightBound, IsClockwise); InsertLocalMinima(locMin); } - return Result; } - - if (IsHorizontal(*E)) - { - //we need to be careful with open paths because this may not be - //a true local minima (ie may be following a skip edge). - //Also, watch for adjacent horz edges that can head left - //before finishing right ... - if (NextIsForward) - { - if (E->Prev->Bot.Y == E->Bot.Y) StartX = E->Prev->Bot.X; - else StartX = E->Prev->Top.X; - } - else - { - if (E->Next->Bot.Y == E->Bot.Y) StartX = E->Next->Bot.X; - else StartX = E->Next->Top.X; - } - if (E->Bot.X != StartX) ReverseHorizontal(*E); - } - - if (NextIsForward) - { - while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) - Result = Result->Next; - if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) - { - //nb: at the top of a bound, horizontals are added to the bound - //only when the preceding edge attaches to the horizontal's left vertex - //unless a Skip edge is encountered when that becomes the top divide - Horz = Result; - while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X == Result->Next->Top.X) - { - if (!NextIsForward) Result = Horz->Prev; - } - else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; - } - while (E != Result) - { - E->NextInLML = E->Next; - if (IsHorizontal(*E) && E != EStart && - E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - E = E->Next; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) - ReverseHorizontal(*E); - Result = Result->Next; //move to the edge just beyond current bound - } else - { - while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) - Result = Result->Prev; - if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) - { - Horz = Result; - while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X) - { - if (!NextIsForward) Result = Horz->Next; - } - else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; - } - - while (E != Result) - { - E->NextInLML = E->Prev; - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - E = E->Prev; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - Result = Result->Prev; //move to the edge just beyond current bound - } - return Result; } //------------------------------------------------------------------------------ @@ -1150,7 +1148,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) } m_edges.push_back(edges); - bool nextIsForward; + bool clockwise; TEdge* EMin = 0; //workaround to avoid an endless loop in the while loop below when @@ -1172,12 +1170,12 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { locMin->LeftBound = E->Prev; locMin->RightBound = E; - nextIsForward = false; //Q.nextInLML = Q.prev + clockwise = false; //Q.nextInLML = Q.prev } else { locMin->LeftBound = E; locMin->RightBound = E->Prev; - nextIsForward = true; //Q.nextInLML = Q.next + clockwise = true; //Q.nextInLML = Q.next } locMin->LeftBound->Side = esLeft; locMin->RightBound->Side = esRight; @@ -1188,15 +1186,15 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) else locMin->LeftBound->WindDelta = 1; locMin->RightBound->WindDelta = -locMin->LeftBound->WindDelta; - E = ProcessBound(locMin->LeftBound, nextIsForward); - TEdge* E2 = ProcessBound(locMin->RightBound, !nextIsForward); + E = ProcessBound(locMin->LeftBound, clockwise); + TEdge* E2 = ProcessBound(locMin->RightBound, !clockwise); if (locMin->LeftBound->OutIdx == Skip) locMin->LeftBound = 0; else if (locMin->RightBound->OutIdx == Skip) locMin->RightBound = 0; InsertLocalMinima(locMin); - if (!nextIsForward) E = E2; + if (!clockwise) E = E2; } return true; } diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp index 9927a97ad..7922abe18 100644 --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -2,7 +2,7 @@ * * * Author : Angus Johnson * * Version : 6.1.5 * -* Date : 24 May 2014 * +* Date : 22 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index 89d173e39..efa051fe7 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -155,7 +155,7 @@ if (0) { # Clipper does not preserve polyline orientation } } -# Clipper bug #96 (our GH #2028) +# Disabled until Clipper bug #96 (our issue #2028) is fixed if (0) { my $subject = Slic3r::Polyline->new( [44735000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000],[44730000,31936670] From 7ea09a007112bbd09447df94fe62efd7deb4eecd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 11:13:53 +0200 Subject: [PATCH 36/48] Bugfix: failure when exporting SVG from object containing multiple islands. Includes regression test. #2050 --- lib/Slic3r/Print.pm | 2 +- t/svg.t | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index a0bba00f5..577618749 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -580,7 +580,7 @@ EOF my @current_layer_slices = (); # sort slices so that the outermost ones come first - my @slices = sort { $a->contour->encloses_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices}; + my @slices = sort { $a->contour->contains_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices}; foreach my $copy (@{$layer->object->copies}) { foreach my $slice (@slices) { my $expolygon = $slice->clone; diff --git a/t/svg.t b/t/svg.t index eb274d59e..9299eeefa 100644 --- a/t/svg.t +++ b/t/svg.t @@ -1,4 +1,4 @@ -use Test::More tests => 1; +use Test::More tests => 2; use strict; use warnings; @@ -17,7 +17,19 @@ use Slic3r::Test; $print->print->export_svg(output_fh => $fh, quiet => 1); $fh->close; }; + die $@ if $@; ok !$@, 'successful SVG export'; } +{ + my $print = Slic3r::Test::init_print('two_hollow_squares'); + eval { + my $fh = IO::Scalar->new(\my $gcode); + $print->print->export_svg(output_fh => $fh, quiet => 1); + $fh->close; + }; + die $@ if $@; + ok !$@, 'successful SVG export of object with two islands'; +} + __END__ From abdf6531f1cc3ca84f8d8f8411fead98754f4675 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 11:50:42 +0200 Subject: [PATCH 37/48] Bugfix: wrong logic for concave_points() and convex_points() --- lib/Slic3r/Polygon.pm | 42 ++++++++++++++++++++++++++++++------------ t/geometry.t | 26 +++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index 124d9ae99..5b13e13ff 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -45,34 +45,52 @@ sub subdivide { return Slic3r::Polygon->new(@new_points); } -# angle is checked on the internal side of the polygon sub concave_points { my ($self, $angle) = @_; $angle //= PI; + # input angle threshold is checked on the internal side of the polygon + # but angle3points measures CCW angle, so we calculate the complementary angle + my $ccw_angle = 2*PI-$angle; + my @points = @$self; my @points_pp = @{$self->pp}; - return [ - map $points[$_], - grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) < $angle, - -1 .. ($#points-1) - ]; + + my @concave = (); + for my $i (-1 .. ($#points-1)) { + next if $points[$i-1]->coincides_with($points[$i]); + # angle is measured in ccw orientation + my $vertex_angle = Slic3r::Geometry::angle3points(@points_pp[$i, $i-1, $i+1]); + if ($vertex_angle <= $ccw_angle) { + push @concave, $points[$i]; + } + } + return [@concave]; } -# angle is checked on the internal side of the polygon sub convex_points { my ($self, $angle) = @_; $angle //= PI; + # input angle threshold is checked on the internal side of the polygon + # but angle3points measures CCW angle, so we calculate the complementary angle + my $ccw_angle = 2*PI-$angle; + my @points = @$self; my @points_pp = @{$self->pp}; - return [ - map $points[$_], - grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) > $angle, - -1 .. ($#points-1) - ]; + + my @convex = (); + for my $i (-1 .. ($#points-1)) { + next if $points[$i-1]->coincides_with($points[$i]); + # angle is measured in ccw orientation + my $vertex_angle = Slic3r::Geometry::angle3points(@points_pp[$i, $i-1, $i+1]); + if ($vertex_angle >= $ccw_angle) { + push @convex, $points[$i]; + } + } + return [@convex]; } 1; \ No newline at end of file diff --git a/t/geometry.t b/t/geometry.t index 7ff2a2aac..a9326782b 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 29; +plan tests => 33; BEGIN { use FindBin; @@ -189,3 +189,27 @@ my $polygons = [ } #========================================================== + +{ + my $square = Slic3r::Polygon->new( + [150,100], + [200,100], + [200,200], + [100,200], + [100,100], + ); + is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon'; + is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square'; +} + +{ + my $square = Slic3r::Polygon->new( + [200,200], + [100,200], + [100,100], + [150,100], + [200,100], + ); + is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon'; + is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square'; +} From 2bce8bb74564a474caf07afbc2808d1d3029ae46 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 12:09:13 +0200 Subject: [PATCH 38/48] Bugfix: detect thin fill loops so that they can be started at the nearest point without unnecessary loops. #1990 --- lib/Slic3r/Layer/Region.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 3d089cc82..2a3fe12f8 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -180,8 +180,7 @@ sub make_perimeters { # and use zigzag). my $w = $gap_size->[2]; my @filled = map { - @{($_->isa('Slic3r::ExtrusionLoop') ? $_->split_at_first_point : $_) - ->polyline + @{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline) ->grow(scale $w/2)}; } @gap_fill; @last = @{diff(\@last, \@filled)}; @@ -438,6 +437,11 @@ sub _fill_gaps { my $loop = Slic3r::ExtrusionLoop->new; $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args)); $polylines[$i] = $loop; + } elsif ($polylines[$i]->is_valid && $polylines[$i]->first_point->coincides_with($polylines[$i]->last_point)) { + # since medial_axis() now returns only Polyline objects, detect loops here + my $loop = Slic3r::ExtrusionLoop->new; + $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i], %path_args)); + $polylines[$i] = $loop; } else { $polylines[$i] = Slic3r::ExtrusionPath->new(polyline => $polylines[$i], %path_args); } From 147385203c05607bc4452c74e0732f1b74005eb7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 12:50:59 +0200 Subject: [PATCH 39/48] Make XS compilation verbose since most users forget to include the build.log file when reporting issues. This also makes Travis-CI logs more useful --- Build.PL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build.PL b/Build.PL index f414a9fda..5c650ce73 100644 --- a/Build.PL +++ b/Build.PL @@ -134,7 +134,7 @@ EOF system './xs/Build', 'distclean'; } } - my $res = system $cpanm, @cpanm_args, '--reinstall', './xs'; + my $res = system $cpanm, @cpanm_args, '--reinstall', '--verbose', './xs'; if ($res != 0) { die "The XS/C++ code failed to compile, aborting\n"; } From e62672f787dbb680a050e33454fe29efa3729af6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 13:10:58 +0200 Subject: [PATCH 40/48] Fix regression in Split from GUI. Might be related to #1998 --- lib/Slic3r/GUI/Plater.pm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 84e17ebe9..a2750193e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -627,12 +627,6 @@ sub split_object { Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it already contains a single part."); return; } - - # remove the original object before spawning the object_loaded event, otherwise - # we'll pass the wrong $obj_idx to it (which won't be recognized after the - # thumbnail thread returns) - $self->remove($obj_idx); - $current_object = $obj_idx = undef; # create a bogus Model object, we only need to instantiate the new Model::Object objects my $new_model = Slic3r::Model->new; @@ -666,6 +660,12 @@ sub split_object { $model_object->center_around_origin; push @model_objects, $model_object; } + + # remove the original object before spawning the object_loaded event, otherwise + # we'll pass the wrong $obj_idx to it (which won't be recognized after the + # thumbnail thread returns) + $self->remove($obj_idx); + $current_object = $obj_idx = undef; # load all model objects at once, otherwise the plate would be rearranged after each one # causing original positions not to be kept From 8290a006ed0829eba2d6b825332b90aff9a45936 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 14:09:42 +0200 Subject: [PATCH 41/48] Bugfix: a lot of extra support material was generated when using both raft layers and support material. #2030 --- lib/Slic3r/Print/SupportMaterial.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 0a5ba7222..4db50c4ae 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -132,7 +132,7 @@ sub contact_area { # If a threshold angle was specified, use a different logic for detecting overhangs. if (defined $threshold_rad || $layer_id < $self->object_config->support_material_enforce_layers - || $self->object_config->raft_layers > 0) { + || ($self->object_config->raft_layers > 0 && $layer_id == 0)) { my $d = defined $threshold_rad ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad)) : 0; From 3d25b9030cd7781299e926ebee0b619119d9a7c1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 15:19:13 +0200 Subject: [PATCH 42/48] Bugfix: movement between objects in sequential printing mode was going too far away. #2013 #2007 --- lib/Slic3r/Print.pm | 4 ++-- t/gcode.t | 19 +++++++++++++++++-- xs/src/Point.cpp | 6 ++++++ xs/src/Point.hpp | 1 + xs/xsp/Point.xsp | 2 ++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 577618749..7c4f1b31f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -953,6 +953,7 @@ sub write_gcode { my $finished_objects = 0; for my $obj_idx (@obj_idx) { + my $object = $self->objects->[$obj_idx]; for my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) { # move to the origin position for the copy we're going to print. # this happens before Z goes down to layer 0 again, so that @@ -960,7 +961,7 @@ sub write_gcode { if ($finished_objects > 0) { $gcodegen->set_shift(map unscale $copy->[$_], X,Y); print $fh $gcodegen->retract; - print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, $gcodegen->config->travel_speed*60, 'move to origin position for next object'); + print $fh $gcodegen->G0($object->_copies_shift->negative, undef, 0, $gcodegen->config->travel_speed*60, 'move to origin position for next object'); } my $buffer = Slic3r::GCode::CoolingBuffer->new( @@ -968,7 +969,6 @@ sub write_gcode { gcodegen => $gcodegen, ); - my $object = $self->objects->[$obj_idx]; my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers}; for my $layer (@layers) { # if we are printing the bottom layer of an object, and we have already finished diff --git a/t/gcode.t b/t/gcode.t index a241bba27..ce9a0f487 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 8; +use Test::More tests => 9; use strict; use warnings; @@ -9,7 +9,7 @@ BEGIN { use List::Util qw(first); use Slic3r; -use Slic3r::Geometry qw(scale); +use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Test; { @@ -46,6 +46,7 @@ use Slic3r::Test; # - complete objects does not crash # - no hard-coded "E" are generated # - Z moves are correctly generated for both objects + # - no travel moves go outside skirt my $config = Slic3r::Config->new_from_defaults; $config->set('gcode_comments', 1); $config->set('complete_objects', 1); @@ -56,16 +57,30 @@ use Slic3r::Test; my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); ok my $gcode = Slic3r::Test::gcode($print), "complete_objects"; my @z_moves = (); + my @travel_moves = (); # array of scaled points + my @extrusions = (); # array of scaled points Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; fail 'unexpected E argument' if defined $args->{E}; if (defined $args->{Z}) { push @z_moves, $args->{Z}; } + + if ($info->{dist_XY}) { + if ($info->{extruding} || $args->{A}) { + push @extrusions, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}); + } else { + push @travel_moves, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}) + if @extrusions; # skip initial travel move to first skirt point + } + } }); my $layer_count = 20/0.4; # cube is 20mm tall is scalar(@z_moves), 2*$layer_count, 'complete_objects generates the correct number of Z moves'; is_deeply [ @z_moves[0..($layer_count-1)] ], [ @z_moves[$layer_count..$#z_moves] ], 'complete_objects generates the correct Z moves'; + + my $convex_hull = convex_hull(\@extrusions); + ok !(defined first { !$convex_hull->contains_point($_) } @travel_moves), 'all travel moves happen within skirt'; } { diff --git a/xs/src/Point.cpp b/xs/src/Point.cpp index 7d0013e22..64677dbf6 100644 --- a/xs/src/Point.cpp +++ b/xs/src/Point.cpp @@ -190,6 +190,12 @@ Point::projection_onto(const Line &line) const } } +Point +Point::negative() const +{ + return Point(-this->x, -this->y); +} + Point operator+(const Point& point1, const Point& point2) { diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index b83a2a238..b87fa3a45 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -43,6 +43,7 @@ class Point double ccw(const Line &line) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; + Point negative() const; #ifdef SLIC3RXS void from_SV(SV* point_sv); diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 12d1928b8..943e1789e 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -35,6 +35,8 @@ %code{% RETVAL = new Point(THIS->projection_onto(*polyline)); %}; Clone projection_onto_line(Line* line) %code{% RETVAL = new Point(THIS->projection_onto(*line)); %}; + Clone negative() + %code{% RETVAL = new Point(THIS->negative()); %}; %{ From 5ca81d699e36af697756ee445da53b2f55d64046 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 16:45:40 +0200 Subject: [PATCH 43/48] Releasing 1.1.3 --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 179405c97..fbf7c6b64 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "1.1.3-dev"; +our $VERSION = "1.1.3"; our $debug = 0; sub debugf { From fb4a9713394414d3eda7e3479e906fb3abc44daf Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 17:12:14 +0200 Subject: [PATCH 44/48] Bump version number --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index fbf7c6b64..2f84d7415 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "1.1.3"; +our $VERSION = "1.1.4-dev"; our $debug = 0; sub debugf { From b02e459c4b293c66ad063135e09266161de4880b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 17:13:24 +0200 Subject: [PATCH 45/48] Attempt to fix compilation issue --- xs/xsp/Polygon.xsp | 1 + xs/xsp/Polyline.xsp | 1 + 2 files changed, 2 insertions(+) diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 11fd04360..93168272b 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -2,6 +2,7 @@ %{ #include +#include "BoundingBox.hpp" #include "Polygon.hpp" %} diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 31f1fd222..e43405df5 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -2,6 +2,7 @@ %{ #include +#include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Polyline.hpp" %} From 6a6439576bed48ee1e5381a39fc913486cde95d4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 17:21:31 +0200 Subject: [PATCH 46/48] Update Clipper to r468. #2028 --- xs/src/clipper.cpp | 193 +++++++++++++++++++++++---------------------- xs/src/clipper.hpp | 2 +- xs/t/11_clipper.t | 6 +- 3 files changed, 102 insertions(+), 99 deletions(-) diff --git a/xs/src/clipper.cpp b/xs/src/clipper.cpp index 936e2524d..249f87c30 100644 --- a/xs/src/clipper.cpp +++ b/xs/src/clipper.cpp @@ -2,7 +2,7 @@ * * * Author : Angus Johnson * * Version : 6.1.5 * -* Date : 22 May 2014 * +* Date : 26 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * @@ -898,112 +898,38 @@ TEdge* FindNextLocMin(TEdge* E) } //------------------------------------------------------------------------------ -TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) +TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) { - TEdge *EStart = E, *Result = E; + TEdge *Result = E; TEdge *Horz = 0; - cInt StartX; - if (IsHorizontal(*E)) - { - //first we need to be careful here with open paths because this - //may not be a true local minima (ie may be following a skip edge). - //also, watch for adjacent horz edges to start heading left - //before finishing right ... - if (IsClockwise) - { - if (E->Prev->Bot.Y == E->Bot.Y) StartX = E->Prev->Bot.X; - else StartX = E->Prev->Top.X; - } - else - { - if (E->Next->Bot.Y == E->Bot.Y) StartX = E->Next->Bot.X; - else StartX = E->Next->Top.X; - } - if (E->Bot.X != StartX) ReverseHorizontal(*E); - } - - if (Result->OutIdx != Skip) - { - if (IsClockwise) - { - while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) - Result = Result->Next; - if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) - { - //nb: at the top of a bound, horizontals are added to the bound - //only when the preceding edge attaches to the horizontal's left vertex - //unless a Skip edge is encountered when that becomes the top divide - Horz = Result; - while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X == Result->Next->Top.X) - { - if (!IsClockwise) Result = Horz->Prev; - } - else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; - } - while (E != Result) - { - E->NextInLML = E->Next; - if (IsHorizontal(*E) && E != EStart && - E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - E = E->Next; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) - ReverseHorizontal(*E); - Result = Result->Next; //move to the edge just beyond current bound - } else - { - while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) - Result = Result->Prev; - if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) - { - Horz = Result; - while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X) - { - if (!IsClockwise) Result = Horz->Next; - } - else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; - } - while (E != Result) - { - E->NextInLML = E->Prev; - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - E = E->Prev; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - Result = Result->Prev; //move to the edge just beyond current bound - } - } - - if (Result->OutIdx == Skip) + if (E->OutIdx == Skip) { //if edges still remain in the current bound beyond the skip edge then //create another LocMin and call ProcessBound once more - E = Result; - if (IsClockwise) + if (NextIsForward) { while (E->Top.Y == E->Next->Bot.Y) E = E->Next; //don't include top horizontals when parsing a bound a second time, //they will be contained in the opposite bound ... while (E != Result && IsHorizontal(*E)) E = E->Prev; - } else + } + else { while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; while (E != Result && IsHorizontal(*E)) E = E->Next; } + if (E == Result) { - if (IsClockwise) Result = E->Next; + if (NextIsForward) Result = E->Next; else Result = E->Prev; - } else + } + else { //there are more edges in the bound beyond result starting with E - if (IsClockwise) - E = Result->Next; + if (NextIsForward) + E = Result->Next; else E = Result->Prev; LocalMinima* locMin = new LocalMinima; @@ -1011,11 +937,88 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) locMin->Y = E->Bot.Y; locMin->LeftBound = 0; locMin->RightBound = E; - locMin->RightBound->WindDelta = 0; - Result = ProcessBound(locMin->RightBound, IsClockwise); + E->WindDelta = 0; + Result = ProcessBound(E, NextIsForward); InsertLocalMinima(locMin); } + return Result; } + + TEdge *EStart; + + if (IsHorizontal(*E)) + { + //We need to be careful with open paths because this may not be a + //true local minima (ie E may be following a skip edge). + //Also, consecutive horz. edges may start heading left before going right. + if (NextIsForward) + EStart = E->Prev; + else + EStart = E->Next; + if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge + { + if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) + ReverseHorizontal(*E); + } + else if (EStart->Bot.X != E->Bot.X) + ReverseHorizontal(*E); + } + + EStart = E; + if (NextIsForward) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X == Result->Next->Top.X) + { + if (!NextIsForward) Result = Horz->Prev; + } + else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X) + { + if (!NextIsForward) Result = Horz->Next; + } + else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } + + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + return Result; } //------------------------------------------------------------------------------ @@ -1148,7 +1151,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) } m_edges.push_back(edges); - bool clockwise; + bool leftBoundIsForward; TEdge* EMin = 0; //workaround to avoid an endless loop in the while loop below when @@ -1170,12 +1173,12 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { locMin->LeftBound = E->Prev; locMin->RightBound = E; - clockwise = false; //Q.nextInLML = Q.prev + leftBoundIsForward = false; //Q.nextInLML = Q.prev } else { locMin->LeftBound = E; locMin->RightBound = E->Prev; - clockwise = true; //Q.nextInLML = Q.next + leftBoundIsForward = true; //Q.nextInLML = Q.next } locMin->LeftBound->Side = esLeft; locMin->RightBound->Side = esRight; @@ -1186,15 +1189,15 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) else locMin->LeftBound->WindDelta = 1; locMin->RightBound->WindDelta = -locMin->LeftBound->WindDelta; - E = ProcessBound(locMin->LeftBound, clockwise); - TEdge* E2 = ProcessBound(locMin->RightBound, !clockwise); + E = ProcessBound(locMin->LeftBound, leftBoundIsForward); + TEdge* E2 = ProcessBound(locMin->RightBound, !leftBoundIsForward); if (locMin->LeftBound->OutIdx == Skip) locMin->LeftBound = 0; else if (locMin->RightBound->OutIdx == Skip) locMin->RightBound = 0; InsertLocalMinima(locMin); - if (!clockwise) E = E2; + if (!leftBoundIsForward) E = E2; } return true; } diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp index 7922abe18..9927a97ad 100644 --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -2,7 +2,7 @@ * * * Author : Angus Johnson * * Version : 6.1.5 * -* Date : 22 May 2014 * +* Date : 24 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index efa051fe7..e9905323d 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 18; +use Test::More tests => 19; my $square = Slic3r::Polygon->new( # ccw [200, 100], @@ -155,8 +155,8 @@ if (0) { # Clipper does not preserve polyline orientation } } -# Disabled until Clipper bug #96 (our issue #2028) is fixed -if (0) { +# Clipper bug #96 (our GH #2028) +{ my $subject = Slic3r::Polyline->new( [44735000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000],[44730000,31936670] ); From 2ac40f95475d3dc3568225c684a80253e42dd472 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 20:00:59 +0200 Subject: [PATCH 47/48] Revert "Update Clipper to r468. #2028" This reverts commit 6a6439576bed48ee1e5381a39fc913486cde95d4. --- xs/src/clipper.cpp | 193 ++++++++++++++++++++++----------------------- xs/src/clipper.hpp | 2 +- xs/t/11_clipper.t | 6 +- 3 files changed, 99 insertions(+), 102 deletions(-) diff --git a/xs/src/clipper.cpp b/xs/src/clipper.cpp index 249f87c30..936e2524d 100644 --- a/xs/src/clipper.cpp +++ b/xs/src/clipper.cpp @@ -2,7 +2,7 @@ * * * Author : Angus Johnson * * Version : 6.1.5 * -* Date : 26 May 2014 * +* Date : 22 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * @@ -898,38 +898,112 @@ TEdge* FindNextLocMin(TEdge* E) } //------------------------------------------------------------------------------ -TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) +TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) { - TEdge *Result = E; + TEdge *EStart = E, *Result = E; TEdge *Horz = 0; + cInt StartX; + if (IsHorizontal(*E)) + { + //first we need to be careful here with open paths because this + //may not be a true local minima (ie may be following a skip edge). + //also, watch for adjacent horz edges to start heading left + //before finishing right ... + if (IsClockwise) + { + if (E->Prev->Bot.Y == E->Bot.Y) StartX = E->Prev->Bot.X; + else StartX = E->Prev->Top.X; + } + else + { + if (E->Next->Bot.Y == E->Bot.Y) StartX = E->Next->Bot.X; + else StartX = E->Next->Top.X; + } + if (E->Bot.X != StartX) ReverseHorizontal(*E); + } + + if (Result->OutIdx != Skip) + { + if (IsClockwise) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X == Result->Next->Top.X) + { + if (!IsClockwise) Result = Horz->Prev; + } + else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X) + { + if (!IsClockwise) Result = Horz->Next; + } + else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } - if (E->OutIdx == Skip) + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + } + + if (Result->OutIdx == Skip) { //if edges still remain in the current bound beyond the skip edge then //create another LocMin and call ProcessBound once more - if (NextIsForward) + E = Result; + if (IsClockwise) { while (E->Top.Y == E->Next->Bot.Y) E = E->Next; //don't include top horizontals when parsing a bound a second time, //they will be contained in the opposite bound ... while (E != Result && IsHorizontal(*E)) E = E->Prev; - } - else + } else { while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; while (E != Result && IsHorizontal(*E)) E = E->Next; } - if (E == Result) { - if (NextIsForward) Result = E->Next; + if (IsClockwise) Result = E->Next; else Result = E->Prev; - } - else + } else { //there are more edges in the bound beyond result starting with E - if (NextIsForward) - E = Result->Next; + if (IsClockwise) + E = Result->Next; else E = Result->Prev; LocalMinima* locMin = new LocalMinima; @@ -937,88 +1011,11 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) locMin->Y = E->Bot.Y; locMin->LeftBound = 0; locMin->RightBound = E; - E->WindDelta = 0; - Result = ProcessBound(E, NextIsForward); + locMin->RightBound->WindDelta = 0; + Result = ProcessBound(locMin->RightBound, IsClockwise); InsertLocalMinima(locMin); } - return Result; } - - TEdge *EStart; - - if (IsHorizontal(*E)) - { - //We need to be careful with open paths because this may not be a - //true local minima (ie E may be following a skip edge). - //Also, consecutive horz. edges may start heading left before going right. - if (NextIsForward) - EStart = E->Prev; - else - EStart = E->Next; - if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge - { - if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) - ReverseHorizontal(*E); - } - else if (EStart->Bot.X != E->Bot.X) - ReverseHorizontal(*E); - } - - EStart = E; - if (NextIsForward) - { - while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) - Result = Result->Next; - if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) - { - //nb: at the top of a bound, horizontals are added to the bound - //only when the preceding edge attaches to the horizontal's left vertex - //unless a Skip edge is encountered when that becomes the top divide - Horz = Result; - while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X == Result->Next->Top.X) - { - if (!NextIsForward) Result = Horz->Prev; - } - else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; - } - while (E != Result) - { - E->NextInLML = E->Next; - if (IsHorizontal(*E) && E != EStart && - E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - E = E->Next; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) - ReverseHorizontal(*E); - Result = Result->Next; //move to the edge just beyond current bound - } else - { - while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) - Result = Result->Prev; - if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) - { - Horz = Result; - while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X) - { - if (!NextIsForward) Result = Horz->Next; - } - else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; - } - - while (E != Result) - { - E->NextInLML = E->Prev; - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - E = E->Prev; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - Result = Result->Prev; //move to the edge just beyond current bound - } - return Result; } //------------------------------------------------------------------------------ @@ -1151,7 +1148,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) } m_edges.push_back(edges); - bool leftBoundIsForward; + bool clockwise; TEdge* EMin = 0; //workaround to avoid an endless loop in the while loop below when @@ -1173,12 +1170,12 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { locMin->LeftBound = E->Prev; locMin->RightBound = E; - leftBoundIsForward = false; //Q.nextInLML = Q.prev + clockwise = false; //Q.nextInLML = Q.prev } else { locMin->LeftBound = E; locMin->RightBound = E->Prev; - leftBoundIsForward = true; //Q.nextInLML = Q.next + clockwise = true; //Q.nextInLML = Q.next } locMin->LeftBound->Side = esLeft; locMin->RightBound->Side = esRight; @@ -1189,15 +1186,15 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) else locMin->LeftBound->WindDelta = 1; locMin->RightBound->WindDelta = -locMin->LeftBound->WindDelta; - E = ProcessBound(locMin->LeftBound, leftBoundIsForward); - TEdge* E2 = ProcessBound(locMin->RightBound, !leftBoundIsForward); + E = ProcessBound(locMin->LeftBound, clockwise); + TEdge* E2 = ProcessBound(locMin->RightBound, !clockwise); if (locMin->LeftBound->OutIdx == Skip) locMin->LeftBound = 0; else if (locMin->RightBound->OutIdx == Skip) locMin->RightBound = 0; InsertLocalMinima(locMin); - if (!leftBoundIsForward) E = E2; + if (!clockwise) E = E2; } return true; } diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp index 9927a97ad..7922abe18 100644 --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -2,7 +2,7 @@ * * * Author : Angus Johnson * * Version : 6.1.5 * -* Date : 24 May 2014 * +* Date : 22 May 2014 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2014 * * * diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index e9905323d..efa051fe7 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 19; +use Test::More tests => 18; my $square = Slic3r::Polygon->new( # ccw [200, 100], @@ -155,8 +155,8 @@ if (0) { # Clipper does not preserve polyline orientation } } -# Clipper bug #96 (our GH #2028) -{ +# Disabled until Clipper bug #96 (our issue #2028) is fixed +if (0) { my $subject = Slic3r::Polyline->new( [44735000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000],[44730000,31936670] ); From 98b8936ee2cf6049ff55850519c2c2c738fe45a2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 26 May 2014 23:51:58 +0200 Subject: [PATCH 48/48] Automatically disable retract_layer_change when using spiral_vase --- lib/Slic3r/Config.pm | 7 +------ xs/src/Config.cpp | 9 +++++++++ xs/src/Config.hpp | 1 + xs/src/PrintConfig.hpp | 7 +++++++ xs/t/15_config.t | 10 +++++++++- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 113d8ce74..ae1912120 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -335,7 +335,7 @@ sub validate { die "Can't make less than one perimeter when spiral vase mode is enabled\n" if $self->perimeters < 1; - die "Spiral vase mode is not compatible with non-zero fill density\n" + die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n" if $self->fill_density > 0; die "Spiral vase mode is not compatible with top solid layers\n" @@ -343,11 +343,6 @@ sub validate { die "Spiral vase mode is not compatible with support material\n" if $self->support_material || $self->support_material_enforce_layers > 0; - - # This should be enforce automatically only on spiral layers and - # done on the others - die "Spiral vase mode is not compatible with retraction on layer change\n" - if defined first { $_ } @{ $self->retract_layer_change }; } # extrusion widths diff --git a/xs/src/Config.cpp b/xs/src/Config.cpp index 279862100..4d03b36a0 100644 --- a/xs/src/Config.cpp +++ b/xs/src/Config.cpp @@ -320,6 +320,15 @@ DynamicConfig::option(const t_config_option_key opt_key, bool create) { return this->options[opt_key]; } +template +T* +DynamicConfig::opt(const t_config_option_key opt_key, bool create) { + return dynamic_cast(this->option(opt_key, create)); +} +template ConfigOptionInt* DynamicConfig::opt(const t_config_option_key opt_key, bool create); +template ConfigOptionBool* DynamicConfig::opt(const t_config_option_key opt_key, bool create); +template ConfigOptionBools* DynamicConfig::opt(const t_config_option_key opt_key, bool create); + const ConfigOption* DynamicConfig::option(const t_config_option_key opt_key) const { return const_cast(this)->option(opt_key, false); diff --git a/xs/src/Config.hpp b/xs/src/Config.hpp index fe521746e..df678131b 100644 --- a/xs/src/Config.hpp +++ b/xs/src/Config.hpp @@ -491,6 +491,7 @@ class DynamicConfig : public ConfigBase DynamicConfig& operator= (DynamicConfig other); void swap(DynamicConfig &other); ~DynamicConfig(); + template T* opt(const t_config_option_key opt_key, bool create = false); ConfigOption* option(const t_config_option_key opt_key, bool create = false); const ConfigOption* option(const t_config_option_key opt_key) const; void keys(t_config_option_keys *keys) const; diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index cd31d899b..0aef25c09 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -985,6 +985,13 @@ class DynamicPrintConfig : public DynamicConfig if (!this->has("support_material_interface_extruder")) this->option("support_material_interface_extruder", true)->setInt(extruder); } + if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { + { + // this should be actually done only on the spiral layers instead of all + ConfigOptionBools* opt = this->opt("retract_layer_change", true); + opt->values.assign(opt->values.size(), false); // set all values to false + } + } }; }; diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 282670080..9541f4310 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 114; +use Test::More tests => 115; foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { $config->set('layer_height', 0.3); @@ -176,4 +176,12 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { is $config->get('perimeter_extruder'), 3, 'defined extruder is not overwritten by default extruder'; } +{ + my $config = Slic3r::Config->new; + $config->set('spiral_vase', 1); + $config->set('retract_layer_change', [1,0]); + $config->normalize; + is_deeply $config->get('retract_layer_change'), [0,0], 'retract_layer_change is disabled with spiral_vase'; +} + __END__