From 23848492ce78aa4f69df94ce6d78b1abb0846922 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 26 Nov 2014 22:46:51 +0100 Subject: [PATCH 001/107] Fixed regression causing crash when using avoid_crossing_perimeters with multiple object, caused by recent refactorings. Added regression test --- lib/Slic3r/GCode.pm | 4 +++- t/avoid_crossing_perimeters.t | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 t/avoid_crossing_perimeters.t diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 0561e4cd8..c8b72a8ad 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -560,6 +560,8 @@ has '_layer_mp' => (is => 'rw'); has 'new_object' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move has 'straight_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move +use Slic3r::Geometry qw(scale); + sub init_external_mp { my ($self, $islands) = @_; $self->_external_mp(Slic3r::MotionPlanner->new($islands)); @@ -612,7 +614,7 @@ sub _plan { # append the actual path and return # use G1 because we rely on paths being straight (G0 may make round paths) $gcode .= join '', - map $gcodegen->writer->travel_to_xy($self->point_to_gcode($_->b), $comment), + map $gcodegen->writer->travel_to_xy($gcodegen->point_to_gcode($_->b), $comment), @{$travel->lines}; return $gcode; diff --git a/t/avoid_crossing_perimeters.t b/t/avoid_crossing_perimeters.t new file mode 100644 index 000000000..dd6c3e7b6 --- /dev/null +++ b/t/avoid_crossing_perimeters.t @@ -0,0 +1,21 @@ +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::Test; + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('avoid_crossing_perimeters', 2); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); + ok my $gcode = Slic3r::Test::gcode($print), "no crash with avoid_crossing_perimeters and multiple objects"; +} + +__END__ From 7a7d00c8d62e6783365550df7f99219f2503a024 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 27 Nov 2014 00:38:05 +0100 Subject: [PATCH 002/107] Rename solid_fill_pattern to external_fill_pattern and clarify tooltip --- lib/Slic3r/Config.pm | 8 ++++---- lib/Slic3r/Fill.pm | 13 +++++------- lib/Slic3r/GUI/Tab.pm | 4 ++-- slic3r.pl | 2 +- t/fill.t | 2 +- xs/src/libslic3r/PrintConfig.cpp | 35 ++++++++++++++++---------------- xs/src/libslic3r/PrintConfig.hpp | 6 +++--- xs/src/libslic3r/PrintObject.cpp | 4 ++-- xs/src/libslic3r/Surface.cpp | 3 ++- 9 files changed, 38 insertions(+), 39 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 529984729..61d4bb97c 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -250,14 +250,14 @@ sub validate { die "Invalid value for --fill-pattern\n" if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}}; - # --solid-fill-pattern - die "Invalid value for --solid-fill-pattern\n" - if !first { $_ eq $self->solid_fill_pattern } @{$Options->{solid_fill_pattern}{values}}; + # --external-fill-pattern + die "Invalid value for --external-fill-pattern\n" + if !first { $_ eq $self->external_fill_pattern } @{$Options->{external_fill_pattern}{values}}; # --fill-density die "The selected fill pattern is not supposed to work at 100% density\n" if $self->fill_density == 100 - && !first { $_ eq $self->fill_pattern } @{$Options->{solid_fill_pattern}{values}}; + && !first { $_ eq $self->fill_pattern } @{$Options->{external_fill_pattern}{values}}; # --infill-every-layers die "Invalid value for --infill-every-layers\n" diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index de322153d..82c2ec6cc 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -83,7 +83,7 @@ sub make_fill { ? $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL)->width : $solid_infill_flow->width; $pattern[$i] = $groups[$i][0]->is_external - ? $layerm->config->solid_fill_pattern + ? $layerm->config->external_fill_pattern : 'rectilinear'; } else { $is_solid[$i] = 0; @@ -190,14 +190,11 @@ sub make_fill { my $is_bridge = $layerm->id > 0 && $surface->is_bridge; my $is_solid = $surface->is_solid; - # force 100% density and rectilinear fill for external surfaces - if ($surface->surface_type != S_TYPE_INTERNAL) { + if ($surface->is_solid) { $density = 100; - $filler = $layerm->config->solid_fill_pattern; - if ($is_bridge) { - $filler = 'rectilinear'; - } elsif ($surface->surface_type == S_TYPE_INTERNALSOLID) { - $filler = 'rectilinear'; + $filler = 'rectilinear'; + if ($surface->is_external) { + $filler = $layerm->config->external_fill_pattern; } } else { next SURFACE unless $density > 0; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 0121a8b20..ecb476fd5 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -432,7 +432,7 @@ sub build { top_solid_layers bottom_solid_layers extra_perimeters avoid_crossing_perimeters thin_walls overhangs seam_position external_perimeters_first - fill_density fill_pattern solid_fill_pattern + fill_density fill_pattern external_fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters infill_first @@ -505,7 +505,7 @@ sub build { my $optgroup = $page->new_optgroup('Infill'); $optgroup->append_single_option_line('fill_density'); $optgroup->append_single_option_line('fill_pattern'); - $optgroup->append_single_option_line('solid_fill_pattern'); + $optgroup->append_single_option_line('external_fill_pattern'); } { my $optgroup = $page->new_optgroup('Reducing printing time'); diff --git a/slic3r.pl b/slic3r.pl index cd8ddd371..70e109215 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -364,7 +364,7 @@ $j --fill-density Infill density (range: 0%-100%, default: $config->{fill_density}%) --fill-angle Infill angle in degrees (range: 0-90, default: $config->{fill_angle}) --fill-pattern Pattern to use to fill non-solid layers (default: $config->{fill_pattern}) - --solid-fill-pattern Pattern to use to fill solid layers (default: $config->{solid_fill_pattern}) + --external-fill-pattern Pattern to use to fill solid layers (default: $config->{external_fill_pattern}) --start-gcode Load initial G-code from the supplied file. This will overwrite the default command (home all axes [G28]). --end-gcode Load final G-code from the supplied file. This will overwrite diff --git a/t/fill.t b/t/fill.t index f0fe6f903..24370b660 100644 --- a/t/fill.t +++ b/t/fill.t @@ -171,7 +171,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my $config = Slic3r::Config->new_from_defaults; $config->set('fill_pattern', $pattern); - $config->set('solid_fill_pattern', $pattern); + $config->set('external_fill_pattern', $pattern); $config->set('perimeters', 1); $config->set('skirts', 0); $config->set('fill_density', 20); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 0ced2c832..54deaee82 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -115,6 +115,24 @@ PrintConfigDef::build_def() { Options["end_gcode"].full_width = true; Options["end_gcode"].height = 120; + Options["external_fill_pattern"].type = coEnum; + Options["external_fill_pattern"].label = "Top/bottom fill pattern"; + Options["external_fill_pattern"].category = "Infill"; + Options["external_fill_pattern"].tooltip = "Fill pattern for top/bottom infill. This only affects the external visible layer, and not its adjacent solid shells."; + Options["external_fill_pattern"].cli = "external-fill-pattern=s"; + Options["external_fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); + Options["external_fill_pattern"].enum_values.push_back("rectilinear"); + Options["external_fill_pattern"].enum_values.push_back("concentric"); + Options["external_fill_pattern"].enum_values.push_back("hilbertcurve"); + Options["external_fill_pattern"].enum_values.push_back("archimedeanchords"); + Options["external_fill_pattern"].enum_values.push_back("octagramspiral"); + Options["external_fill_pattern"].enum_labels.push_back("rectilinear"); + Options["external_fill_pattern"].enum_labels.push_back("concentric"); + Options["external_fill_pattern"].enum_labels.push_back("hilbertcurve (slow)"); + Options["external_fill_pattern"].enum_labels.push_back("archimedeanchords (slow)"); + Options["external_fill_pattern"].enum_labels.push_back("octagramspiral (slow)"); + Options["external_fill_pattern"].aliases.push_back("solid_fill_pattern"); + Options["external_perimeter_extrusion_width"].type = coFloatOrPercent; Options["external_perimeter_extrusion_width"].label = "External perimeters"; Options["external_perimeter_extrusion_width"].category = "Extrusion Width"; @@ -668,23 +686,6 @@ PrintConfigDef::build_def() { Options["small_perimeter_speed"].cli = "small-perimeter-speed=s"; Options["small_perimeter_speed"].ratio_over = "perimeter_speed"; - Options["solid_fill_pattern"].type = coEnum; - Options["solid_fill_pattern"].label = "Top/bottom fill pattern"; - Options["solid_fill_pattern"].category = "Infill"; - Options["solid_fill_pattern"].tooltip = "Fill pattern for top/bottom infill."; - Options["solid_fill_pattern"].cli = "solid-fill-pattern=s"; - Options["solid_fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); - Options["solid_fill_pattern"].enum_values.push_back("rectilinear"); - Options["solid_fill_pattern"].enum_values.push_back("concentric"); - Options["solid_fill_pattern"].enum_values.push_back("hilbertcurve"); - Options["solid_fill_pattern"].enum_values.push_back("archimedeanchords"); - Options["solid_fill_pattern"].enum_values.push_back("octagramspiral"); - Options["solid_fill_pattern"].enum_labels.push_back("rectilinear"); - Options["solid_fill_pattern"].enum_labels.push_back("concentric"); - Options["solid_fill_pattern"].enum_labels.push_back("hilbertcurve (slow)"); - Options["solid_fill_pattern"].enum_labels.push_back("archimedeanchords (slow)"); - Options["solid_fill_pattern"].enum_labels.push_back("octagramspiral (slow)"); - Options["solid_infill_below_area"].type = coFloat; Options["solid_infill_below_area"].label = "Solid infill threshold area"; Options["solid_infill_below_area"].category = "Infill"; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index c1e6ac69f..447948a26 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -205,6 +205,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig ConfigOptionInt bottom_solid_layers; ConfigOptionFloat bridge_flow_ratio; ConfigOptionFloat bridge_speed; + ConfigOptionEnum external_fill_pattern; ConfigOptionFloatOrPercent external_perimeter_extrusion_width; ConfigOptionFloatOrPercent external_perimeter_speed; ConfigOptionBool external_perimeters_first; @@ -223,7 +224,6 @@ class PrintRegionConfig : public virtual StaticPrintConfig 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; @@ -237,6 +237,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig this->bottom_solid_layers.value = 3; this->bridge_flow_ratio.value = 1; this->bridge_speed.value = 60; + this->external_fill_pattern.value = ipRectilinear; this->external_perimeter_extrusion_width.value = 0; this->external_perimeter_extrusion_width.percent = false; this->external_perimeter_speed.value = 70; @@ -260,7 +261,6 @@ class PrintRegionConfig : public virtual StaticPrintConfig 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; @@ -279,6 +279,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig if (opt_key == "bottom_solid_layers") return &this->bottom_solid_layers; if (opt_key == "bridge_flow_ratio") return &this->bridge_flow_ratio; if (opt_key == "bridge_speed") return &this->bridge_speed; + if (opt_key == "external_fill_pattern") return &this->external_fill_pattern; if (opt_key == "external_perimeter_extrusion_width") return &this->external_perimeter_extrusion_width; if (opt_key == "external_perimeter_speed") return &this->external_perimeter_speed; if (opt_key == "external_perimeters_first") return &this->external_perimeters_first; @@ -297,7 +298,6 @@ class PrintRegionConfig : public virtual StaticPrintConfig 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; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 54ddea33d..b4ce83e06 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -234,9 +234,9 @@ PrintObject::invalidate_state_by_config_options(const std::vectorsurface_type == stTop || this->surface_type == stBottom || this->surface_type == stBottomBridge - || this->surface_type == stInternalSolid; + || this->surface_type == stInternalSolid + || this->surface_type == stInternalBridge; } bool From 98cb9f0e18767b772c409ceff802107a89c9df70 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 18:09:06 +0100 Subject: [PATCH 003/107] Refactoring: moved G-code export logic into new Slic3r::Print::GCode class. Removed Slic3r::GCode::Layer class. Fixes the order of post-processing filters so that cooling buffer is applied before any other filter whose logic is affected by speeds --- lib/Slic3r.pm | 2 +- lib/Slic3r/GCode/Layer.pm | 251 ------------------- lib/Slic3r/Print.pm | 243 +------------------ lib/Slic3r/Print/GCode.pm | 491 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 497 insertions(+), 490 deletions(-) delete mode 100644 lib/Slic3r/GCode/Layer.pm create mode 100644 lib/Slic3r/Print/GCode.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index d9b159118..e2c2646ef 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -48,7 +48,6 @@ use Slic3r::Format::STL; use Slic3r::GCode; use Slic3r::GCode::ArcFitting; use Slic3r::GCode::CoolingBuffer; -use Slic3r::GCode::Layer; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::PlaceholderParser; use Slic3r::GCode::PressureRegulator; @@ -65,6 +64,7 @@ use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; use Slic3r::Print; +use Slic3r::Print::GCode; use Slic3r::Print::Object; use Slic3r::Print::Simple; use Slic3r::Print::SupportMaterial; diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm deleted file mode 100644 index b13eab6a0..000000000 --- a/lib/Slic3r/GCode/Layer.pm +++ /dev/null @@ -1,251 +0,0 @@ -package Slic3r::GCode::Layer; -use Moo; - -use List::Util qw(first); -use Slic3r::Geometry qw(X Y unscale); - -has 'print' => (is => 'ro', required => 1, handles => [qw(config)]); -has 'gcodegen' => (is => 'ro', required => 1); -has 'origin' => (is => 'ro', default => sub { Slic3r::Pointf->new(0,0) }); - -has 'spiralvase' => (is => 'lazy'); -has 'vibration_limit' => (is => 'lazy'); -has 'arc_fitting' => (is => 'lazy'); -has 'pressure_regulator' => (is => 'lazy'); -has 'skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1 -has 'brim_done' => (is => 'rw'); -has 'second_layer_things_done' => (is => 'rw'); -has '_last_obj_copy' => (is => 'rw'); - -sub _build_spiralvase { - my $self = shift; - - return $self->print->config->spiral_vase - ? Slic3r::GCode::SpiralVase->new(config => $self->print->config) - : undef; -} - -sub _build_vibration_limit { - my $self = shift; - - return $self->print->config->vibration_limit - ? Slic3r::GCode::VibrationLimit->new(config => $self->print->config) - : undef; -} - -sub _build_arc_fitting { - my $self = shift; - - return $self->print->config->gcode_arcs - ? Slic3r::GCode::ArcFitting->new(config => $self->print->config) - : undef; -} - -sub _build_pressure_regulator { - my $self = shift; - - return $self->print->config->pressure_advance > 0 - ? Slic3r::GCode::PressureRegulator->new(config => $self->print->config) - : undef; -} - -sub process_layer { - my $self = shift; - my ($layer, $object_copies) = @_; - 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) { - $self->spiralvase->enable( - ($layer->id > 0 || $self->print->config->brim_width == 0) - && ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1) - && !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions}) - && !defined(first { @{$_->perimeters} > 1 } @{$layer->regions}) - && !defined(first { @{$_->fills} > 0 } @{$layer->regions}) - ); - } - - # if we're going to apply spiralvase to this layer, disable loop clipping - $self->gcodegen->enable_loop_clipping(!defined $self->spiralvase || !$self->spiralvase->enable); - - if (!$self->second_layer_things_done && $layer->id == 1) { - for my $extruder (@{$self->gcodegen->writer->extruders}) { - my $temperature = $self->config->get_at('temperature', $extruder->id); - $gcode .= $self->gcodegen->writer->set_temperature($temperature, 0, $extruder->id) - if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id); - } - $gcode .= $self->gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature) - if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature; - $self->second_layer_things_done(1); - } - - # set new layer - this will change Z and force a retraction if retract_layer_change is enabled - $gcode .= $self->gcodegen->change_layer($layer); - $gcode .= $self->gcodegen->placeholder_parser->process($self->print->config->layer_gcode, { - layer_num => $layer->id, - }) . "\n" if $self->print->config->layer_gcode; - - # extrude skirt - if (((values %{$self->skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) - && !$self->skirt_done->{$layer->print_z}) { - $self->gcodegen->set_origin($self->origin); - my @extruder_ids = map { $_->id } @{$self->gcodegen->writer->extruders}; - $gcode .= $self->gcodegen->set_extruder($extruder_ids[0]); - # skip skirt if we have a large brim - if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) { - # distribute skirt loops across all extruders - my @skirt_loops = @{$self->print->skirt}; - for my $i (0 .. $#skirt_loops) { - # when printing layers > 0 ignore 'min_skirt_length' and - # just use the 'skirts' setting; also just use the current extruder - last if ($layer->id > 0) && ($i >= $self->print->config->skirts); - 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', $object->config->support_material_speed); - } - } - $self->skirt_done->{$layer->print_z} = 1; - $self->gcodegen->avoid_crossing_perimeters->straight_once(1); - } - - # extrude brim - if (!$self->brim_done) { - $gcode .= $self->gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1); - $self->gcodegen->set_origin($self->origin); - $gcode .= $self->gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) - for @{$self->print->brim}; - $self->brim_done(1); - $self->gcodegen->avoid_crossing_perimeters->straight_once(1); - } - - for my $copy (@$object_copies) { - $self->gcodegen->avoid_crossing_perimeters->new_object(1) if ($self->_last_obj_copy // '') ne "$copy"; - $self->_last_obj_copy("$copy"); - - $self->gcodegen->set_origin(Slic3r::Pointf->new(map $self->origin->[$_] + unscale $copy->[$_], X,Y)); - - # extrude support material before other things because it might use a lower Z - # and also because we avoid travelling on other things when printing it - 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->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->get_abs_value('support_material_speed')) - for @{$layer->support_fills->chained_path_from($self->gcodegen->last_pos, 0)}; - } - } - - # tweak region ordering to save toolchanges - my @region_ids = 0 .. ($self->print->region_count-1); - if ($self->gcodegen->writer->multiple_extruders) { - my $last_extruder_id = $self->gcodegen->writer->extruder->id; - my $best_region_id = first { $self->print->regions->[$_]->config->perimeter_extruder-1 == $last_extruder_id } @region_ids; - @region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id; - } - - foreach my $region_id (@region_ids) { - my $layerm = $layer->regions->[$region_id] or next; - my $region = $self->print->regions->[$region_id]; - $self->gcodegen->config->apply_region_config($region->config); - - # group extrusions by island - my @perimeters_by_island = map [], 0..$#{$layer->slices}; # slice idx => @perimeters - my @infill_by_island = map [], 0..$#{$layer->slices}; # slice idx => @fills - - # NOTE: we assume $layer->slices was already ordered with chained_path()! - - PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { - for my $i (0 .. $#{$layer->slices}-1) { - if ($layer->slices->[$i]->contour->contains_point($perimeter->first_point)) { - push @{ $perimeters_by_island[$i] }, $perimeter; - next PERIMETER; - } - } - push @{ $perimeters_by_island[-1] }, $perimeter; # optimization - } - FILL: foreach my $fill (@{$layerm->fills}) { - for my $i (0 .. $#{$layer->slices}-1) { - if ($layer->slices->[$i]->contour->contains_point($fill->first_point)) { - push @{ $infill_by_island[$i] }, $fill; - next FILL; - } - } - push @{ $infill_by_island[-1] }, $fill; # optimization - } - - for my $i (0 .. $#{$layer->slices}) { - # give priority to infill if we were already using its extruder and it wouldn't - # be good for perimeters - if ($self->print->config->infill_first - || ($self->gcodegen->writer->multiple_extruders && $region->config->infill_extruder-1 == $self->gcodegen->writer->extruder->id && $region->config->infill_extruder != $region->config->perimeter_extruder)) { - $gcode .= $self->_extrude_infill($infill_by_island[$i], $region); - $gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region); - } else { - $gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region); - $gcode .= $self->_extrude_infill($infill_by_island[$i], $region); - } - } - } - } - - # apply spiral vase post-processing if this layer contains suitable geometry - # (we must feed all the G-code into the post-processor, including the first - # bottom non-spiral layers otherwise it will mess with positions) - $gcode = $self->spiralvase->process_layer($gcode) - if defined $self->spiralvase; - - # apply vibration limit if enabled - $gcode = $self->vibration_limit->process($gcode) - if $self->print->config->vibration_limit != 0; - - # apply pressure regulation if enabled - $gcode = $self->pressure_regulator->process($gcode) - if $self->print->config->pressure_advance > 0; - - # apply arc fitting if enabled - $gcode = $self->arc_fitting->process($gcode) - if $self->print->config->gcode_arcs; - - return $gcode; -} - -sub _extrude_perimeters { - my $self = shift; - my ($island_perimeters, $region) = @_; - - return "" if !@$island_perimeters; - - my $gcode = ""; - $gcode .= $self->gcodegen->set_extruder($region->config->perimeter_extruder-1); - $gcode .= $self->gcodegen->extrude($_, 'perimeter') for @$island_perimeters; - return $gcode; -} - -sub _extrude_infill { - my $self = shift; - my ($island_fills, $region) = @_; - - return "" if !@$island_fills; - - my $gcode = ""; - $gcode .= $self->gcodegen->set_extruder($region->config->infill_extruder-1); - for my $fill (@$island_fills) { - if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { - $gcode .= $self->gcodegen->extrude($_, 'fill') - for @{$fill->chained_path_from($self->gcodegen->last_pos, 0)}; - } else { - $gcode .= $self->gcodegen->extrude($fill, 'fill') ; - } - } - return $gcode; -} - -1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index dca82fc2b..88b527080 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -7,8 +7,7 @@ use File::Spec; use List::Util qw(min max first sum); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale chained_path - convex_hull); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection offset offset2 union union_pt_chained JT_ROUND JT_SQUARE); use Slic3r::Print::State ':steps'; @@ -456,243 +455,11 @@ sub write_gcode { binmode $fh, ':utf8'; } - - # write some information - my @lt = localtime; - printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n", - $lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0]; - - print $fh "; $_\n" foreach split /\R/, $self->config->notes; - print $fh "\n" if $self->config->notes; - - my $first_object = $self->objects->[0]; - my $layer_height = $first_object->config->layer_height; - for my $region_id (0..$#{$self->regions}) { - printf $fh "; external perimeters extrusion width = %.2fmm\n", - $self->regions->[$region_id]->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width; - printf $fh "; perimeters extrusion width = %.2fmm\n", - $self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width; - printf $fh "; infill extrusion width = %.2fmm\n", - $self->regions->[$region_id]->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, -1, $first_object)->width; - printf $fh "; solid infill extrusion width = %.2fmm\n", - $self->regions->[$region_id]->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width; - printf $fh "; top infill extrusion width = %.2fmm\n", - $self->regions->[$region_id]->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width; - printf $fh "; support material extrusion width = %.2fmm\n", - $self->objects->[0]->support_material_flow->width - if $self->has_support_material; - printf $fh "; first layer extrusion width = %.2fmm\n", - $self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, -1, $self->objects->[0])->width - if $self->regions->[$region_id]->config->first_layer_extrusion_width; - print $fh "\n"; - } - - # prepare the helper object for replacing placeholders in custom G-code and output filename - $self->placeholder_parser->update_timestamp; - - # estimate the total number of layer changes - # TODO: only do this when M73 is enabled - my $layer_count; - if ($self->config->complete_objects) { - $layer_count = sum(map { $_->total_layer_count * @{$_->copies} } @{$self->objects}); - } else { - # if sequential printing is not enable, all copies of the same object share the same layer change command(s) - $layer_count = sum(map { $_->total_layer_count } @{$self->objects}); - } - - # set up our helper object - my $gcodegen = Slic3r::GCode->new( - placeholder_parser => $self->placeholder_parser, - layer_count => $layer_count, - enable_cooling_markers => 1, + my $exporter = Slic3r::Print::GCode->new( + print => $self, + fh => $fh, ); - $gcodegen->apply_print_config($self->config); - $gcodegen->set_extruders($self->extruders); - - print $fh $gcodegen->writer->set_fan(0, 1) if $self->config->cooling && $self->config->disable_fan_first_layers; - - # set bed temperature - if ((my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) { - printf $fh $gcodegen->writer->set_bed_temperature($temp, 1); - } - - # set extruder(s) temperature before and after start G-code - my $print_first_layer_temperature = sub { - my ($wait) = @_; - - return if $self->config->start_gcode =~ /M(?:109|104)/i; - for my $t (@{$self->extruders}) { - my $temp = $self->config->get_at('first_layer_temperature', $t); - $temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention; - printf $fh $gcodegen->writer->set_temperature($temp, $wait, $t) if $temp > 0; - } - }; - $print_first_layer_temperature->(0); - printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode); - $print_first_layer_temperature->(1); - - # set other general things - print $fh $gcodegen->preamble; - - # initialize a motion planner for object-to-object travel moves - if ($self->config->avoid_crossing_perimeters) { - my $distance_from_objects = 1; - # compute the offsetted convex hull for each object and repeat it for each copy. - my @islands = (); - foreach my $obj_idx (0 .. ($self->object_count - 1)) { - my $convex_hull = convex_hull([ - map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers}, - ]); - # discard layers only containing thin walls (offset would fail on an empty polygon) - if (@$convex_hull) { - my $expolygon = Slic3r::ExPolygon->new($convex_hull); - my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)}; - foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) { - push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island; - } - } - } - $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ])); - } - - # calculate wiping points if needed - if ($self->config->ooze_prevention) { - my @skirt_points = map @$_, map @$_, @{$self->skirt}; - if (@skirt_points) { - my $outer_skirt = convex_hull(\@skirt_points); - my @skirts = (); - foreach my $extruder_id (@{$self->extruders}) { - push @skirts, my $s = $outer_skirt->clone; - $s->translate(map scale($_), @{$self->config->get_at('extruder_offset', $extruder_id)}); - } - my $convex_hull = convex_hull([ map @$_, @skirts ]); - - $gcodegen->ooze_prevention->enable(1); - $gcodegen->ooze_prevention->standby_points( - [ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ] - ); - } - } - - # prepare the layer processor - my $layer_gcode = Slic3r::GCode::Layer->new( - print => $self, - gcodegen => $gcodegen, - ); - - # set initial extruder only after custom start G-code - print $fh $gcodegen->set_extruder($self->extruders->[0]); - - # do all objects for each layer - if ($self->config->complete_objects) { - # print objects from the smallest to the tallest to avoid collisions - # when moving onto next object starting point - my @obj_idx = sort { $self->objects->[$a]->size->[Z] <=> $self->objects->[$b]->size->[Z] } 0..($self->object_count - 1); - - 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 - # no collision happens hopefully. - if ($finished_objects > 0) { - $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); - print $fh $gcodegen->retract; - print $fh $gcodegen->travel_to( - $object->_copies_shift->negative, - undef, - 'move to origin position for next object', - ); - } - - my $buffer = Slic3r::GCode::CoolingBuffer->new( - config => $self->config, - gcodegen => $gcodegen, - ); - - 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 - # another one, set first layer temperatures. this happens before the Z move - # is triggered, so machine has more time to reach such temperatures - if ($layer->id == 0 && $finished_objects > 0) { - printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature), - if $self->config->first_layer_bed_temperature; - $print_first_layer_temperature->(0); - } - print $fh $buffer->append( - $layer_gcode->process_layer($layer, [$copy]), - $layer->object->ptr, - $layer->id, - $layer->print_z, - ); - } - print $fh $buffer->flush; - $finished_objects++; - } - } - } else { - # order objects using a nearest neighbor search - my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])}; - - # sort layers by Z - my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx - foreach my $obj_idx (0 .. ($self->object_count - 1)) { - my $object = $self->objects->[$obj_idx]; - foreach my $layer (@{$object->layers}, @{$object->support_layers}) { - $layers{ $layer->print_z } ||= []; - $layers{ $layer->print_z }[$obj_idx] ||= []; - push @{$layers{ $layer->print_z }[$obj_idx]}, $layer; - } - } - - my $buffer = Slic3r::GCode::CoolingBuffer->new( - config => $self->config, - gcodegen => $gcodegen, - ); - foreach my $print_z (sort { $a <=> $b } keys %layers) { - foreach my $obj_idx (@obj_idx) { - foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) { - print $fh $buffer->append( - $layer_gcode->process_layer($layer, $layer->object->_shifted_copies), - $layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers - $layer->id, - $layer->print_z, - ); - } - } - } - print $fh $buffer->flush; - } - - # write end commands to file - print $fh $gcodegen->retract; # TODO: process this retract through PressureRegulator in order to discharge fully - print $fh $gcodegen->writer->set_fan(0); - printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode); - print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100% - - $self->total_used_filament(0); - $self->total_extruded_volume(0); - foreach my $extruder (@{$gcodegen->writer->extruders}) { - my $used_filament = $extruder->used_filament; - my $extruded_volume = $extruder->extruded_volume; - - printf $fh "; filament used = %.1fmm (%.1fcm3)\n", - $used_filament, $extruded_volume/1000; - - $self->total_used_filament($self->total_used_filament + $used_filament); - $self->total_extruded_volume($self->total_extruded_volume + $extruded_volume); - } - - # append full config - print $fh "\n"; - 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); - } - } + $exporter->export; # close our gcode file close $fh; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm new file mode 100644 index 000000000..c1b1a6813 --- /dev/null +++ b/lib/Slic3r/Print/GCode.pm @@ -0,0 +1,491 @@ +package Slic3r::Print::GCode; +use Moo; + +has 'print' => (is => 'ro', required => 1, handles => [qw(objects placeholder_parser config)]); +has 'fh' => (is => 'ro', required => 1); + +has '_gcodegen' => (is => 'rw'); +has '_cooling_buffer' => (is => 'rw'); +has '_spiral_vase' => (is => 'rw'); +has '_vibration_limit' => (is => 'rw'); +has '_arc_fitting' => (is => 'rw'); +has '_pressure_regulator' => (is => 'rw'); +has '_skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1 +has '_brim_done' => (is => 'rw'); +has '_second_layer_things_done' => (is => 'rw'); +has '_last_obj_copy' => (is => 'rw'); + +use List::Util qw(first sum); +use Slic3r::Flow ':roles'; +use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull); +use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset); + +sub BUILD { + my ($self) = @_; + + { + # estimate the total number of layer changes + # TODO: only do this when M73 is enabled + my $layer_count; + if ($self->config->complete_objects) { + $layer_count = sum(map { $_->total_layer_count * @{$_->copies} } @{$self->objects}); + } else { + # if sequential printing is not enable, all copies of the same object share the same layer change command(s) + $layer_count = sum(map { $_->total_layer_count } @{$self->objects}); + } + + # set up our helper object + my $gcodegen = Slic3r::GCode->new( + placeholder_parser => $self->placeholder_parser, + layer_count => $layer_count, + enable_cooling_markers => 1, + ); + $gcodegen->apply_print_config($self->config); + $gcodegen->set_extruders($self->print->extruders); + $self->_gcodegen($gcodegen); + } + + $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new( + config => $self->config, + gcodegen => $self->_gcodegen, + )); + + $self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config)) + if $self->config->spiral_vase; + + $self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config)) + if $self->config->vibration_limit > 0; + + $self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config)) + if $self->config->gcode_arcs; + + $self->_pressure_regulator(Slic3r::GCode::PressureRegulator->new(config => $self->config)) + if $self->config->pressure_advance > 0; +} + +sub export { + my ($self) = @_; + + my $fh = $self->fh; + my $gcodegen = $self->_gcodegen; + + # write some information + my @lt = localtime; + printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n", + $lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0]; + + print $fh "; $_\n" foreach split /\R/, $self->config->notes; + print $fh "\n" if $self->config->notes; + + my $first_object = $self->objects->[0]; + my $layer_height = $first_object->config->layer_height; + for my $region_id (0..$#{$self->print->regions}) { + my $region = $self->print->regions->[$region_id]; + printf $fh "; external perimeters extrusion width = %.2fmm\n", + $region->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width; + printf $fh "; perimeters extrusion width = %.2fmm\n", + $region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width; + printf $fh "; infill extrusion width = %.2fmm\n", + $region->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, -1, $first_object)->width; + printf $fh "; solid infill extrusion width = %.2fmm\n", + $region->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width; + printf $fh "; top infill extrusion width = %.2fmm\n", + $region->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width; + printf $fh "; support material extrusion width = %.2fmm\n", + $self->objects->[0]->support_material_flow->width + if $self->print->has_support_material; + printf $fh "; first layer extrusion width = %.2fmm\n", + $region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, -1, $self->objects->[0])->width + if $region->config->first_layer_extrusion_width; + print $fh "\n"; + } + + # prepare the helper object for replacing placeholders in custom G-code and output filename + $self->placeholder_parser->update_timestamp; + + print $fh $gcodegen->writer->set_fan(0, 1) + if $self->config->cooling && $self->config->disable_fan_first_layers; + + # set bed temperature + if ((my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) { + printf $fh $gcodegen->writer->set_bed_temperature($temp, 1); + } + + # set extruder(s) temperature before and after start G-code + $self->_print_first_layer_temperature(0); + printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode); + $self->_print_first_layer_temperature(1); + + # set other general things + print $fh $gcodegen->preamble; + + # initialize a motion planner for object-to-object travel moves + if ($self->config->avoid_crossing_perimeters) { + my $distance_from_objects = 1; + # compute the offsetted convex hull for each object and repeat it for each copy. + my @islands = (); + foreach my $obj_idx (0 .. ($self->print->object_count - 1)) { + my $convex_hull = convex_hull([ + map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers}, + ]); + # discard layers only containing thin walls (offset would fail on an empty polygon) + if (@$convex_hull) { + my $expolygon = Slic3r::ExPolygon->new($convex_hull); + my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)}; + foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) { + push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island; + } + } + } + $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ])); + } + + # calculate wiping points if needed + if ($self->config->ooze_prevention) { + my @skirt_points = map @$_, map @$_, @{$self->print->skirt}; + if (@skirt_points) { + my $outer_skirt = convex_hull(\@skirt_points); + my @skirts = (); + foreach my $extruder_id (@{$self->print->extruders}) { + push @skirts, my $s = $outer_skirt->clone; + $s->translate(map scale($_), @{$self->config->get_at('extruder_offset', $extruder_id)}); + } + my $convex_hull = convex_hull([ map @$_, @skirts ]); + + $gcodegen->ooze_prevention->enable(1); + $gcodegen->ooze_prevention->standby_points( + [ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ] + ); + } + } + + # set initial extruder only after custom start G-code + print $fh $gcodegen->set_extruder($self->print->extruders->[0]); + + # do all objects for each layer + if ($self->config->complete_objects) { + # print objects from the smallest to the tallest to avoid collisions + # when moving onto next object starting point + my @obj_idx = sort { $self->objects->[$a]->size->z <=> $self->objects->[$b]->size->z } 0..($self->print->object_count - 1); + + 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 + # no collision happens hopefully. + if ($finished_objects > 0) { + $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); + print $fh $gcodegen->retract; + print $fh $gcodegen->travel_to( + $object->_copies_shift->negative, + undef, + 'move to origin position for next object', + ); + } + + 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 + # another one, set first layer temperatures. this happens before the Z move + # is triggered, so machine has more time to reach such temperatures + if ($layer->id == 0 && $finished_objects > 0) { + printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature), + if $self->config->first_layer_bed_temperature; + $self->_print_first_layer_temperature(0); + } + $self->process_layer($layer, [$copy]); + } + $self->flush_cooling_buffer; + $finished_objects++; + } + } + } else { + # order objects using a nearest neighbor search + my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])}; + + # sort layers by Z + my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx + foreach my $obj_idx (0 .. ($self->print->object_count - 1)) { + my $object = $self->objects->[$obj_idx]; + foreach my $layer (@{$object->layers}, @{$object->support_layers}) { + $layers{ $layer->print_z } ||= []; + $layers{ $layer->print_z }[$obj_idx] ||= []; + push @{$layers{ $layer->print_z }[$obj_idx]}, $layer; + } + } + + foreach my $print_z (sort { $a <=> $b } keys %layers) { + foreach my $obj_idx (@obj_idx) { + foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) { + $self->process_layer($layer, $layer->object->_shifted_copies); + } + } + } + $self->flush_cooling_buffer; + } + + # write end commands to file + print $fh $gcodegen->retract; # TODO: process this retract through PressureRegulator in order to discharge fully + print $fh $gcodegen->writer->set_fan(0); + printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode); + print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100% + + $self->print->total_used_filament(0); + $self->print->total_extruded_volume(0); + foreach my $extruder (@{$gcodegen->writer->extruders}) { + my $used_filament = $extruder->used_filament; + my $extruded_volume = $extruder->extruded_volume; + + printf $fh "; filament used = %.1fmm (%.1fcm3)\n", + $used_filament, $extruded_volume/1000; + + $self->print->total_used_filament($self->print->total_used_filament + $used_filament); + $self->print->total_extruded_volume($self->print->total_extruded_volume + $extruded_volume); + } + + # append full config + print $fh "\n"; + foreach my $config ($self->print->config, $self->print->default_object_config, $self->print->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); + } + } +} + +sub _print_first_layer_temperature { + my ($self, $wait) = @_; + + return if $self->config->start_gcode =~ /M(?:109|104)/i; + for my $t (@{$self->print->extruders}) { + my $temp = $self->config->get_at('first_layer_temperature', $t); + $temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention; + printf {$self->fh} $self->_gcodegen->writer->set_temperature($temp, $wait, $t) if $temp > 0; + } +} + +sub process_layer { + my $self = shift; + my ($layer, $object_copies) = @_; + 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->_spiral_vase) { + $self->_spiral_vase->enable( + ($layer->id > 0 || $self->print->config->brim_width == 0) + && ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1) + && !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions}) + && !defined(first { @{$_->perimeters} > 1 } @{$layer->regions}) + && !defined(first { @{$_->fills} > 0 } @{$layer->regions}) + ); + } + + # if we're going to apply spiralvase to this layer, disable loop clipping + $self->_gcodegen->enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable); + + if (!$self->_second_layer_things_done && $layer->id == 1) { + for my $extruder (@{$self->_gcodegen->writer->extruders}) { + my $temperature = $self->config->get_at('temperature', $extruder->id); + $gcode .= $self->_gcodegen->writer->set_temperature($temperature, 0, $extruder->id) + if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id); + } + $gcode .= $self->_gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature) + if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature; + $self->_second_layer_things_done(1); + } + + # set new layer - this will change Z and force a retraction if retract_layer_change is enabled + $gcode .= $self->_gcodegen->change_layer($layer); + $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, { + layer_num => $layer->id, + }) . "\n" if $self->print->config->layer_gcode; + + # extrude skirt + if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) + && !$self->_skirt_done->{$layer->print_z}) { + $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); + my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders}; + $gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]); + # skip skirt if we have a large brim + if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) { + # distribute skirt loops across all extruders + my @skirt_loops = @{$self->print->skirt}; + for my $i (0 .. $#skirt_loops) { + # when printing layers > 0 ignore 'min_skirt_length' and + # just use the 'skirts' setting; also just use the current extruder + last if ($layer->id > 0) && ($i >= $self->print->config->skirts); + 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', $object->config->support_material_speed); + } + } + $self->_skirt_done->{$layer->print_z} = 1; + $self->_gcodegen->avoid_crossing_perimeters->straight_once(1); + } + + # extrude brim + if (!$self->_brim_done) { + $gcode .= $self->_gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1); + $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); + $gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) + for @{$self->print->brim}; + $self->_brim_done(1); + $self->_gcodegen->avoid_crossing_perimeters->straight_once(1); + } + + for my $copy (@$object_copies) { + $self->_gcodegen->avoid_crossing_perimeters->new_object(1) if ($self->_last_obj_copy // '') ne "$copy"; + $self->_last_obj_copy("$copy"); + + $self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); + + # extrude support material before other things because it might use a lower Z + # and also because we avoid travelling on other things when printing it + 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->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->get_abs_value('support_material_speed')) + for @{$layer->support_fills->chained_path_from($self->_gcodegen->last_pos, 0)}; + } + } + + # tweak region ordering to save toolchanges + my @region_ids = 0 .. ($self->print->region_count-1); + if ($self->_gcodegen->writer->multiple_extruders) { + my $last_extruder_id = $self->_gcodegen->writer->extruder->id; + my $best_region_id = first { $self->print->regions->[$_]->config->perimeter_extruder-1 == $last_extruder_id } @region_ids; + @region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id; + } + + foreach my $region_id (@region_ids) { + my $layerm = $layer->regions->[$region_id] or next; + my $region = $self->print->regions->[$region_id]; + $self->_gcodegen->config->apply_region_config($region->config); + + # group extrusions by island + my @perimeters_by_island = map [], 0..$#{$layer->slices}; # slice idx => @perimeters + my @infill_by_island = map [], 0..$#{$layer->slices}; # slice idx => @fills + + # NOTE: we assume $layer->slices was already ordered with chained_path()! + + PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { + for my $i (0 .. $#{$layer->slices}-1) { + if ($layer->slices->[$i]->contour->contains_point($perimeter->first_point)) { + push @{ $perimeters_by_island[$i] }, $perimeter; + next PERIMETER; + } + } + push @{ $perimeters_by_island[-1] }, $perimeter; # optimization + } + FILL: foreach my $fill (@{$layerm->fills}) { + for my $i (0 .. $#{$layer->slices}-1) { + if ($layer->slices->[$i]->contour->contains_point($fill->first_point)) { + push @{ $infill_by_island[$i] }, $fill; + next FILL; + } + } + push @{ $infill_by_island[-1] }, $fill; # optimization + } + + for my $i (0 .. $#{$layer->slices}) { + # give priority to infill if we were already using its extruder and it wouldn't + # be good for perimeters + if ($self->print->config->infill_first + || ($self->_gcodegen->writer->multiple_extruders && $region->config->infill_extruder-1 == $self->_gcodegen->writer->extruder->id && $region->config->infill_extruder != $region->config->perimeter_extruder)) { + $gcode .= $self->_extrude_infill($infill_by_island[$i], $region); + $gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region); + } else { + $gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region); + $gcode .= $self->_extrude_infill($infill_by_island[$i], $region); + } + } + } + } + + # apply spiral vase post-processing if this layer contains suitable geometry + # (we must feed all the G-code into the post-processor, including the first + # bottom non-spiral layers otherwise it will mess with positions) + # we apply spiral vase at this stage because it requires a full layer + $gcode = $self->_spiral_vase->process_layer($gcode) + if defined $self->_spiral_vase; + + # apply cooling logic; this may alter speeds + $gcode = $self->_cooling_buffer->append( + $gcode, + $layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers + $layer->id, + $layer->print_z, + ) if defined $self->_cooling_buffer && defined $layer; + + print {$self->fh} $self->filter($gcode); +} + +sub _extrude_perimeters { + my $self = shift; + my ($island_perimeters, $region) = @_; + + return "" if !@$island_perimeters; + + my $gcode = ""; + $gcode .= $self->_gcodegen->set_extruder($region->config->perimeter_extruder-1); + $gcode .= $self->_gcodegen->extrude($_, 'perimeter') for @$island_perimeters; + return $gcode; +} + +sub _extrude_infill { + my $self = shift; + my ($island_fills, $region) = @_; + + return "" if !@$island_fills; + + my $gcode = ""; + $gcode .= $self->_gcodegen->set_extruder($region->config->infill_extruder-1); + for my $fill (@$island_fills) { + if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { + $gcode .= $self->_gcodegen->extrude($_, 'fill') + for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)}; + } else { + $gcode .= $self->_gcodegen->extrude($fill, 'fill') ; + } + } + return $gcode; +} + +sub flush_cooling_buffer { + my ($self) = @_; + print {$self->fh} $self->filter($self->_cooling_buffer->flush); +} + +sub filter { + my ($self, $gcode) = @_; + + # apply vibration limit if enabled; + # this injects pauses according to time (thus depends on actual speeds) + $gcode = $self->_vibration_limit->process($gcode) + if $self->print->config->vibration_limit != 0; + + # apply pressure regulation if enabled; + # this depends on actual speeds + $gcode = $self->_pressure_regulator->process($gcode) + if $self->print->config->pressure_advance > 0; + + # apply arc fitting if enabled; + # this does not depend on speeds but changes G1 XY commands into G2/G2 IJ + $gcode = $self->_arc_fitting->process($gcode) + if $self->print->config->gcode_arcs; + + return $gcode; +} + +1; From e8f242ee3fb00a1bf9b2e169d0f1ce372403f278 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 20:18:09 +0100 Subject: [PATCH 004/107] Move toolpaths preview to the plater dialog --- lib/Slic3r/GUI/Plater.pm | 30 +++------ lib/Slic3r/GUI/Plater/2DToolpaths.pm | 92 ++++++++++++++++++---------- xs/src/libslic3r/Print.cpp | 13 ++++ xs/src/libslic3r/Print.hpp | 1 + xs/xsp/Print.xsp | 2 + 5 files changed, 83 insertions(+), 55 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 32645e338..49b5a62a0 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -102,10 +102,13 @@ sub new { } }); - # Initialize 3D preview canvas + # Initialize 3D preview and toolpaths preview if ($Slic3r::GUI::have_OpenGL) { $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config}); $self->{preview_notebook}->AddPage($self->{canvas3D}, '3D'); + + $self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print}); + $self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Preview'); } # toolbar for object manipulation @@ -169,11 +172,8 @@ sub new { # right pane buttons $self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_toolpaths_preview} = Wx::Button->new($self, -1, "Toolpaths preview…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); $self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); - $self->{btn_toolpaths_preview}->SetFont($Slic3r::GUI::small_font); - $self->{btn_toolpaths_preview}->Disable; if ($Slic3r::GUI::have_button_icons) { my %icons = qw( @@ -183,7 +183,6 @@ sub new { arrange bricks.png export_gcode cog_go.png export_stl brick_go.png - toolpaths_preview joystick.png increase add.png decrease delete.png @@ -205,7 +204,6 @@ sub new { Slic3r::thread_cleanup(); }); EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl); - EVT_BUTTON($self, $self->{btn_toolpaths_preview}, \&toolpaths_preview); if ($self->{htoolbar}) { EVT_TOOL($self, TB_ADD, sub { $self->add; }); @@ -346,7 +344,6 @@ sub new { my $right_buttons_sizer = Wx::BoxSizer->new(wxVERTICAL); $right_buttons_sizer->Add($presets, 0, wxEXPAND, 0) if defined $presets; $right_buttons_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxTOP, 8); - $right_buttons_sizer->Add($self->{btn_toolpaths_preview}, 0, wxEXPAND | wxTOP, 2); $right_buttons_sizer->Add($self->{btn_export_stl}, 0, wxEXPAND | wxTOP, 2); my $right_top_sizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -809,7 +806,7 @@ sub schedule_background_process { if (defined $self->{apply_config_timer}) { $self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot - $self->{btn_toolpaths_preview}->Disable; + $self->{toolpaths2D}->reload_print; } } @@ -885,7 +882,7 @@ sub stop_background_process { $self->statusbar->SetCancelCallback(undef); $self->statusbar->StopBusy; $self->statusbar->SetStatusText(""); - $self->{btn_toolpaths_preview}->Disable; + $self->{toolpaths2D}->reload_print; if ($self->{process_thread}) { Slic3r::debugf "Killing background process.\n"; @@ -1004,7 +1001,7 @@ sub on_process_completed { $self->{process_thread} = undef; return if !$result; - $self->{btn_toolpaths_preview}->Enable; + $self->{toolpaths2D}->reload_print; # if we have an export filename, start a new thread for exporting G-code if ($self->{export_gcode_output_file}) { @@ -1281,19 +1278,6 @@ sub object_settings_dialog { } } -sub toolpaths_preview { - my ($self) = @_; - - # TODO: we should check whether steps are done in $print rather then checking the thread - if ($self->{process_thread}) { - Slic3r::GUI::show_error($self, "Unable to show preview while toolpaths are being generated."); - return; - } - - my $dlg = Slic3r::GUI::Plater::2DToolpaths::Dialog->new($self, $self->{print}); - $dlg->ShowModal; -} - sub object_list_changed { my $self = shift; diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 8e48a7290..c69562c1d 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -3,11 +3,12 @@ use strict; use warnings; use utf8; -use List::Util qw(); -use Slic3r::Geometry qw(); +use Slic3r::Print::State ':steps'; use Wx qw(:misc :sizer :slider :statictext); use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN); -use base 'Wx::Panel'; +use base qw(Wx::Panel Class::Accessor); + +__PACKAGE__->mk_accessors(qw(print enabled)); sub new { my $class = shift; @@ -15,17 +16,13 @@ sub new { my $self = $class->SUPER::new($parent, -1, wxDefaultPosition); - # init print - $self->{print} = $print; - $self->reload_print; - # init GUI elements my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print); my $slider = $self->{slider} = Wx::Slider->new( $self, -1, 0, # default 0, # min - scalar(@{$self->{layers_z}})-1, # max + 0, # max wxDefaultPosition, wxDefaultSize, wxVERTICAL | wxSL_INVERSE, @@ -43,7 +40,8 @@ sub new { $sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5); EVT_SLIDER($self, $slider, sub { - $self->set_z($self->{layers_z}[$slider->GetValue]); + $self->set_z($self->{layers_z}[$slider->GetValue]) + if $self->enabled; }); EVT_KEY_DOWN($canvas, sub { my ($s, $event) = @_; @@ -62,7 +60,9 @@ sub new { $self->SetMinSize($self->GetSize); $sizer->SetSizeHints($self); - $self->set_z($self->{layers_z}[0]); + # init print + $self->{print} = $print; + $self->reload_print; return $self; } @@ -70,6 +70,18 @@ sub new { sub reload_print { my ($self) = @_; + # we require that there's at least one object and the posSlice step + # is performed on all of them (this ensures that _shifted_copies was + # populated and we know the number of layers) + if (!$self->print->object_step_done(STEP_SLICE)) { + $self->enabled(0); + $self->{slider}->Hide; + $self->{canvas}->Refresh; # clears canvas + return; + } + + $self->{canvas}->bb($self->print->total_bounding_box); + my %z = (); # z => 1 foreach my $object (@{$self->{print}->objects}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) { @@ -77,11 +89,16 @@ sub reload_print { } } $self->{layers_z} = [ sort { $a <=> $b } keys %z ]; + ###$self->{slider}->SetMax(scalar(@{$self->{layers_z}})-1); + $self->enabled(1); + $self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}}; + $self->{slider}->Show; } sub set_z { my ($self, $z) = @_; + return if !$self->enabled; $self->{z_label}->SetLabel(sprintf '%.2f', $z); $self->{canvas}->set_z($z); } @@ -89,14 +106,15 @@ sub set_z { package Slic3r::GUI::Plater::2DToolpaths::Canvas; -use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); +use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); use OpenGL qw(:glconstants :glfunctions :glufunctions); use base qw(Wx::GLCanvas Class::Accessor); use Wx::GLCanvas qw(:all); use List::Util qw(min first); use Slic3r::Geometry qw(scale unscale epsilon); +use Slic3r::Print::State ':steps'; -__PACKAGE__->mk_accessors(qw(print z layers color init dirty bb)); +__PACKAGE__->mk_accessors(qw(print z layers color init bb)); # make OpenGL::Array thread-safe { @@ -109,15 +127,12 @@ sub new { my $self = $class->SUPER::new($parent); $self->print($print); - $self->bb($self->print->total_bounding_box); EVT_PAINT($self, sub { my $dc = Wx::PaintDC->new($self); $self->Render($dc); }); - EVT_SIZE($self, sub { $self->dirty(1) }); - EVT_IDLE($self, sub { - return unless $self->dirty; + EVT_SIZE($self, sub { return if !$self->IsShownOnScreen; $self->Resize( $self->GetSizeWH ); $self->Refresh; @@ -152,7 +167,7 @@ sub set_z { $self->z($z); $self->layers([ @layers ]); - $self->dirty(1); + $self->Refresh; } sub Render { @@ -164,6 +179,15 @@ sub Render { $self->SetCurrent($context); $self->InitGL; + glClearColor(1, 1, 1, 0); + glClear(GL_COLOR_BUFFER_BIT); + + if (!$self->GetParent->enabled || !$self->layers) { + glFlush(); + $self->SwapBuffers; + return; + } + glMatrixMode(GL_PROJECTION); glLoadIdentity(); my $bb = $self->bb; @@ -184,9 +208,6 @@ sub Render { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glClearColor(1, 1, 1, 0); - glClear(GL_COLOR_BUFFER_BIT); - my $skirt_drawn = 0; my $brim_drawn = 0; foreach my $layer (@{$self->layers}) { @@ -194,29 +215,37 @@ sub Render { my $print_z = $layer->print_z; # draw brim - if ($layer->id == 0 && !$brim_drawn) { + if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) { $self->color([0, 0, 0]); $self->_draw(undef, $print_z, $_) for @{$self->print->brim}; $brim_drawn = 1; } - if (($self->print->config->skirt_height == -1 || $self->print->config->skirt_height >= $layer->id) && !$skirt_drawn) { + if ($self->print->step_done(STEP_SKIRT) + && ($self->print->config->skirt_height == -1 || $self->print->config->skirt_height > $layer->id) + && !$skirt_drawn) { $self->color([0, 0, 0]); $self->_draw(undef, $print_z, $_) for @{$self->print->skirt}; $skirt_drawn = 1; } foreach my $layerm (@{$layer->regions}) { - $self->color([0.7, 0, 0]); - $self->_draw($object, $print_z, $_) for @{$layerm->perimeters}; + if ($object->step_done(STEP_PERIMETERS)) { + $self->color([0.7, 0, 0]); + $self->_draw($object, $print_z, $_) for @{$layerm->perimeters}; + } - $self->color([0, 0, 0.7]); - $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills}; + if ($object->step_done(STEP_INFILL)) { + $self->color([0, 0, 0.7]); + $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills}; + } } - if ($layer->isa('Slic3r::Layer::Support')) { - $self->color([0, 0, 0]); - $self->_draw($object, $print_z, $_) for @{$layer->support_fills}; - $self->_draw($object, $print_z, $_) for @{$layer->support_interface_fills}; + if ($object->step_done(STEP_SUPPORTMATERIAL)) { + if ($layer->isa('Slic3r::Layer::Support')) { + $self->color([0, 0, 0]); + $self->_draw($object, $print_z, $_) for @{$layer->support_fills}; + $self->_draw($object, $print_z, $_) for @{$layer->support_interface_fills}; + } } } @@ -299,8 +328,7 @@ sub Resize { my ($self, $x, $y) = @_; return unless $self->GetContext; - $self->dirty(0); - + $self->SetCurrent($self->GetContext); glViewport(0, 0, $x, $y); } diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index d0175ebac..3339162c7 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -290,6 +290,19 @@ Print::invalidate_all_steps() return invalidated; } +// returns true if an object step is done on all objects +// and there's at least one object +bool +Print::step_done(PrintObjectStep step) const +{ + if (this->objects.empty()) return false; + FOREACH_OBJECT(this, object) { + if (!(*object)->state.is_done(step)) + return false; + } + return true; +} + // returns 0-based indices of used extruders std::set Print::extruders() const diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 0c4d7c24f..574a9b6d2 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -178,6 +178,7 @@ class Print bool invalidate_state_by_config_options(const std::vector &opt_keys); bool invalidate_step(PrintStep step); bool invalidate_all_steps(); + bool step_done(PrintObjectStep step) const; void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig config); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 556e87396..6369524e1 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -156,6 +156,8 @@ _constant() bool invalidate_all_steps(); bool step_done(PrintStep step) %code%{ RETVAL = THIS->state.is_done(step); %}; + bool object_step_done(PrintObjectStep step) + %code%{ RETVAL = THIS->step_done(step); %}; void set_step_done(PrintStep step) %code%{ THIS->state.set_done(step); %}; void set_step_started(PrintStep step) From 80adf9e5d0445f18640ba92792173394624087a5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 20:19:04 +0100 Subject: [PATCH 005/107] Open the settings dialog instead of the cut dialog when user double clicks on objects in plater --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 49b5a62a0..c3f5d483e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -76,7 +76,7 @@ sub new { $self->select_object($obj_idx); }); $self->{canvas}->on_double_click(sub { - $self->object_cut_dialog if $self->selected_object; + $self->object_settings_dialog if $self->selected_object; }); $self->{canvas}->on_right_click(sub { my ($click_pos) = @_; From 0d3c4a160fa9881406e4f6057fc0b878b2775dd9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 20:26:00 +0100 Subject: [PATCH 006/107] Typo in Reader.pm #2033 --- lib/Slic3r/GCode/Reader.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm index ae436b854..cdcaeeeb1 100644 --- a/lib/Slic3r/GCode/Reader.pm +++ b/lib/Slic3r/GCode/Reader.pm @@ -50,7 +50,7 @@ sub parse { if ($command =~ /^G[01]$/) { foreach my $axis (@AXES) { if (exists $args{$axis}) { - $self->$axis = 0 if $axis eq 'E' && $self->config->use_relative_e_distances; + $self->$axis(0) if $axis eq 'E' && $self->config->use_relative_e_distances; $info{"dist_$axis"} = $args{$axis} - $self->$axis; $info{"new_$axis"} = $args{$axis}; } else { From e4dd5cf82f2295878c4d25ceac35f98d52109a73 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 20:38:42 +0100 Subject: [PATCH 007/107] Bugfix: object steps were not invalidated when First layer extrusion width was changed. #2379 --- xs/src/libslic3r/Print.cpp | 13 +++++++++++++ xs/src/libslic3r/PrintObject.cpp | 7 +++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 3339162c7..c4a231c78 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -173,6 +173,7 @@ bool Print::invalidate_state_by_config_options(const std::vector &opt_keys) { std::set steps; + std::set osteps; // this method only accepts PrintConfig option keys for (std::vector::const_iterator opt_key = opt_keys.begin(); opt_key != opt_keys.end(); ++opt_key) { @@ -223,6 +224,7 @@ Print::invalidate_state_by_config_options(const std::vector || *opt_key == "output_filename_format" || *opt_key == "perimeter_acceleration" || *opt_key == "post_process" + || *opt_key == "pressure_advance" || *opt_key == "retract_before_travel" || *opt_key == "retract_layer_change" || *opt_key == "retract_length" @@ -245,6 +247,12 @@ Print::invalidate_state_by_config_options(const std::vector || *opt_key == "wipe" || *opt_key == "z_offset") { // these options only affect G-code export, so nothing to invalidate + } else if (*opt_key == "first_layer_extrusion_width") { + osteps.insert(posPerimeters); + osteps.insert(posInfill); + osteps.insert(posSupportMaterial); + steps.insert(psSkirt); + steps.insert(psBrim); } else { // for legacy, if we can't handle this option let's invalidate all steps return this->invalidate_all_steps(); @@ -255,6 +263,11 @@ Print::invalidate_state_by_config_options(const std::vector for (std::set::const_iterator step = steps.begin(); step != steps.end(); ++step) { if (this->invalidate_step(*step)) invalidated = true; } + for (std::set::const_iterator ostep = osteps.begin(); ostep != osteps.end(); ++ostep) { + FOREACH_OBJECT(this, object) { + if ((*object)->invalidate_step(*ostep)) invalidated = true; + } + } return invalidated; } diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index b4ce83e06..309012c7c 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -203,6 +203,7 @@ PrintObject::invalidate_state_by_config_options(const std::vector Date: Sun, 30 Nov 2014 20:53:53 +0100 Subject: [PATCH 008/107] Fixed regression causing the plater Split command not to remove objects from the model. #2380 --- lib/Slic3r/GUI/Plater.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index c3f5d483e..2111dc7d9 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -767,7 +767,10 @@ sub split_object { my $self = shift; my ($obj_idx, $current_object) = $self->selected_object; - my $current_model_object = $self->{model}->objects->[$obj_idx]; + + # we clone model object because split_object() adds the split volumes + # into the same model object, thus causing duplicated when we call load_model_objects() + my $current_model_object = $self->{model}->clone->objects->[$obj_idx]; if (@{$current_model_object->volumes} > 1) { Slic3r::GUI::warning_catcher($self)->("The selected object can't be split because it contains more than one volume/material."); From 1fda9e3d502de097c6197dee6835870f2b1670f4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 21:48:50 +0100 Subject: [PATCH 009/107] Ported concave_points() and convex_points() to XS --- lib/Slic3r/Polygon.pm | 47 ------------------------------- xs/src/libslic3r/Polygon.cpp | 54 ++++++++++++++++++++++++++++++++++++ xs/src/libslic3r/Polygon.hpp | 4 +++ xs/xsp/Polygon.xsp | 4 +++ 4 files changed, 62 insertions(+), 47 deletions(-) diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index bc0fbc5dd..1d089189c 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -37,51 +37,4 @@ sub subdivide { return Slic3r::Polygon->new(@new_points); } -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 @concave = (); - my @points = @$self; - my @points_pp = @{$self->pp}; - - for my $i (-1 .. ($#points-1)) { - # 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]; -} - -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 @convex = (); - my @points = @$self; - my @points_pp = @{$self->pp}; - - for my $i (-1 .. ($#points-1)) { - # 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/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 7fc6f0091..14206b189 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -222,6 +222,60 @@ Polygon::wkt() const return wkt.str(); } +void +Polygon::concave_points(double angle, Points* points) const +{ + /* input angle threshold is checked on the internal side of the polygon + but ccw() returns 0 for collinear, >0 for ccw and <0 for cw */ + double ccw_angle = angle - PI; + + // check whether first point forms a concave angle + if (this->points.front().ccw(this->points.back(), *(this->points.begin()+1)) >= ccw_angle) + points->push_back(this->points.front()); + + // check whether points 1..(n-1) form concave angles + for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { + if (p->ccw(*(p-1), *(p+1)) >= ccw_angle) points->push_back(*p); + } + + // check whether last point forms a concave angle + if (this->points.back().ccw(*(this->points.end()-2), this->points.front()) >= ccw_angle) + points->push_back(this->points.back()); +} + +void +Polygon::concave_points(Points* points) const +{ + this->concave_points(PI, points); +} + +void +Polygon::convex_points(double angle, Points* points) const +{ + /* input angle threshold is checked on the internal side of the polygon + but ccw() returns 0 for collinear, >0 for ccw and <0 for cw */ + double ccw_angle = angle - PI; + + // check whether first point forms a convex angle + if (this->points.front().ccw(this->points.back(), *(this->points.begin()+1)) <= ccw_angle) + points->push_back(this->points.front()); + + // check whether points 1..(n-1) form convex angles + for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { + if (p->ccw(*(p-1), *(p+1)) <= ccw_angle) points->push_back(*p); + } + + // check whether last point forms a convex angle + if (this->points.back().ccw(*(this->points.end()-2), this->points.front()) <= ccw_angle) + points->push_back(this->points.back()); +} + +void +Polygon::convex_points(Points* points) const +{ + this->convex_points(PI, points); +} + #ifdef SLIC3RXS REGISTER_CLASS(Polygon, "Polygon"); diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 5220220f3..76e668024 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -38,6 +38,10 @@ class Polygon : public MultiPoint { void triangulate_convex(Polygons* polygons) const; Point centroid() const; std::string wkt() const; + void concave_points(double angle, Points* points) const; + void concave_points(Points* points) const; + void convex_points(double angle, Points* points) const; + void convex_points(Points* points) const; #ifdef SLIC3RXS void from_SV_check(SV* poly_sv); diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 99ff1d644..6a8eac99c 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -47,6 +47,10 @@ THIS->bounding_box(RETVAL); %}; std::string wkt(); + Points concave_points(double angle) + %code{% THIS->concave_points(angle, &RETVAL); %}; + Points convex_points(double angle) + %code{% THIS->convex_points(angle, &RETVAL); %}; %{ Polygon* From c7f5753a2838bab8a84521795505e1e2c43194ca Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 21:58:41 +0100 Subject: [PATCH 010/107] Ported PrintObject::bounding_box() to XS --- lib/Slic3r/Print/Object.pm | 10 ---------- xs/src/libslic3r/Print.hpp | 2 ++ xs/src/libslic3r/PrintObject.cpp | 10 ++++++++++ xs/xsp/Print.xsp | 5 +++++ 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index ba03cc6a8..5a7091121 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -40,16 +40,6 @@ sub total_layer_count { return $self->layer_count + $self->support_layer_count; } -sub bounding_box { - my $self = shift; - - # since the object is aligned to origin, bounding box coincides with size - return Slic3r::Geometry::BoundingBox->new_from_points([ - Slic3r::Point->new(0,0), - map Slic3r::Point->new($_->x, $_->y), $self->size #)) - ]); -} - # this should be idempotent sub slice { my $self = shift; diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 574a9b6d2..868a3d775 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -5,6 +5,7 @@ #include #include #include +#include "BoundingBox.hpp" #include "Flow.hpp" #include "PrintConfig.hpp" #include "Point.hpp" @@ -108,6 +109,7 @@ class PrintObject bool delete_all_copies(); bool set_copies(const Points &points); bool reload_model_instances(); + void bounding_box(BoundingBox* bb) const; // adds region_id, too, if necessary void add_region_volume(int region_id, int volume_id); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 309012c7c..e8d4eb788 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -111,6 +111,16 @@ PrintObject::reload_model_instances() return this->set_copies(copies); } +void +PrintObject::bounding_box(BoundingBox* bb) const +{ + // since the object is aligned to origin, bounding box coincides with size + Points pp; + pp.push_back(Point(0,0)); + pp.push_back(this->size); + *bb = BoundingBox(pp); +} + void PrintObject::add_region_volume(int region_id, int volume_id) { diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 6369524e1..60135f4a1 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -61,6 +61,11 @@ _constant() %code%{ RETVAL = THIS->layer_height_ranges; %}; Ref size() %code%{ RETVAL = &THIS->size; %}; + BoundingBox* bounding_box() + %code{% + RETVAL = new BoundingBox(); + THIS->bounding_box(RETVAL); + %}; Ref _copies_shift() %code%{ RETVAL = &THIS->_copies_shift; %}; From eb23990d6d0cbc028768a2ce499de1eb67a3d579 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 22:01:46 +0100 Subject: [PATCH 011/107] Ported PrintObject::total_layer_count() to XS --- lib/Slic3r/Print/Object.pm | 8 -------- xs/src/libslic3r/Print.hpp | 5 +++-- xs/src/libslic3r/PrintObject.cpp | 13 +++++++++++-- xs/xsp/Print.xsp | 1 + 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 5a7091121..56279223a 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -32,14 +32,6 @@ sub support_layers { return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ]; } -# this is the *total* layer count (including support layers) -# this value is not supposed to be compared with $layer->id -# since they have different semantics -sub total_layer_count { - my $self = shift; - return $self->layer_count + $self->support_layer_count; -} - # this should be idempotent sub slice { my $self = shift; diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 868a3d775..27ba83780 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -114,13 +114,14 @@ class PrintObject // adds region_id, too, if necessary void add_region_volume(int region_id, int volume_id); - size_t layer_count(); + size_t total_layer_count() const; + size_t layer_count() const; void clear_layers(); Layer* get_layer(int idx); Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_layer(int idx); - size_t support_layer_count(); + size_t support_layer_count() const; void clear_support_layers(); SupportLayer* get_support_layer(int idx); SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index e8d4eb788..16c904cfb 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -131,8 +131,17 @@ PrintObject::add_region_volume(int region_id, int volume_id) region_volumes[region_id].push_back(volume_id); } +/* This is the *total* layer count (including support layers) + this value is not supposed to be compared with Layer::id + since they have different semantics */ size_t -PrintObject::layer_count() +PrintObject::total_layer_count() const +{ + return this->layer_count() + this->support_layer_count(); +} + +size_t +PrintObject::layer_count() const { return this->layers.size(); } @@ -167,7 +176,7 @@ PrintObject::delete_layer(int idx) } size_t -PrintObject::support_layer_count() +PrintObject::support_layer_count() const { return this->support_layers.size(); } diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 60135f4a1..2213a9511 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -88,6 +88,7 @@ _constant() void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) %code%{ THIS->layer_height_ranges = layer_height_ranges; %}; + size_t total_layer_count(); size_t layer_count(); void clear_layers(); Ref get_layer(int idx); From 98c67007d56a516d8194a12b8e4558ae6f76f6c3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 30 Nov 2014 23:59:51 +0100 Subject: [PATCH 012/107] Finished bb103122065cddf8f8ea63f5e4fce954142c4d4c --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index c69562c1d..b3ba93c3c 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -89,7 +89,7 @@ sub reload_print { } } $self->{layers_z} = [ sort { $a <=> $b } keys %z ]; - ###$self->{slider}->SetMax(scalar(@{$self->{layers_z}})-1); + $self->{slider}->SetRange(0, scalar(@{$self->{layers_z}})-1); $self->enabled(1); $self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}}; $self->{slider}->Show; From 04bcb410a945b7724c3286bf54ced12c17eb1ea5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Dec 2014 00:10:32 +0100 Subject: [PATCH 013/107] Minor GUI improvements to toolpaths preview --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index b3ba93c3c..f573ccebb 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -4,7 +4,7 @@ use warnings; use utf8; use Slic3r::Print::State ':steps'; -use Wx qw(:misc :sizer :slider :statictext); +use Wx qw(:misc :sizer :slider :statictext wxWHITE); use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN); use base qw(Wx::Panel Class::Accessor); @@ -15,6 +15,7 @@ sub new { my ($parent, $print) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition); + $self->SetBackgroundColour(wxWHITE); # init GUI elements my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print); @@ -93,6 +94,7 @@ sub reload_print { $self->enabled(1); $self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}}; $self->{slider}->Show; + $self->Layout; } sub set_z { From c9e896c669ed74a62a08d12e16810cf4e18f9319 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Dec 2014 00:15:45 +0100 Subject: [PATCH 014/107] Display validation errors in status bar when background processing couldn't generate toolpaths --- lib/Slic3r/GUI/Plater.pm | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 2111dc7d9..5b93c2562 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -853,7 +853,10 @@ sub start_background_process { $self->GetFrame->config->validate; $self->{print}->validate; }; - return if $@; + if ($@) { + $self->statusbar->SetStatusText($@); + return; + } # apply extra variables { @@ -869,9 +872,9 @@ sub start_background_process { }; if ($@) { Slic3r::debugf "Discarding background process error: $@\n"; - Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, 0)); + Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, $@)); } else { - Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, 1)); + Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, undef)); } Slic3r::thread_cleanup(); }); @@ -993,17 +996,17 @@ sub export_gcode { # This gets called only if we have threads. sub on_process_completed { - my ($self, $result) = @_; + my ($self, $error) = @_; $self->statusbar->SetCancelCallback(undef); $self->statusbar->StopBusy; - $self->statusbar->SetStatusText(""); + $self->statusbar->SetStatusText($error // ""); Slic3r::debugf "Background processing completed.\n"; $self->{process_thread}->detach if $self->{process_thread}; $self->{process_thread} = undef; - return if !$result; + return if $error; $self->{toolpaths2D}->reload_print; # if we have an export filename, start a new thread for exporting G-code From 95f7bcb9fef03d3f9fc35a3c3358be5a10bc75c3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 1 Dec 2014 21:06:21 +0100 Subject: [PATCH 015/107] Removed Toolpaths Preview menu item. #2385 --- lib/Slic3r/GUI/MainFrame.pm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 76371292a..57729ad9d 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -192,10 +192,6 @@ sub _init_menubar { $self->_append_menu_item($self->{plater_menu}, "Export AMF...", 'Export current plate as AMF', sub { $plater->export_amf; }); - $self->{plater_menu}->AppendSeparator(); - $self->_append_menu_item($self->{plater_menu}, "Toolpaths preview…", 'Open a viewer with toolpaths preview', sub { - $plater->toolpaths_preview; - }); $self->{object_menu} = $self->{plater}->object_menu; $self->on_plater_selection_changed(0); From 6ce651eb4ae0fab4d99ef73d176e2a614e0e710c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 7 Dec 2014 19:53:22 +0100 Subject: [PATCH 016/107] Fixed wrong implementation of concave_points() and convex_points() in C++. #2384 --- t/geometry.t | 28 +++++++++++++++++++++++++++- t/perimeters.t | 2 +- xs/src/libslic3r/MultiPoint.hpp | 2 ++ xs/src/libslic3r/Point.cpp | 7 +++++++ xs/src/libslic3r/Point.hpp | 1 + xs/src/libslic3r/Polygon.cpp | 28 ++++++++++++---------------- xs/src/libslic3r/Polygon.hpp | 3 +++ 7 files changed, 53 insertions(+), 18 deletions(-) diff --git a/t/geometry.t b/t/geometry.t index 24fbf52d9..d85c8bbcf 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 33; +plan tests => 38; BEGIN { use FindBin; @@ -213,3 +213,29 @@ my $polygons = [ 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 $triangle = Slic3r::Polygon->new( + [16000170,26257364], [714223,461012], [31286371,461008], + ); + is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle'; + is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle'; +} + +{ + my $triangle = Slic3r::Polygon->new( + [16000170,26257364], [714223,461012], [20000000,461012], [31286371,461012], + ); + is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle having collinear point'; + is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle having collinear point'; +} + +{ + my $triangle = Slic3r::Polygon->new( + [16000170,26257364], [714223,461012], [31286371,461008], + ); + my $simplified = $triangle->simplify(250000)->[0]; + is scalar(@$simplified), 3, 'triangle is never simplified to less than 3 points'; +} + +__END__ diff --git a/t/perimeters.t b/t/perimeters.t index 17e1b31c7..49217fd82 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -266,7 +266,7 @@ use Slic3r::Test; my $was_extruding = 0; my @seam_points = (); my $print = Slic3r::Test::init_print($model_name, config => $config); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding}) { diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index ba3c72e50..eaca9b8eb 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -17,6 +17,8 @@ class MultiPoint Points points; operator Points() const; + MultiPoint() {}; + explicit MultiPoint(const Points &_points): points(_points) {}; void scale(double factor); void translate(double x, double y); void translate(const Point &vector); diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 9debe9e8e..76da85334 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -160,6 +160,13 @@ Point::ccw(const Line &line) const return this->ccw(line.a, line.b); } +// returns the CCW angle between this-p1 and this-p2 +double +Point::ccw_angle(const Point &p1, const Point &p2) const +{ + return Line(*this, p1).orientation() - Line(*this, p2).orientation(); +} + Point Point::projection_onto(const MultiPoint &poly) const { diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 514eb51cd..60127b1b7 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -48,6 +48,7 @@ class Point double distance_to(const Line &line) const; double ccw(const Point &p1, const Point &p2) const; double ccw(const Line &line) const; + double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; Point negative() const; diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 14206b189..36c644243 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -158,8 +158,12 @@ Polygon::contains(const Point &point) const Polygons Polygon::simplify(double tolerance) const { - Polygon p = *this; - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); + // repeat first point at the end in order to apply Douglas-Peucker + // on the whole polygon + Points points = this->points; + points.push_back(points.front()); + Polygon p(MultiPoint::_douglas_peucker(points, tolerance)); + p.points.pop_back(); Polygons pp; pp.push_back(p); @@ -225,21 +229,17 @@ Polygon::wkt() const void Polygon::concave_points(double angle, Points* points) const { - /* input angle threshold is checked on the internal side of the polygon - but ccw() returns 0 for collinear, >0 for ccw and <0 for cw */ - double ccw_angle = angle - PI; - // check whether first point forms a concave angle - if (this->points.front().ccw(this->points.back(), *(this->points.begin()+1)) >= ccw_angle) + if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle) points->push_back(this->points.front()); // check whether points 1..(n-1) form concave angles for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { - if (p->ccw(*(p-1), *(p+1)) >= ccw_angle) points->push_back(*p); + if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points->push_back(*p); } // check whether last point forms a concave angle - if (this->points.back().ccw(*(this->points.end()-2), this->points.front()) >= ccw_angle) + if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle) points->push_back(this->points.back()); } @@ -252,21 +252,17 @@ Polygon::concave_points(Points* points) const void Polygon::convex_points(double angle, Points* points) const { - /* input angle threshold is checked on the internal side of the polygon - but ccw() returns 0 for collinear, >0 for ccw and <0 for cw */ - double ccw_angle = angle - PI; - // check whether first point forms a convex angle - if (this->points.front().ccw(this->points.back(), *(this->points.begin()+1)) <= ccw_angle) + if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle) points->push_back(this->points.front()); // check whether points 1..(n-1) form convex angles for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { - if (p->ccw(*(p-1), *(p+1)) <= ccw_angle) points->push_back(*p); + if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points->push_back(*p); } // check whether last point forms a convex angle - if (this->points.back().ccw(*(this->points.end()-2), this->points.front()) <= ccw_angle) + if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle) points->push_back(this->points.back()); } diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 76e668024..778825f3e 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -19,6 +19,9 @@ class Polygon : public MultiPoint { operator Polyline() const; Point& operator[](Points::size_type idx); const Point& operator[](Points::size_type idx) const; + + Polygon() {}; + explicit Polygon(const Points &points): MultiPoint(points) {}; Point last_point() const; Lines lines() const; void lines(Lines* lines) const; From 807d042d118e48cb68e13a1496a2b0d9322e4cc9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 7 Dec 2014 19:56:35 +0100 Subject: [PATCH 017/107] Typo. #2401 --- lib/Slic3r/Layer/Region.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 0063095e1..fbc665df3 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -496,7 +496,7 @@ sub process_external_surfaces { $angle = $bridge_detector->angle; if (defined $angle && $self->object->config->support_material) { - $self->bridged->append($_) for @{ $bridge_detector->coverage_with_angle($angle) }; + $self->bridged->append($_) for @{ $bridge_detector->coverage_by_angle($angle) }; $self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges }; } } From 2d243a39fff6c38ac96f09e9e3c0f343f3fd4a08 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 7 Dec 2014 20:23:00 +0100 Subject: [PATCH 018/107] Bugfix: crash when deleting objects from plater with toolpaths preview open. #2389 --- lib/Slic3r/GUI/Plater.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5b93c2562..42c672bf5 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -525,6 +525,9 @@ sub remove { $self->stop_background_process; + # Prevent toolpaths preview from rendering while we modify the Print object + $self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D}; + # if no object index is supplied, remove the selected one if (!defined $obj_idx) { ($obj_idx, undef) = $self->selected_object; @@ -547,6 +550,9 @@ sub reset { $self->stop_background_process; + # Prevent toolpaths preview from rendering while we modify the Print object + $self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D}; + @{$self->{objects}} = (); $self->{model}->clear_objects; $self->{print}->clear_objects; From f7026c41c5587717a6978501d8a3e645629d4358 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 8 Dec 2014 18:23:37 +0100 Subject: [PATCH 019/107] Show button icons with wxWidgets 3.x.x too. #2372 --- lib/Slic3r/GUI.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 969c7fa40..d089bbf29 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -59,7 +59,7 @@ our $Settings = { }, }; -our $have_button_icons = &Wx::wxVERSION_STRING =~ / 2\.9\.[1-9]/; +our $have_button_icons = &Wx::wxVERSION_STRING =~ / (?:2\.9\.[1-9]|3\.)/; our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $small_font->SetPointSize(11) if !&Wx::wxMSW; our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); From 9a9ba02d85dbd735998fac95dba66365d4df9ef8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 8 Dec 2014 20:14:04 +0100 Subject: [PATCH 020/107] Bugfix: infill was not correctly generated when infill_every_layers was used along with raft_layers. Includes regression test. #2396 --- lib/Slic3r/Print/Object.pm | 36 ++++++++++++-------- t/combineinfill.t | 67 ++++++++++++++++++++++++++------------ 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 56279223a..47a81394a 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -991,39 +991,47 @@ sub discover_horizontal_shells { sub combine_infill { my $self = shift; - return unless defined first { $_->config->infill_every_layers > 1 && $_->config->fill_density > 0 } @{$self->print->regions}; - - my @layer_heights = map $_->height, @{$self->layers}; - + # work on each region separately for my $region_id (0 .. ($self->print->region_count-1)) { my $region = $self->print->regions->[$region_id]; my $every = $region->config->infill_every_layers; + next unless $every > 1 && $region->config->fill_density > 0; # limit the number of combined layers to the maximum height allowed by this regions' nozzle my $nozzle_diameter = $self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1); # define the combinations - my @combine = (); # layer_id => thickness in layers + my %combine = (); # layer_idx => number of additional combined lower layers { my $current_height = my $layers = 0; - for my $layer_id (1 .. $#layer_heights) { - my $height = $self->get_layer($layer_id)->height; + for my $layer_idx (0 .. ($self->layer_count-1)) { + my $layer = $self->get_layer($layer_idx); + next if $layer->id == 0; # skip first print layer (which may not be first layer in array because of raft) + my $height = $layer->height; + # check whether the combination of this layer with the lower layers' buffer + # would exceed max layer height or max combined layer count if ($current_height + $height >= $nozzle_diameter || $layers >= $every) { - $combine[$layer_id-1] = $layers; + # append combination to lower layer + $combine{$layer_idx-1} = $layers; $current_height = $layers = 0; } $current_height += $height; $layers++; } + + # append lower layers (if any) to uppermost layer + $combine{$self->layer_count-1} = $layers; } - # skip bottom layer - for my $layer_id (1 .. $#combine) { - next unless ($combine[$layer_id] // 1) > 1; + # loop through layers to which we have assigned layers to combine + for my $layer_idx (sort keys %combine) { + next unless $combine{$layer_idx} > 1; + + # get all the LayerRegion objects to be combined my @layerms = map $self->get_layer($_)->regions->[$region_id], - ($layer_id - ($combine[$layer_id]-1) .. $layer_id); + ($layer_idx - ($combine{$layer_idx}-1) .. $layer_idx); # only combine internal infill for my $type (S_TYPE_INTERNAL) { @@ -1046,7 +1054,7 @@ sub combine_infill { Slic3r::debugf " combining %d %s regions from layers %d-%d\n", scalar(@$intersection), ($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'), - $layer_id-($every-1), $layer_id; + $layer_idx-($every-1), $layer_idx; # $intersection now contains the regions that can be combined across the full amount of layers # so let's remove those areas from all layers @@ -1073,7 +1081,7 @@ sub combine_infill { )}; # apply surfaces back with adjusted depth to the uppermost layer - if ($layerm->id == $layer_id) { + if ($layerm->id == $self->get_layer($layer_idx)->id) { push @new_this_type, map Slic3r::Surface->new( expolygon => $_, diff --git a/t/combineinfill.t b/t/combineinfill.t index 62e22e6b2..5b51899ee 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -11,37 +11,64 @@ use List::Util qw(first); use Slic3r; use Slic3r::Test; -plan tests => 2; +plan tests => 6; { + my $test = sub { + my ($config) = @_; + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash"; + + my $tool = undef; + my %layers = (); # layer_z => 1 + my %layer_infill = (); # layer_z => has_infill + Slic3r::GCode::Reader->new->parse($gcode, sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0 && $tool != $config->support_material_extruder-1) { + $layer_infill{$self->Z} //= 0; + if ($tool == $config->infill_extruder-1) { + $layer_infill{$self->Z} = 1; + } + } + $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} > 0; + }); + + my $layers_with_perimeters = scalar(keys %layer_infill); + my $layers_with_infill = grep $_ > 0, values %layer_infill; + is scalar(keys %layers), $layers_with_perimeters+$config->raft_layers, 'expected number of layers'; + + # first infill layer is never combined, so we don't consider it + $layers_with_infill--; + $layers_with_perimeters--; + + # we expect that infill is generated for half the number of combined layers + # plus for each single layer that was not combined (remainder) + is $layers_with_infill, + int($layers_with_perimeters/$config->infill_every_layers) + ($layers_with_perimeters % $config->infill_every_layers), + 'infill is only present in correct number of layers'; + }; + my $config = Slic3r::Config->new_from_defaults; + $config->set('gcode_comments', 1); $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.5]); $config->set('infill_every_layers', 2); + $config->set('perimeter_extruder', 1); $config->set('infill_extruder', 2); + $config->set('support_material_extruder', 3); + $config->set('support_material_interface_extruder', 3); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash"; + $test->($config); - my $tool = undef; - my %layer_infill = (); # layer_z => has_infill - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - $layer_infill{$self->Z} //= 0; - if ($tool == $config->infill_extruder-1) { - $layer_infill{$self->Z} = 1; - } - } - }); - my $layers_with_infill = grep $_, values %layer_infill; - $layers_with_infill--; # first layer is never combined - is $layers_with_infill, scalar(keys %layer_infill)/2, 'infill is only present in correct number of layers'; + $config->set('skirts', 0); # prevent usage of perimeter_extruder in raft layers + $config->set('raft_layers', 5); + $test->($config); } # the following needs to be adapted to the new API From d350241da341542fb45e4d6a3a7896136ce2fa40 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 8 Dec 2014 21:23:42 +0100 Subject: [PATCH 021/107] Make combine_infill() completely idempotent. Includes unit testing --- lib/Slic3r/Print/Object.pm | 18 +++++++++++++----- t/combineinfill.t | 28 ++++++++++++++++++++++++++-- xs/src/libslic3r/PrintObject.cpp | 4 ++-- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 47a81394a..677cae194 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -678,7 +678,8 @@ sub detect_surfaces_type { # Note: this method should be idempotent, but fill_surfaces gets modified # in place. However we're now only using its boundaries (which are invariant) - # so we're safe + # so we're safe. This guarantees idempotence of prepare_infill() also in case + # that combine_infill() turns some fill_surface into VOID surfaces. my $fill_boundaries = [ map $_->clone->p, @{$layerm->fill_surfaces} ]; $layerm->fill_surfaces->clear; foreach my $surface (@{$layerm->slices}) { @@ -988,12 +989,19 @@ sub discover_horizontal_shells { } # combine fill surfaces across layers +# Idempotence of this method is guaranteed by the fact that we don't remove things from +# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. sub combine_infill { my $self = shift; + # define the type used for voids + my %voidtype = ( + &S_TYPE_INTERNAL() => S_TYPE_INTERNALVOID, + ); + # work on each region separately for my $region_id (0 .. ($self->print->region_count-1)) { - my $region = $self->print->regions->[$region_id]; + my $region = $self->print->get_region($region_id); my $every = $region->config->infill_every_layers; next unless $every > 1 && $region->config->fill_density > 0; @@ -1030,7 +1038,7 @@ sub combine_infill { next unless $combine{$layer_idx} > 1; # get all the LayerRegion objects to be combined - my @layerms = map $self->get_layer($_)->regions->[$region_id], + my @layerms = map $self->get_layer($_)->get_region($region_id), ($layer_idx - ($combine{$layer_idx}-1) .. $layer_idx); # only combine internal infill @@ -1092,8 +1100,8 @@ sub combine_infill { @$intersection; } else { # save void surfaces - push @this_type, - map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNALVOID), + push @new_this_type, + map Slic3r::Surface->new(expolygon => $_, surface_type => $voidtype{$type}), @{intersection_ex( [ map @{$_->expolygon}, @this_type ], [ @intersection_with_clearance ], diff --git a/t/combineinfill.t b/t/combineinfill.t index 5b51899ee..24cd2bb71 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -9,9 +9,10 @@ BEGIN { use List::Util qw(first); use Slic3r; +use Slic3r::Surface ':types'; use Slic3r::Test; -plan tests => 6; +plan tests => 8; { my $test = sub { @@ -53,7 +54,6 @@ plan tests => 6; }; my $config = Slic3r::Config->new_from_defaults; - $config->set('gcode_comments', 1); $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.5]); @@ -71,6 +71,30 @@ plan tests => 6; $test->($config); } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('layer_height', 0.2); + $config->set('first_layer_height', 0.2); + $config->set('nozzle_diameter', [0.5]); + $config->set('infill_every_layers', 2); + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + $print->process; + + ok defined(first { @{$_->get_region(0)->fill_surfaces->filter_by_type(S_TYPE_INTERNALVOID)} > 0 } + @{$print->print->get_object(0)->layers}), + 'infill combination produces internal void surfaces'; + + # we disable combination after infill has been generated + $config->set('infill_every_layers', 1); + $print->apply_config($config); + $print->process; + + ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 } + @{$print->print->get_object(0)->layers}), + 'infill combination is idempotent'; +} + # the following needs to be adapted to the new API if (0) { my $config = Slic3r::Config->new_from_defaults; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 16c904cfb..0da9d589b 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -249,6 +249,8 @@ PrintObject::invalidate_state_by_config_options(const std::vector Date: Mon, 8 Dec 2014 22:05:26 +0100 Subject: [PATCH 022/107] Remember window size and position. #1253 #2251 --- lib/Slic3r/GUI/MainFrame.pm | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 57729ad9d..cb3426f80 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -38,16 +38,6 @@ sub new { $self->{loaded} = 1; - # declare events - EVT_CLOSE($self, sub { - my (undef, $event) = @_; - if ($event->CanVeto && !$self->check_unsaved_changes) { - $event->Veto; - return; - } - $event->Skip; - }); - # initialize layout { my $sizer = Wx::BoxSizer->new(wxVERTICAL); @@ -56,11 +46,36 @@ sub new { $self->SetSizer($sizer); $self->Fit; $self->SetMinSize([760, 490]); - $self->SetSize($self->GetMinSize); + if (defined $Slic3r::GUI::Settings->{_}{main_frame_size}) { + $self->SetSize([ split ',', $Slic3r::GUI::Settings->{_}{main_frame_size}, 2 ]); + $self->Move([ split ',', $Slic3r::GUI::Settings->{_}{main_frame_pos}, 2 ]); + $self->Maximize(1) if $Slic3r::GUI::Settings->{_}{main_frame_maximized}; + } else { + $self->SetSize($self->GetMinSize); + } $self->Show; $self->Layout; } + # declare events + EVT_CLOSE($self, sub { + my (undef, $event) = @_; + + if ($event->CanVeto && !$self->check_unsaved_changes) { + $event->Veto; + return; + } + + # save window size + $Slic3r::GUI::Settings->{_}{main_frame_pos} = join ',', $self->GetScreenPositionXY; + $Slic3r::GUI::Settings->{_}{main_frame_size} = join ',', $self->GetSizeWH; + $Slic3r::GUI::Settings->{_}{main_frame_maximized} = $self->IsMaximized; + wxTheApp->save_settings; + + # propagate event + $event->Skip; + }); + return $self; } From c8596c5c58d7331330ca6d9403ad0c68c9dee915 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 9 Dec 2014 01:08:58 +0100 Subject: [PATCH 023/107] Limit "Only retract when crossing perimeters" so that retraction is triggered also when crossing the boundaries of a single region. #2298 --- lib/Slic3r/GCode.pm | 6 ++---- xs/src/libslic3r/Layer.cpp | 12 ++++++++++++ xs/src/libslic3r/Layer.hpp | 1 + xs/src/libslic3r/Surface.cpp | 9 +++++++++ xs/src/libslic3r/Surface.hpp | 1 + xs/src/libslic3r/SurfaceCollection.cpp | 12 ++++++++++++ xs/src/libslic3r/SurfaceCollection.hpp | 1 + xs/xsp/Layer.xsp | 9 +++++++++ 8 files changed, 47 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index c8b72a8ad..0f322ca15 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -344,8 +344,7 @@ sub travel_to { if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id) || ($self->config->only_retract_when_crossing_perimeters && $self->config->fill_density > 0 - && $self->layer->slices->contains_line($travel) - && (!$self->layer->has_upper_layer || $self->layer->upper_layer->slices->contains_line($travel))) + && $self->layer->any_internal_region_slice_contains_line($travel)) || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel)) ) { # Just perform a straight travel move without any retraction. @@ -608,8 +607,7 @@ sub _plan { # if the path is not contained in a single island we need to retract $gcode .= $gcodegen->retract if !$gcodegen->config->only_retract_when_crossing_perimeters - || !$gcodegen->layer->slices->contains_polyline($travel) - || ($gcodegen->layer->has_upper_layer && !$gcodegen->layer->upper_layer->slices->contains_polyline($travel)); + || !$gcodegen->layer->any_internal_region_slice_contains_polyline($travel); # append the actual path and return # use G1 because we rely on paths being straight (G0 may make round paths) diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 17747d06d..26e9ab7f8 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -120,6 +120,18 @@ Layer::make_slices() } } +template +bool +Layer::any_internal_region_slice_contains(const T &item) const +{ + FOREACH_LAYERREGION(this, layerm) { + if ((*layerm)->slices.any_internal_contains(item)) return true; + } + return false; +} +template bool Layer::any_internal_region_slice_contains(const Line &item) const; +template bool Layer::any_internal_region_slice_contains(const Polyline &item) const; + #ifdef SLIC3RXS REGISTER_CLASS(Layer, "Layer"); diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index cd0250089..ff8cca1d9 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -90,6 +90,7 @@ class Layer { LayerRegion* add_region(PrintRegion* print_region); void make_slices(); + template bool any_internal_region_slice_contains(const T &item) const; protected: int _id; // sequential number of layer, 0-based diff --git a/xs/src/libslic3r/Surface.cpp b/xs/src/libslic3r/Surface.cpp index 3ea6429af..e4625f799 100644 --- a/xs/src/libslic3r/Surface.cpp +++ b/xs/src/libslic3r/Surface.cpp @@ -26,6 +26,15 @@ Surface::is_external() const || this->surface_type == stBottomBridge; } +bool +Surface::is_internal() const +{ + return this->surface_type == stInternal + || this->surface_type == stInternalBridge + || this->surface_type == stInternalSolid + || this->surface_type == stInternalVoid; +} + bool Surface::is_bottom() const { diff --git a/xs/src/libslic3r/Surface.hpp b/xs/src/libslic3r/Surface.hpp index c98567cf0..28a90799a 100644 --- a/xs/src/libslic3r/Surface.hpp +++ b/xs/src/libslic3r/Surface.hpp @@ -24,6 +24,7 @@ class Surface double area() const; bool is_solid() const; bool is_external() const; + bool is_internal() const; bool is_bottom() const; bool is_bridge() const; diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index 1590e7a21..027138818 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -68,6 +68,18 @@ SurfaceCollection::group(std::vector *retval) } } +template +bool +SurfaceCollection::any_internal_contains(const T &item) const +{ + for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { + if (surface->is_internal() && surface->expolygon.contains(item)) return true; + } + return false; +} +template bool SurfaceCollection::any_internal_contains(const Line &item) const; +template bool SurfaceCollection::any_internal_contains(const Polyline &item) const; + #ifdef SLIC3RXS REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); #endif diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index fe3fae8c6..494bb1a21 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -15,6 +15,7 @@ class SurfaceCollection operator ExPolygons() const; void simplify(double tolerance); void group(std::vector *retval); + template bool any_internal_contains(const T &item) const; }; } diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index e0f7e8a17..a84d4464f 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -69,6 +69,10 @@ %code%{ RETVAL = (int)(intptr_t)THIS; %}; void make_slices(); + bool any_internal_region_slice_contains_line(Line* line) + %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; + bool any_internal_region_slice_contains_polyline(Polyline* polyline) + %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; }; %name{Slic3r::Layer::Support} class SupportLayer { @@ -114,4 +118,9 @@ Ref slices() %code%{ RETVAL = &THIS->slices; %}; + + bool any_internal_region_slice_contains_line(Line* line) + %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; + bool any_internal_region_slice_contains_polyline(Polyline* polyline) + %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; }; From 829bd7378e8725227d0c06741c7ed46abf80c6cd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 10 Dec 2014 17:34:59 +0100 Subject: [PATCH 024/107] Remove the Rotate... button from Windows as well, like for other operating systems --- lib/Slic3r/GUI/Plater.pm | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 42c672bf5..bae0cfb74 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -26,7 +26,7 @@ use constant TB_45CW => &Wx::NewId; use constant TB_45CCW => &Wx::NewId; use constant TB_SCALE => &Wx::NewId; use constant TB_SPLIT => &Wx::NewId; -use constant TB_VIEW => &Wx::NewId; +use constant TB_CUT => &Wx::NewId; use constant TB_SETTINGS => &Wx::NewId; use constant CONFIG_TIMER_ID => &Wx::NewId; @@ -127,7 +127,7 @@ sub new { $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new("$Slic3r::var/arrow_out.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_VIEW, "Cut…", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG), ''); } else { @@ -140,14 +140,13 @@ sub new { decrease => "", rotate45ccw => "", rotate45cw => "", - rotate => "Rotate…", changescale => "Scale…", split => "Split", - view => "View/Cut…", + cut => "Cut…", settings => "Settings…", ); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); - for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw rotate changescale split view settings)) { + for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } @@ -190,7 +189,7 @@ sub new { rotate45ccw arrow_rotate_anticlockwise.png changescale arrow_out.png split shape_ungroup.png - view package.png + cut package.png settings cog.png ); for (grep $self->{"btn_$_"}, keys %icons) { @@ -216,7 +215,7 @@ sub new { EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) }); EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); }); EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; }); - EVT_TOOL($self, TB_VIEW, sub { $_[0]->object_cut_dialog }); + EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog }); EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog }); } else { EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); @@ -229,7 +228,7 @@ sub new { EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45) }); EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); }); EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; }); - EVT_BUTTON($self, $self->{btn_view}, sub { $_[0]->object_cut_dialog }); + EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog }); EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); } @@ -1312,11 +1311,11 @@ sub selection_changed { my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split view settings); + for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool($_, $have_sel) - for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_VIEW, TB_SETTINGS); + for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); } if ($self->{object_info_size}) { # have we already loaded the info pane? From baf070a36d52dd1c35473cc04c91501c592bac19 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 12 Dec 2014 12:34:40 +0100 Subject: [PATCH 025/107] Bugfix: workaround for MSW wxWidgets not drawing the slider --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index f573ccebb..311e4dea4 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -23,7 +23,9 @@ sub new { $self, -1, 0, # default 0, # min - 0, # max + # we set max to a bogus non-zero value because the MSW implementation of wxSlider + # will skip drawing the slider if max <= min: + 1, # max wxDefaultPosition, wxDefaultSize, wxVERTICAL | wxSL_INVERSE, From e8ab9ac13a8355a2a9aca9b574142d636b1fe3b6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 12 Dec 2014 19:14:52 +0100 Subject: [PATCH 026/107] Ported Print::bounding_box(), Print::total_bounding_box(), Print::skirt_flow(), Print:skirt_first_layer_height() to XS --- lib/Slic3r/Print.pm | 57 ---------------------- lib/Slic3r/Print/SupportMaterial.pm | 3 -- xs/MANIFEST | 2 + xs/src/libslic3r/Print.cpp | 72 ++++++++++++++++++++++++++++ xs/src/libslic3r/Print.hpp | 4 ++ xs/src/libslic3r/SupportMaterial.hpp | 11 +++++ xs/xsp/Print.xsp | 4 ++ xs/xsp/SupportMaterial.xsp | 16 +++++++ 8 files changed, 109 insertions(+), 60 deletions(-) create mode 100644 xs/src/libslic3r/SupportMaterial.hpp create mode 100644 xs/xsp/SupportMaterial.xsp diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 88b527080..09ebd40a2 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -40,46 +40,6 @@ sub total_layer_count { return max(map $_->total_layer_count, @{$self->objects}); } -# the bounding box of objects placed in copies position -# (without taking skirt/brim/support material into account) -sub bounding_box { - my $self = shift; - - my @points = (); - foreach my $object (@{$self->objects}) { - foreach my $copy (@{$object->_shifted_copies}) { - push @points, - [ $copy->[X], $copy->[Y] ], - [ $copy->[X] + $object->size->[X], $copy->[Y] + $object->size->[Y] ]; - } - } - return Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), @points ]); -} - -# the total bounding box of extrusions, including skirt/brim/support material -sub total_bounding_box { - my ($self) = @_; - - # get objects bounding box - my $bb = $self->bounding_box; - - # check how much we need to increase it - my $extra = 0; - if ($self->has_support_material) { - $extra = &Slic3r::Print::SupportMaterial::MARGIN; - } - $extra = max($extra, $self->config->brim_width); - if ($self->config->skirts > 0) { - my $skirt_flow = $self->skirt_flow; - $extra = max($extra, $self->config->brim_width + $self->config->skirt_distance + ($self->config->skirts * $skirt_flow->spacing)); - } - - if ($extra > 0) { - $bb->offset(scale $extra); - } - return $bb; -} - sub size { my $self = shift; return $self->bounding_box->size; @@ -422,23 +382,6 @@ sub make_brim { $self->set_step_done(STEP_BRIM); } -sub skirt_first_layer_height { - my ($self) = @_; - return $self->objects->[0]->config->get_abs_value('first_layer_height'); -} - -sub skirt_flow { - my ($self) = @_; - - return Slic3r::Flow->new_from_width( - width => ($self->config->first_layer_extrusion_width || $self->regions->[0]->config->perimeter_extrusion_width), - role => FLOW_ROLE_PERIMETER, - nozzle_diameter => $self->config->get_at('nozzle_diameter', $self->objects->[0]->config->support_material_extruder-1), - layer_height => $self->skirt_first_layer_height, - bridge_flow_ratio => 0, - ); -} - sub write_gcode { my $self = shift; my ($file) = @_; diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 596d87983..01df21a55 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -17,9 +17,6 @@ has 'interface_flow' => (is => 'rw', required => 1); use constant DEBUG_CONTACT_ONLY => 0; -# how much we extend support around the actual contact area -use constant MARGIN => 1.5; - # increment used to reach MARGIN in steps to avoid trespassing thin objects use constant MARGIN_STEP => MARGIN/3; diff --git a/xs/MANIFEST b/xs/MANIFEST index ac073b1d0..4a3907872 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1703,6 +1703,7 @@ src/libslic3r/PrintConfig.cpp src/libslic3r/PrintConfig.hpp src/libslic3r/PrintObject.cpp src/libslic3r/PrintRegion.cpp +src/libslic3r/SupportMaterial.hpp src/libslic3r/Surface.cpp src/libslic3r/Surface.hpp src/libslic3r/SurfaceCollection.cpp @@ -1773,6 +1774,7 @@ xsp/Polygon.xsp xsp/Polyline.xsp xsp/PolylineCollection.xsp xsp/Print.xsp +xsp/SupportMaterial.xsp xsp/Surface.xsp xsp/SurfaceCollection.xsp xsp/TriangleMesh.xsp diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index c4a231c78..88e88a45a 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -1,7 +1,9 @@ #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" +#include "Flow.hpp" #include "Geometry.hpp" +#include "SupportMaterial.hpp" #include namespace Slic3r { @@ -641,6 +643,76 @@ Print::validate() const } } +// the bounding box of objects placed in copies position +// (without taking skirt/brim/support material into account) +BoundingBox +Print::bounding_box() const +{ + BoundingBox bb; + FOREACH_OBJECT(this, object) { + for (Points::const_iterator copy = (*object)->_shifted_copies.begin(); copy != (*object)->_shifted_copies.end(); ++copy) { + bb.merge(*copy); + + Point p = *copy; + p.translate((*object)->size); + bb.merge(p); + } + } + return bb; +} + +// the total bounding box of extrusions, including skirt/brim/support material +// this methods needs to be called even when no steps were processed, so it should +// only use configuration values +BoundingBox +Print::total_bounding_box() const +{ + // get objects bounding box + BoundingBox bb = this->bounding_box(); + + // check how much we need to increase it + double extra = 0; // unscaled + if (this->has_support_material()) + extra = SUPPORT_MATERIAL_MARGIN; + + extra = std::max(extra, this->config.brim_width.value); + if (this->config.skirts > 0) { + Flow skirt_flow = this->skirt_flow(); + extra = std::max( + extra, + this->config.brim_width.value + this->config.skirt_distance.value + (this->config.skirts.value * skirt_flow.spacing()) + ); + } + + if (extra > 0) + bb.offset(scale_(extra)); + + return bb; +} + +double +Print::skirt_first_layer_height() const +{ + if (this->objects.empty()) CONFESS("skirt_first_layer_height() can't be called without PrintObjects"); + return this->objects.front()->config.get_abs_value("first_layer_height"); +} + +Flow +Print::skirt_flow() const +{ + ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; + if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; + + return Flow::new_from_config_width( + frPerimeter, + width, + this->config.nozzle_diameter.get_at(this->objects.front()->config.support_material_extruder-1), + this->skirt_first_layer_height(), + 0 + ); +} + + PrintRegionConfig Print::_region_config_from_model_volume(const ModelVolume &volume) { diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 27ba83780..45e1eb25c 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -187,6 +187,10 @@ class Print bool apply_config(DynamicPrintConfig config); void init_extruders(); void validate() const; + BoundingBox bounding_box() const; + BoundingBox total_bounding_box() const; + double skirt_first_layer_height() const; + Flow skirt_flow() const; std::set extruders() const; void _simplify_slices(double distance); diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp new file mode 100644 index 000000000..edea22695 --- /dev/null +++ b/xs/src/libslic3r/SupportMaterial.hpp @@ -0,0 +1,11 @@ +#ifndef slic3r_SupportMaterial_hpp_ +#define slic3r_SupportMaterial_hpp_ + +namespace Slic3r { + +// how much we extend support around the actual contact area +#define SUPPORT_MATERIAL_MARGIN 1.5 + +} + +#endif diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 2213a9511..7ed3d6853 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -193,6 +193,10 @@ _constant() croak("%s\n", e.what()); } %}; + Clone bounding_box(); + Clone total_bounding_box(); + double skirt_first_layer_height(); + Clone skirt_flow(); %{ double diff --git a/xs/xsp/SupportMaterial.xsp b/xs/xsp/SupportMaterial.xsp new file mode 100644 index 000000000..a0301f248 --- /dev/null +++ b/xs/xsp/SupportMaterial.xsp @@ -0,0 +1,16 @@ +%module{Slic3r::XS}; + +#include +#include "libslic3r/SupportMaterial.hpp" + +%package{Slic3r::Print::SupportMaterial}; +%{ + +SV* +MARGIN() + PROTOTYPE: + CODE: + RETVAL = newSVnv(SUPPORT_MATERIAL_MARGIN); + OUTPUT: RETVAL + +%} \ No newline at end of file From 93d9ee9205bfcb67eff98862ce9683e92f6435f3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 12 Dec 2014 19:25:50 +0100 Subject: [PATCH 027/107] Consider extrusion width in Print::total_bounding_box() --- xs/src/libslic3r/Print.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 88e88a45a..8a409c045 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -670,17 +670,27 @@ Print::total_bounding_box() const // get objects bounding box BoundingBox bb = this->bounding_box(); - // check how much we need to increase it - double extra = 0; // unscaled - if (this->has_support_material()) - extra = SUPPORT_MATERIAL_MARGIN; + // we need to offset the objects bounding box by at least half the perimeters extrusion width + Flow perimeter_flow = this->objects.front()->get_layer(0)->get_region(0)->flow(frPerimeter); + double extra = perimeter_flow.width/2; - extra = std::max(extra, this->config.brim_width.value); - if (this->config.skirts > 0) { - Flow skirt_flow = this->skirt_flow(); + // consider support material + if (this->has_support_material()) { + extra = std::max(extra, SUPPORT_MATERIAL_MARGIN); + } + + // consider brim and skirt + Flow skirt_flow = this->skirt_flow(); + if (this->config.brim_width.value > 0) { + extra = std::max(extra, this->config.brim_width.value + skirt_flow.width/2); + } + if (this->config.skirts.value > 0) { extra = std::max( extra, - this->config.brim_width.value + this->config.skirt_distance.value + (this->config.skirts.value * skirt_flow.spacing()) + this->config.brim_width.value + + this->config.skirt_distance.value + + this->config.skirts.value * skirt_flow.spacing() + + skirt_flow.width/2 ); } From b28fb2ef173a7e29d8d9c0992ec63b64c80fe497 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 12 Dec 2014 20:04:45 +0100 Subject: [PATCH 028/107] Bugfix: objects were not aligned to Z = 0 before exporting STL from plater. #2393 --- lib/Slic3r/GUI/Plater.pm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index bae0cfb74..9b10d321e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1074,7 +1074,15 @@ sub export_stl { return if !@{$self->{objects}}; my $output_file = $self->_get_export_file('STL') or return; - Slic3r::Format::STL->write_file($output_file, $self->{model}, binary => 1); + + # In order to allow for consistent positioning in the parts editor, + # we never alter the original Z position. Meshes are aligned to zero + # at slice time. So we do the same before exporting. + my $model = $self->{model}->clone; + foreach my $model_object (@{$model->objects}) { + $model_object->translate(0,0,-$model_object->bounding_box->z_min); + } + Slic3r::Format::STL->write_file($output_file, $model, binary => 1); $self->statusbar->SetStatusText("STL file exported to $output_file"); # this method gets executed in a separate thread by wxWidgets since it's a button handler From 050f9ff61af1a673284d469b676df8628eb6e30f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 12 Dec 2014 20:16:32 +0100 Subject: [PATCH 029/107] Removed two useless methods in Print --- xs/src/libslic3r/Print.cpp | 58 +++++++++++++++----------------------- xs/src/libslic3r/Print.hpp | 2 -- xs/xsp/Print.xsp | 6 ---- 3 files changed, 23 insertions(+), 43 deletions(-) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 8a409c045..6edd32609 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -76,46 +76,20 @@ Print::get_object(size_t idx) return objects.at(idx); } -PrintObject* -Print::add_object(ModelObject *model_object, const BoundingBoxf3 &modobj_bbox) -{ - PrintObject *object = new PrintObject(this, model_object, modobj_bbox); - objects.push_back(object); - - // invalidate steps - this->invalidate_step(psSkirt); - this->invalidate_step(psBrim); - - return object; -} - -PrintObject* -Print::set_new_object(size_t idx, ModelObject *model_object, const BoundingBoxf3 &modobj_bbox) -{ - if (idx >= this->objects.size()) throw "bad idx"; - - PrintObjectPtrs::iterator old_it = this->objects.begin() + idx; - // before deleting object, invalidate all of its steps in order to - // invalidate all of the dependent ones in Print - (*old_it)->invalidate_all_steps(); - delete *old_it; - - PrintObject *object = new PrintObject(this, model_object, modobj_bbox); - this->objects[idx] = object; - return object; -} - void Print::delete_object(size_t idx) { PrintObjectPtrs::iterator i = this->objects.begin() + idx; + + // before deleting object, invalidate all of its steps in order to + // invalidate all of the dependent ones in Print + (*i)->invalidate_all_steps(); + + // destroy object and remove it from our container delete *i; this->objects.erase(i); // TODO: purge unused regions - - this->state.invalidate(psSkirt); - this->state.invalidate(psBrim); } void @@ -375,9 +349,23 @@ Print::add_model_object(ModelObject* model_object, int idx) { BoundingBoxf3 bb; model_object->raw_bounding_box(&bb); - o = (idx != -1) - ? this->set_new_object(idx, model_object, bb) - : this->add_object(model_object, bb); + if (idx != -1) { + // replacing existing object + PrintObjectPtrs::iterator old_it = this->objects.begin() + idx; + // before deleting object, invalidate all of its steps in order to + // invalidate all of the dependent ones in Print + (*old_it)->invalidate_all_steps(); + delete *old_it; + + this->objects[idx] = o = new PrintObject(this, model_object, bb); + } else { + o = new PrintObject(this, model_object, bb); + objects.push_back(o); + + // invalidate steps + this->invalidate_step(psSkirt); + this->invalidate_step(psBrim); + } } for (ModelVolumePtrs::const_iterator v_i = model_object->volumes.begin(); v_i != model_object->volumes.end(); ++v_i) { diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 45e1eb25c..fd0e6a19d 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -168,8 +168,6 @@ class Print // methods for handling objects void clear_objects(); PrintObject* get_object(size_t idx); - PrintObject* add_object(ModelObject *model_object, const BoundingBoxf3 &modobj_bbox); - PrintObject* set_new_object(size_t idx, ModelObject *model_object, const BoundingBoxf3 &modobj_bbox); void delete_object(size_t idx); void reload_object(size_t idx); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 7ed3d6853..aed6f6e4e 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -139,12 +139,6 @@ _constant() %code%{ RETVAL = &THIS->objects; %}; void clear_objects(); Ref get_object(int idx); - Ref add_object(ModelObject* model_object, - BoundingBoxf3 *modobj_bbox) - %code%{ RETVAL = THIS->add_object(model_object, *modobj_bbox); %}; - Ref set_new_object(size_t idx, ModelObject* model_object, - BoundingBoxf3 *modobj_bbox) - %code%{ RETVAL = THIS->set_new_object(idx, model_object, *modobj_bbox); %}; void delete_object(int idx); void reload_object(int idx); size_t object_count() From 360dee862b067d8768c739cd6c8622a0463bbe4e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 12 Dec 2014 22:43:04 +0100 Subject: [PATCH 030/107] Keep model objects aligned to Z = 0 in plater --- lib/Slic3r/GUI/Plater.pm | 22 ++++++++-------------- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 4 +++- lib/Slic3r/Print/Object.pm | 2 +- lib/Slic3r/Print/Simple.pm | 1 + xs/src/libslic3r/Model.cpp | 21 +++++++++++++-------- xs/src/libslic3r/Model.hpp | 3 ++- xs/src/libslic3r/Point.cpp | 8 +++++++- xs/src/libslic3r/Point.hpp | 4 +++- xs/t/19_model.t | 6 +++--- xs/xsp/Model.xsp | 4 ++-- 10 files changed, 43 insertions(+), 32 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 9b10d321e..3b0d037a2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -469,7 +469,7 @@ sub load_model_objects { $need_arrange = 1; # add a default instance and center object around origin - $o->center_around_origin; + $o->center_around_origin; # also aligns object to Z = 0 $o->add_instance(offset => $bed_centerf); } @@ -1074,15 +1074,7 @@ sub export_stl { return if !@{$self->{objects}}; my $output_file = $self->_get_export_file('STL') or return; - - # In order to allow for consistent positioning in the parts editor, - # we never alter the original Z position. Meshes are aligned to zero - # at slice time. So we do the same before exporting. - my $model = $self->{model}->clone; - foreach my $model_object (@{$model->objects}) { - $model_object->translate(0,0,-$model_object->bounding_box->z_min); - } - Slic3r::Format::STL->write_file($output_file, $model, binary => 1); + Slic3r::Format::STL->write_file($output_file, $self->{model}, binary => 1); $self->statusbar->SetStatusText("STL file exported to $output_file"); # this method gets executed in a separate thread by wxWidgets since it's a button handler @@ -1282,10 +1274,12 @@ sub object_settings_dialog { $self->pause_background_process; $dlg->ShowModal; - # update thumbnail since parts may have changed - if ($dlg->PartsChanged) { - $self->make_thumbnail($obj_idx); - } + # update thumbnail since parts may have changed + if ($dlg->PartsChanged) { + # recenter and re-align to Z = 0 + $model_object->center_around_origin; + $self->make_thumbnail($obj_idx); + } # update print if ($dlg->PartsChanged || $dlg->PartSettingsChanged) { diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index d24aa8932..bacc119d7 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -214,7 +214,7 @@ sub on_btn_load { $new_volume->set_name(basename($input_file)); # apply the same translation we applied to the object - $new_volume->mesh->translate(@{$self->{model_object}->origin_translation}, 0); + $new_volume->mesh->translate(@{$self->{model_object}->origin_translation}); # set a default extruder value, since user can't add it manually $new_volume->config->set_ifndef('extruder', 0); @@ -226,7 +226,9 @@ sub on_btn_load { $self->reload_tree; if ($self->{canvas}) { + $self->{canvas}->reset_objects; $self->{canvas}->load_object($self->{model_object}); + $self->{canvas}->set_bounding_box($self->{model_object}->bounding_box); $self->{canvas}->Render; } } diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 677cae194..4a5ec01fe 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -332,7 +332,7 @@ sub _slice_region { # consider the first one $self->model_object->instances->[0]->transform_mesh($mesh, 1); - # align mesh to Z = 0 and apply XY shift + # align mesh to Z = 0 (it should be already aligned actually) and apply XY shift $mesh->translate((map unscale(-$_), @{$self->_copies_shift}), -$self->model_object->bounding_box->z_min); # perform actual slicing diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm index 7647bbfc4..5618484fa 100644 --- a/lib/Slic3r/Print/Simple.pm +++ b/lib/Slic3r/Print/Simple.pm @@ -68,6 +68,7 @@ sub set_model { # if all input objects have defined position(s) apply duplication to the whole model $model->duplicate($self->duplicate, $self->_print->config->min_object_distance); } + $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects}; $model->center_instances_around_point($self->print_center); foreach my $model_object (@{$model->objects}) { diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index d5e9aa6ee..06919cedd 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -480,26 +480,31 @@ ModelObject::center_around_origin() mesh.bounding_box(&bb); } - // first align to origin on XY - double shift_x = -bb.min.x; - double shift_y = -bb.min.y; + // first align to origin on XYZ + Vectorf3 vector(-bb.min.x, -bb.min.y, -bb.min.z); // then center it on XY Sizef3 size = bb.size(); - shift_x -= size.x/2; - shift_y -= size.y/2; + vector.x -= size.x/2; + vector.y -= size.y/2; - this->translate(shift_x, shift_y, 0); - this->origin_translation.translate(shift_x, shift_y); + this->translate(vector); + this->origin_translation.translate(vector); if (!this->instances.empty()) { for (ModelInstancePtrs::const_iterator i = this->instances.begin(); i != this->instances.end(); ++i) { - (*i)->offset.translate(-shift_x, -shift_y); + (*i)->offset.translate(-vector.x, -vector.y); } this->update_bounding_box(); } } +void +ModelObject::translate(const Vectorf3 &vector) +{ + this->translate(vector.x, vector.y, vector.z); +} + void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z) { diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 9cbe6be40..62bb38bfc 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -99,7 +99,7 @@ class ModelObject center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preset alignment when user expects that. */ - Pointf origin_translation; + Pointf3 origin_translation; // these should be private but we need to expose them via XS until all methods are ported BoundingBoxf3 _bounding_box; @@ -126,6 +126,7 @@ class ModelObject void raw_bounding_box(BoundingBoxf3* bb) const; void instance_bounding_box(size_t instance_idx, BoundingBoxf3* bb) const; void center_around_origin(); + void translate(const Vectorf3 &vector); void translate(coordf_t x, coordf_t y, coordf_t z); void scale(const Pointf3 &versor); size_t materials_count() const; diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 76da85334..5f3f1da01 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -41,7 +41,7 @@ Point::translate(double x, double y) } void -Point::translate(const Point &vector) +Point::translate(const Vector &vector) { this->translate(vector.x, vector.y); } @@ -340,6 +340,12 @@ Pointf3::scale(double factor) this->z *= factor; } +void +Pointf3::translate(const Vectorf3 &vector) +{ + this->translate(vector.x, vector.y, vector.z); +} + void Pointf3::translate(double x, double y, double z) { diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 60127b1b7..271c9584e 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -14,6 +14,7 @@ class Point; class Pointf; class Pointf3; typedef Point Vector; +typedef Pointf3 Vectorf3; typedef std::vector Points; typedef std::vector PointPtrs; typedef std::vector PointConstPtrs; @@ -36,7 +37,7 @@ class Point std::string wkt() const; void scale(double factor); void translate(double x, double y); - void translate(const Point &vector); + void translate(const Vector &vector); void rotate(double angle, const Point ¢er); bool coincides_with(const Point &point) const; bool coincides_with_epsilon(const Point &point) const; @@ -93,6 +94,7 @@ class Pointf3 : public Pointf coordf_t z; explicit Pointf3(coordf_t _x = 0, coordf_t _y = 0, coordf_t _z = 0): Pointf(_x, _y), z(_z) {}; void scale(double factor); + void translate(const Vectorf3 &vector); void translate(double x, double y, double z); }; diff --git a/xs/t/19_model.t b/xs/t/19_model.t index 56b3631af..d6f6d97a1 100644 --- a/xs/t/19_model.t +++ b/xs/t/19_model.t @@ -10,9 +10,9 @@ use Test::More tests => 4; 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'; + isa_ok $object->origin_translation, 'Slic3r::Pointf3::Ref'; + $object->origin_translation->translate(10,0,0); + is_deeply \@{$object->origin_translation}, [10,0,0], 'origin_translation is modified by ref'; my $lhr = [ [ 5, 10, 0.1 ] ]; $object->set_layer_height_ranges($lhr); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index fd31a93d4..8d795f7d6 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -188,9 +188,9 @@ ModelMaterial::attributes() void set_layer_height_ranges(t_layer_height_ranges ranges) %code%{ THIS->layer_height_ranges = ranges; %}; - Ref origin_translation() + Ref origin_translation() %code%{ RETVAL = &THIS->origin_translation; %}; - void set_origin_translation(Pointf* point) + void set_origin_translation(Pointf3* point) %code%{ THIS->origin_translation = *point; %}; bool needed_repair() const; From 133466a6b5045c88364177893f060612d3b62762 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 12 Dec 2014 22:50:43 +0100 Subject: [PATCH 031/107] Minor improvements to the Skirt Loops tooltip --- xs/src/libslic3r/PrintConfig.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 54deaee82..8e9720b76 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -664,8 +664,8 @@ PrintConfigDef::build_def() { Options["skirt_height"].cli = "skirt-height=i"; Options["skirts"].type = coInt; - Options["skirts"].label = "Loops"; - Options["skirts"].tooltip = "Number of loops for this skirt, in other words its thickness. Set this to zero to disable skirt."; + Options["skirts"].label = "Loops (minimum)"; + Options["skirts"].tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely."; Options["skirts"].cli = "skirts=i"; Options["skirts"].min = 0; From ac495e974ab7645c60e06ddd34b4138592aa251c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 12 Dec 2014 23:02:28 +0100 Subject: [PATCH 032/107] Update test --- xs/xsp/Point.xsp | 1 + 1 file changed, 1 insertion(+) diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index e1163ea5c..f058ead97 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -116,4 +116,5 @@ Point::coincides_with(point_sv) %code{% THIS->y = val; %}; void set_z(double val) %code{% THIS->z = val; %}; + void translate(double x, double y, double z); }; From 84760b8d59f908aad8a262e270bc41a669c1954e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Dec 2014 00:01:24 +0100 Subject: [PATCH 033/107] Require a recent threads.pm version because of upstream bug 85140 potentially causing deadlocks when stopping running threads. #2394 https://rt.cpan.org/Ticket/Display.html?id=85140 --- Build.PL | 1 + lib/Slic3r.pm | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Build.PL b/Build.PL index fee51ef54..fc5b16211 100644 --- a/Build.PL +++ b/Build.PL @@ -22,6 +22,7 @@ my %prereqs = qw( Test::More 0 Thread::Semaphore 0 IO::Scalar 0 + threads 1.96 Time::HiRes 0 ); my %recommends = qw( diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index e2c2646ef..d4344963f 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -82,17 +82,21 @@ use constant EXTERNAL_INFILL_MARGIN => 3; use constant INSET_OVERLAP_TOLERANCE => 0.2; # keep track of threads we created -my @threads : shared = (); +my @threads = (); my $sema = Thread::Semaphore->new; my $paused = 0; sub spawn_thread { my ($cb) = @_; + my $parent_tid = threads->tid; + @_ = (); my $thread = threads->create(sub { + Slic3r::debugf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid; local $SIG{'KILL'} = sub { - Slic3r::debugf "Exiting thread...\n"; + Slic3r::debugf "Exiting thread %d...\n", threads->tid; + kill_all_threads(); Slic3r::thread_cleanup(); threads->exit(); }; @@ -205,6 +209,7 @@ sub kill_all_threads { # detach any running thread created in the current one my @killed = (); foreach my $thread (get_running_threads()) { + Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid; $thread->kill('KILL'); push @killed, $thread; } @@ -212,7 +217,11 @@ sub kill_all_threads { # unlock semaphore before we block on wait # otherwise we'd get a deadlock if threads were paused resume_threads(); - $_->join for @killed; # block until threads are killed + foreach my $thread (@killed) { + Slic3r::debugf " Threads %d waiting for %d...\n", threads->tid, $thread->tid; + $thread->join; # block until threads are killed + Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid; + } @threads = (); } From e9166a8fe64b85f68cea59863a0f9bc2ea44c431 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Dec 2014 15:01:53 +0100 Subject: [PATCH 034/107] Use a semaphore to synchronize threads instead of blocking with join(). Lock threads array in order to ensure all of them are signalled. #2394 --- lib/Slic3r.pm | 67 +++++++++++++++++++++++--------------- lib/Slic3r/Print/Object.pm | 2 -- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index d4344963f..9019607db 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -82,30 +82,37 @@ use constant EXTERNAL_INFILL_MARGIN => 3; use constant INSET_OVERLAP_TOLERANCE => 0.2; # keep track of threads we created -my @threads = (); -my $sema = Thread::Semaphore->new; +my @my_threads = (); +my @threads : shared = (); +my $pause_sema = Thread::Semaphore->new; +my $parallel_sema; my $paused = 0; sub spawn_thread { my ($cb) = @_; my $parent_tid = threads->tid; + lock @threads; @_ = (); my $thread = threads->create(sub { + @my_threads = (); + Slic3r::debugf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid; local $SIG{'KILL'} = sub { Slic3r::debugf "Exiting thread %d...\n", threads->tid; + $parallel_sema->up if $parallel_sema; kill_all_threads(); Slic3r::thread_cleanup(); threads->exit(); }; local $SIG{'STOP'} = sub { - $sema->down; - $sema->up; + $pause_sema->down; + $pause_sema->up; }; $cb->(); }); + push @my_threads, $thread->tid; push @threads, $thread->tid; return $thread; } @@ -118,10 +125,15 @@ sub parallelize { my $q = Thread::Queue->new; $q->enqueue(@items, (map undef, 1..$params{threads})); + $parallel_sema = Thread::Semaphore->new(-$params{threads}); + $parallel_sema->up; my $thread_cb = sub { # execute thread callback $params{thread_cb}->($q); + # signal the parent thread that we're done + $parallel_sema->up; + # cleanup before terminating thread Slic3r::thread_cleanup(); @@ -133,17 +145,17 @@ sub parallelize { # The downside to using this exit is that we can't return # any value to the main thread but we're not doing that # anymore anyway. - # collect_cb is completely useless now - # and should be removed from the codebase. threads->exit; }; - $params{collect_cb} ||= sub {}; @_ = (); my @my_threads = map spawn_thread($thread_cb), 1..$params{threads}; - foreach my $th (@my_threads) { - $params{collect_cb}->($th->join); - } + + # We use a semaphore instead of $th->join because joined threads are + # not listed by threads->list or threads->object anymore, thus can't + # be signalled. + $parallel_sema->down; + $_->detach for @my_threads; } else { $params{no_threads_cb}->(); } @@ -202,40 +214,43 @@ sub thread_cleanup { } sub get_running_threads { - return grep defined($_), map threads->object($_), @threads; + return grep defined($_), map threads->object($_), @_; } sub kill_all_threads { - # detach any running thread created in the current one - my @killed = (); - foreach my $thread (get_running_threads()) { - Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid; - $thread->kill('KILL'); - push @killed, $thread; + # if we're the main thread, we send SIGKILL to all the running threads + if (threads->tid == 0) { + lock @threads; + foreach my $thread (get_running_threads(@threads)) { + Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid; + $thread->kill('KILL'); + } + + # unlock semaphore before we block on wait + # otherwise we'd get a deadlock if threads were paused + resume_threads(); } - # unlock semaphore before we block on wait - # otherwise we'd get a deadlock if threads were paused - resume_threads(); - foreach my $thread (@killed) { - Slic3r::debugf " Threads %d waiting for %d...\n", threads->tid, $thread->tid; + # in any thread we wait for our children + foreach my $thread (get_running_threads(@my_threads)) { + Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid; $thread->join; # block until threads are killed Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid; } - @threads = (); + @my_threads = (); } sub pause_threads { return if $paused; $paused = 1; - $sema->down; - $_->kill('STOP') for get_running_threads(); + $pause_sema->down; + $_->kill('STOP') for get_running_threads(@threads); } sub resume_threads { return unless $paused; $paused = 0; - $sema->up; + $pause_sema->up; } sub encode_path { diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 4a5ec01fe..166f18792 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -426,7 +426,6 @@ sub make_perimeters { $self->get_layer($i)->make_perimeters; } }, - collect_cb => sub {}, no_threads_cb => sub { $_->make_perimeters for @{$self->layers}; }, @@ -506,7 +505,6 @@ sub infill { $layerm->fills->append($_) for $self->fill_maker->make_fill($layerm); } }, - collect_cb => sub {}, no_threads_cb => sub { foreach my $layerm (map @{$_->regions}, @{$self->layers}) { $layerm->fills->clear; From b0aa1260e2396023c6039ceb2ff405ec4b9853ab Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Dec 2014 15:40:42 +0100 Subject: [PATCH 035/107] Bugfix: wxComboBox left blank after menu item selection on MSW due to an undocumented wxWidgets issue. #2361 --- lib/Slic3r/GUI/OptionsGroup/Field.pm | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/OptionsGroup/Field.pm b/lib/Slic3r/GUI/OptionsGroup/Field.pm index 76cabb515..fcf97eb3f 100644 --- a/lib/Slic3r/GUI/OptionsGroup/Field.pm +++ b/lib/Slic3r/GUI/OptionsGroup/Field.pm @@ -231,7 +231,7 @@ use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow'; use List::Util qw(first); -use Wx qw(:misc :combobox); +use Wx qw(wxTheApp :misc :combobox); use Wx::Event qw(EVT_COMBOBOX EVT_TEXT); sub BUILD { @@ -258,7 +258,17 @@ sub BUILD { $label = $value; } - $field->SetValue($label); + # The MSW implementation of wxComboBox will leave the field blank if we call + # SetValue() in the EVT_COMBOBOX event handler, so we postpone the call. + wxTheApp->CallAfter(sub { + my $dce = $self->disable_change_event; + $self->disable_change_event(1); + + # ChangeValue() is not exported in wxPerl + $field->SetValue($label); + + $self->disable_change_event($dce); + }); $self->disable_change_event($disable_change_event); $self->_on_change($self->option->opt_id); From d1f58cbed5d66d71d8de23f5efe959a594932ff8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Dec 2014 20:41:03 +0100 Subject: [PATCH 036/107] Objects can be selected in 3D preview now. Double click and right click work as well --- lib/Slic3r/GUI/Plater.pm | 62 +++++++++++------- lib/Slic3r/GUI/Plater/2D.pm | 4 +- lib/Slic3r/GUI/Plater/3D.pm | 51 +++++++++++++-- lib/Slic3r/GUI/PreviewCanvas.pm | 110 ++++++++++++++++++++++++++++---- utils/view-mesh.pl | 1 + xs/xsp/Model.xsp | 2 + xs/xsp/Point.xsp | 4 ++ 7 files changed, 190 insertions(+), 44 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3b0d037a2..82030388c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -68,27 +68,25 @@ sub new { # Initialize preview notebook $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM); - # Initialize 2D preview canvas - $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); - $self->{preview_notebook}->AddPage($self->{canvas}, '2D'); - $self->{canvas}->on_select_object(sub { + # Initialize handlers for canvases + my $on_select_object = sub { my ($obj_idx) = @_; $self->select_object($obj_idx); - }); - $self->{canvas}->on_double_click(sub { + }; + my $on_double_click = sub { $self->object_settings_dialog if $self->selected_object; - }); - $self->{canvas}->on_right_click(sub { - my ($click_pos) = @_; + }; + my $on_right_click = sub { + my ($canvas, $click_pos) = @_; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; my $menu = $self->object_menu; - $self->{canvas}->PopupMenu($menu, $click_pos); + $canvas->PopupMenu($menu, $click_pos); $menu->Destroy; - }); - $self->{canvas}->on_instance_moved(sub { + }; + my $on_instance_moved = sub { my ($obj_idx, $instance_idx) = @_; $self->update; @@ -100,12 +98,24 @@ sub new { } else { $self->resume_background_process; } - }); + }; + + # Initialize 2D preview canvas + $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); + $self->{preview_notebook}->AddPage($self->{canvas}, '2D'); + $self->{canvas}->on_select_object($on_select_object); + $self->{canvas}->on_double_click($on_double_click); + $self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); }); + $self->{canvas}->on_instance_moved($on_instance_moved); # Initialize 3D preview and toolpaths preview if ($Slic3r::GUI::have_OpenGL) { $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config}); $self->{preview_notebook}->AddPage($self->{canvas3D}, '3D'); + $self->{canvas3D}->set_on_select_object($on_select_object); + $self->{canvas3D}->set_on_double_click($on_double_click); + $self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); }); + $self->{canvas3D}->set_on_instance_moved($on_instance_moved); $self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print}); $self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Preview'); @@ -233,7 +243,7 @@ sub new { } $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) - for $self, $self->{canvas}, $self->{list}; + for grep defined($_), $self, $self->{canvas}, $self->{canvas3D}, $self->{list}; EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub { my ($self, $event) = @_; @@ -655,7 +665,7 @@ sub rotate { $self->selection_changed; # refresh info (size etc.) $self->update; - $self->{canvas}->Refresh; + $self->refresh_canvases; } sub flip { @@ -684,7 +694,7 @@ sub flip { $self->selection_changed; # refresh info (size etc.) $self->update; - $self->{canvas}->Refresh; + $self->refresh_canvases; } sub changescale { @@ -739,7 +749,7 @@ sub changescale { $self->selection_changed(1); # refresh info (size, volume etc.) $self->update; - $self->{canvas}->Refresh; + $self->refresh_canvases; } sub arrange { @@ -765,7 +775,7 @@ sub arrange { } else { $self->resume_background_process; } - $self->{canvas}->Refresh; + $self->refresh_canvases; } sub split_object { @@ -1146,7 +1156,7 @@ sub on_thumbnail_made { $self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx); $self->update; - $self->{canvas}->Refresh; + $self->refresh_canvases; } # this method gets called whenever print center is changed or the objects' bounding box changes @@ -1158,8 +1168,7 @@ sub update { $self->{model}->center_instances_around_point($self->bed_centerf); } - $self->{canvas}->Refresh; - $self->{canvas3D}->update if $self->{canvas3D}; + $self->refresh_canvases; } sub on_extruders_change { @@ -1207,7 +1216,7 @@ sub list_item_deselected { if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); - $self->{canvas}->Refresh; + $self->refresh_canvases; } } @@ -1217,7 +1226,7 @@ sub list_item_selected { my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); - $self->{canvas}->Refresh; + $self->refresh_canvases; } sub list_item_activated { @@ -1385,6 +1394,13 @@ sub selected_object { return ($obj_idx, $self->{objects}[$obj_idx]), } +sub refresh_canvases { + my ($self) = @_; + + $self->{canvas}->Refresh; + $self->{canvas3D}->update if $self->{canvas3D}; +} + sub validate_config { my $self = shift; diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index e845cbe9a..c269420eb 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -210,14 +210,14 @@ sub mouse_event { } } $self->Refresh; - } elsif ($event->ButtonUp(&Wx::wxMOUSE_BTN_LEFT)) { + } elsif ($event->LeftUp) { $self->{on_instance_moved}->(@{ $self->{drag_object} }) if $self->{drag_object}; $self->Refresh; $self->{drag_start_pos} = undef; $self->{drag_object} = undef; $self->SetCursor(wxSTANDARD_CURSOR); - } elsif ($event->ButtonDClick) { + } elsif ($event->LeftDClick) { $self->{on_double_click}->(); } elsif ($event->Dragging) { return if !$self->{drag_start_pos}; # concurrency problems diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 8f6960b7d..c7d6934a3 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -15,6 +15,7 @@ sub new { my ($parent, $objects, $model, $config) = @_; my $self = $class->SUPER::new($parent); + $self->enable_picking(1); $self->{objects} = $objects; $self->{model} = $model; @@ -24,23 +25,63 @@ sub new { $self->{on_right_click} = sub {}; $self->{on_instance_moved} = sub {}; - - return $self; } +sub set_on_select_object { + my ($self, $cb) = @_; + $self->on_select_object(sub { + my ($volume_idx) = @_; + + return $cb->(undef) if $volume_idx == -1; + my $obj_idx = $self->{_volumes_inv}{$volume_idx}; + return $cb->($obj_idx); + }); +} + +sub set_on_double_click { + my ($self, $cb) = @_; + $self->on_double_click($cb); +} + +sub set_on_right_click { + my ($self, $cb) = @_; + $self->on_right_click($cb); +} + +sub set_on_instance_moved { + my ($self, $cb) = @_; + $self->on_instance_moved(sub { + my ($volume_idx, $instance_idx) = @_; + + my $obj_idx = $self->{_volumes_inv}{$volume_idx}; + return $cb->($obj_idx, $instance_idx); + }); +} + sub update { my ($self) = @_; + $self->{_volumes} = {}; # obj_idx => [ volume_idx, volume_idx ] + $self->{_volumes_inv} = {}; # volume_idx => obj_idx $self->reset_objects; return if $self->{model}->objects_count == 0; $self->set_bounding_box($self->{model}->bounding_box); $self->set_bed_shape($self->{config}->bed_shape); - foreach my $model_object (@{$self->{model}->objects}) { - $self->load_object($model_object, 1); + foreach my $obj_idx (0..$#{$self->{model}->objects}) { + my $model_object = $self->{model}->get_object($obj_idx); + my @volume_idxs = $self->load_object($model_object, 1); + + # store mapping between canvas volumes and model objects + $self->{_volumes}{$obj_idx} = [ @volume_idxs ]; + $self->{_volumes_inv}{$_} = $obj_idx for @volume_idxs; + + if ($self->{objects}[$obj_idx]{selected}) { + $self->select_volume($_) for @volume_idxs; + } } } -1; +1; \ No newline at end of file diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 369a1058b..ebe282079 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -14,6 +14,11 @@ use Wx::GLCanvas qw(:all); __PACKAGE__->mk_accessors( qw(quat dirty init mview_init object_bounding_box + enable_picking + on_select_object + on_double_click + on_right_click + on_instance_moved volumes initpos sphi stheta cutting_plane_z @@ -21,12 +26,14 @@ __PACKAGE__->mk_accessors( qw(quat dirty init mview_init bed_triangles bed_grid_lines origin + _mouse_gl_pos ) ); use constant TRACKBALLSIZE => 0.8; use constant TURNTABLE_MODE => 1; use constant GROUND_Z => 0.02; use constant SELECTED_COLOR => [0,1,0,1]; +use constant HOVER_COLOR => [0.8,0.8,0,1]; use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ]; # make OpenGL::Array thread-safe @@ -74,13 +81,49 @@ sub new { }); EVT_MOUSE_EVENTS($self, sub { my ($self, $e) = @_; - - if ($e->Dragging() && $e->LeftIsDown()) { - $self->handle_rotation($e); - } elsif ($e->Dragging() && $e->RightIsDown()) { - $self->handle_translation($e); - } elsif ($e->LeftUp() || $e->RightUp()) { + + my $pos = Slic3r::Pointf->new($e->GetPositionXY); + if ($e->LeftDClick) { + $self->on_double_click->() + if $self->on_double_click; + } elsif ($e->LeftDown || $e->RightDown) { + my $volume_idx = first { $self->volumes->[$_]->{hover} } 0..$#{$self->volumes}; + $volume_idx //= -1; + + # select volume in this 3D canvas + $self->select_volume($volume_idx); + $self->Refresh; + + # propagate event through callback + $self->on_select_object->($volume_idx) + if $self->on_select_object; + + if ($e->RightDown && $volume_idx != -1) { + # if right clicking on volume, propagate event through callback + $self->on_right_click->($e->GetPosition) + if $self->on_right_click; + } + } elsif ($e->Dragging) { + my $volume_idx = first { $self->volumes->[$_]->{hover} } 0..$#{$self->volumes}; + $volume_idx //= -1; + + if ($e->LeftIsDown && $volume_idx == -1) { + # if dragging over blank area with left button, rotate + $self->handle_rotation($e); + } elsif ($e->RightIsDown && $volume_idx == -1) { + # if dragging over blank area with right button, translate + $self->handle_translation($e); + } elsif ($e->LeftIsDown && $volume_idx != -1) { + # if dragging volume, move it + # TODO + } + } elsif ($e->LeftUp || $e->RightUp) { $self->initpos(undef); + } elsif ($e->Moving) { + my $glpos = $pos->clone; + $glpos->set_y($self->GetSize->GetHeight - $glpos->y); #)) + $self->_mouse_gl_pos($glpos); + $self->Refresh; } else { $e->Skip(); } @@ -163,9 +206,11 @@ sub load_object { # sort volumes: non-modifiers first my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes}; + my @volumes_idx = (); foreach my $volume (@volumes) { - my @instances = $all_instances ? @{$object->instances} : $object->instances->[0]; - foreach my $instance (@instances) { + my @instance_idxs = $all_instances ? (0..$#{$object->instances}) : (0); + foreach my $instance_idx (@instance_idxs) { + my $instance = $object->instances->[$instance_idx]; my $mesh = $volume->mesh->clone; $instance->transform_mesh($mesh); @@ -179,10 +224,12 @@ sub load_object { my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ]; push @$color, $volume->modifier ? 0.5 : 1; push @{$self->volumes}, my $v = { - mesh => $mesh, - color => $color, - z_min => $z_min, + instance_idx => $instance_idx, + mesh => $mesh, + color => $color, + z_min => $z_min, }; + push @volumes_idx, $#{$self->volumes}; { my $vertices = $mesh->vertices; @@ -196,6 +243,16 @@ sub load_object { } } } + + return @volumes_idx; +} + +sub select_volume { + my ($self, $volume_idx) = @_; + + $_->{selected} = 0 for @{$self->volumes}; + $self->volumes->[$volume_idx]->{selected} = 1 + if $volume_idx != -1; } sub SetCuttingPlane { @@ -522,6 +579,23 @@ sub Render { my $center = $bb->center; glTranslatef(-$center->x, -$center->y, 0); #,, + if ($self->enable_picking) { + glDisable(GL_LIGHTING); + $self->draw_mesh(1); + glFlush(); + glFinish(); + + if (my $glpos = $self->_mouse_gl_pos) { + my $col = [ glReadPixels_p($glpos->x, $glpos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; + my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; + $_->{hover} = 0 for @{$self->volumes}; + if ($volume_idx <= $#{$self->volumes}) { + $self->volumes->[$volume_idx]{hover} = 1; + } + } + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_LIGHTING); + } # draw objects $self->draw_mesh; @@ -604,22 +678,30 @@ sub Render { } sub draw_mesh { - my $self = shift; + my ($self, $fakecolor) = @_; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); - foreach my $volume (@{$self->volumes}) { + foreach my $volume_idx (0..$#{$self->volumes}) { + my $volume = $self->volumes->[$volume_idx]; glTranslatef(0, 0, -$volume->{z_min}); glVertexPointer_p(3, $volume->{verts}); glCullFace(GL_BACK); glNormalPointer_p($volume->{norms}); - if ($volume->{selected}) { + if ($fakecolor) { + my $r = ($volume_idx & 0x000000FF) >> 0; + my $g = ($volume_idx & 0x0000FF00) >> 8; + my $b = ($volume_idx & 0x00FF0000) >> 16; + glColor4f($r/255.0, $g/255.0, $b/255.0, 1); + } elsif ($volume->{selected}) { glColor4f(@{ &SELECTED_COLOR }); + } elsif ($volume->{hover}) { + glColor4f(@{ &HOVER_COLOR }); } else { glColor4f(@{ $volume->{color} }); } diff --git a/utils/view-mesh.pl b/utils/view-mesh.pl index a696d7c85..61d6748f6 100644 --- a/utils/view-mesh.pl +++ b/utils/view-mesh.pl @@ -32,6 +32,7 @@ my %opt = (); $model->add_default_instances; my $app = Slic3r::ViewMesh->new; + $app->{canvas}->enable_picking(1); $app->{canvas}->load_object($model->objects->[0]); $app->{canvas}->set_bounding_box($model->objects->[0]->bounding_box); $app->{canvas}->set_auto_bed_shape; diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 8d795f7d6..2252d357d 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -20,6 +20,8 @@ void clear_objects(); size_t objects_count() %code%{ RETVAL = THIS->objects.size(); %}; + Ref get_object(int idx) + %code%{ RETVAL = THIS->objects.at(idx); %}; Ref get_material(t_model_material_id material_id) %code%{ diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index f058ead97..e02638a13 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -93,6 +93,10 @@ Point::coincides_with(point_sv) %code{% RETVAL = THIS->x; %}; double y() %code{% RETVAL = THIS->y; %}; + void set_x(double val) + %code{% THIS->x = val; %}; + void set_y(double val) + %code{% THIS->y = val; %}; void translate(double x, double y); void scale(double factor); void rotate(double angle, Pointf* center) From 16945dad7061d11a69988c20c32cdd61aef4af59 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Dec 2014 20:47:59 +0100 Subject: [PATCH 037/107] :lipstick: --- lib/Slic3r/GUI/PreviewCanvas.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index ebe282079..ee2cf302e 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -611,7 +611,7 @@ sub Render { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableClientState(GL_VERTEX_ARRAY); - glColor4f(0.5, 0.5, 0.5, 0.3); + glColor4f(0.6, 0.7, 0.5, 0.3); glNormal3d(0,0,1); glVertexPointer_p(3, $self->bed_triangles); glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3); @@ -622,7 +622,7 @@ sub Render { # draw grid glTranslatef(0, 0, 0.02); glLineWidth(3); - glColor3f(1.0, 1.0, 1.0); + glColor3f(0.95, 0.95, 0.95); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer_p(3, $self->bed_grid_lines); glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3); From ac2b6de62bd38e6f9dd87f26f2a0394d7b6b41c7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Dec 2014 20:56:22 +0100 Subject: [PATCH 038/107] Minor addition to OpenGL code to prevent darker models --- lib/Slic3r/GUI/PreviewCanvas.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index ee2cf302e..25b5de93c 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -594,6 +594,8 @@ sub Render { } } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glFlush(); + glFinish(); glEnable(GL_LIGHTING); } # draw objects From 2f2ae7552920fcfbec348b7e328c711569a7b0f5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 13 Dec 2014 22:18:43 +0100 Subject: [PATCH 039/107] Some incomplete work for moving objects in 3D plater --- lib/Slic3r/GUI/PreviewCanvas.pm | 64 ++++++++++++++++++++++----------- utils/view-mesh.pl | 2 ++ xs/src/libslic3r/Point.cpp | 27 ++++++++++++++ xs/src/libslic3r/Point.hpp | 4 +++ xs/xsp/Point.xsp | 6 ++++ 5 files changed, 83 insertions(+), 20 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 25b5de93c..0e0d22c8f 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -15,6 +15,7 @@ use Wx::GLCanvas qw(:all); __PACKAGE__->mk_accessors( qw(quat dirty init mview_init object_bounding_box enable_picking + enable_moving on_select_object on_double_click on_right_click @@ -27,6 +28,8 @@ __PACKAGE__->mk_accessors( qw(quat dirty init mview_init bed_grid_lines origin _mouse_gl_pos + _drag_volume_idx + _drag_start_pos ) ); use constant TRACKBALLSIZE => 0.8; @@ -74,8 +77,8 @@ sub new { my $zoom = ($e->GetWheelRotation() / $e->GetWheelDelta() / 10); $zoom = $zoom > 0 ? (1.0 + $zoom) : 1 / (1.0 - $zoom); - my @pos3d = $self->mouse_to_3d($e->GetX(), $e->GetY()); - $self->ZoomTo($zoom, $pos3d[0], $pos3d[1]); + my $pos3d = $self->mouse_to_3d($e->GetX(), $e->GetY()); + $self->ZoomTo($zoom, $pos3d->x, $pos3d->y); #)) $self->Refresh; }); @@ -98,11 +101,28 @@ sub new { $self->on_select_object->($volume_idx) if $self->on_select_object; - if ($e->RightDown && $volume_idx != -1) { - # if right clicking on volume, propagate event through callback - $self->on_right_click->($e->GetPosition) - if $self->on_right_click; + if ($volume_idx != -1) { + if ($e->LeftDown && $self->enable_moving) { + $self->_drag_volume_idx($volume_idx); + $self->_drag_start_pos($self->mouse_to_3d(@$pos)); + } elsif ($e->RightDown) { + # if right clicking on volume, propagate event through callback + $self->on_right_click->($e->GetPosition) + if $self->on_right_click; + } } + } elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) { + # get volume being dragged + my $volume = $self->volumes->[$self->_drag_volume_idx]; + + # get new position and calculate the move vector + my $cur_pos = $self->mouse_to_3d(@$pos); + my $vector = $self->_drag_start_pos->vector_to($cur_pos); + + # apply new temporary volume origin and ignore Z + $volume->{origin}->set_x(-$vector->x); + $volume->{origin}->set_y(-$vector->y); #)) + $self->Refresh; } elsif ($e->Dragging) { my $volume_idx = first { $self->volumes->[$_]->{hover} } 0..$#{$self->volumes}; $volume_idx //= -1; @@ -113,12 +133,16 @@ sub new { } elsif ($e->RightIsDown && $volume_idx == -1) { # if dragging over blank area with right button, translate $self->handle_translation($e); - } elsif ($e->LeftIsDown && $volume_idx != -1) { - # if dragging volume, move it - # TODO } } elsif ($e->LeftUp || $e->RightUp) { $self->initpos(undef); + + if ($self->on_instance_moved && defined $self->_drag_volume_idx) { + my $volume = $self->volumes->[$self->_drag_volume_idx]; + $self->on_instance_moved($self->_drag_volume_idx, $volume->{instance_idx}); + } + $self->_drag_volume_idx(undef); + $self->_drag_start_pos(undef); } elsif ($e->Moving) { my $glpos = $pos->clone; $glpos->set_y($self->GetSize->GetHeight - $glpos->y); #)) @@ -227,7 +251,7 @@ sub load_object { instance_idx => $instance_idx, mesh => $mesh, color => $color, - z_min => $z_min, + origin => Slic3r::Pointf3->new(0,0,$z_min), }; push @volumes_idx, $#{$self->volumes}; @@ -264,7 +288,7 @@ sub SetCuttingPlane { my @verts = (); foreach my $volume (@{$self->volumes}) { foreach my $volume (@{$self->volumes}) { - my $expolygons = $volume->{mesh}->slice([ $z + $volume->{z_min} ])->[0]; + my $expolygons = $volume->{mesh}->slice([ $z + $volume->{origin}->z ])->[0]; $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1); foreach my $line (map @{$_->lines}, map @$_, @$expolygons) { @@ -418,9 +442,9 @@ sub handle_translation { } else { my $new = $e->GetPosition(); my $orig = $self->initpos; - my @orig3d = $self->mouse_to_3d($orig->x, $orig->y); #)() - my @new3d = $self->mouse_to_3d($new->x, $new->y); #)() - glTranslatef($new3d[0] - $orig3d[0], $new3d[1] - $orig3d[1], 0); + my $orig3d = $self->mouse_to_3d($orig->x, $orig->y); #)() + my $new3d = $self->mouse_to_3d($new->x, $new->y); #)() + glTranslatef($new3d->x - $orig3d->x, $new3d->y - $orig3d->y, 0); #-- $self->initpos($new); $self->Refresh; } @@ -434,7 +458,7 @@ sub mouse_to_3d { my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items my @projected = gluUnProject_p($x, $viewport[3] - $y, 1.0, @mview, @proj, @viewport); - return @projected; + return Slic3r::Pointf3->new(@projected); } sub ZoomTo { @@ -581,7 +605,7 @@ sub Render { if ($self->enable_picking) { glDisable(GL_LIGHTING); - $self->draw_mesh(1); + $self->draw_volumes(1); glFlush(); glFinish(); @@ -599,7 +623,7 @@ sub Render { glEnable(GL_LIGHTING); } # draw objects - $self->draw_mesh; + $self->draw_volumes; # draw ground and axes glDisable(GL_LIGHTING); @@ -679,7 +703,7 @@ sub Render { $self->SwapBuffers(); } -sub draw_mesh { +sub draw_volumes { my ($self, $fakecolor) = @_; glEnable(GL_BLEND); @@ -689,7 +713,7 @@ sub draw_mesh { foreach my $volume_idx (0..$#{$self->volumes}) { my $volume = $self->volumes->[$volume_idx]; - glTranslatef(0, 0, -$volume->{z_min}); + glTranslatef(@{$volume->{origin}->negative}); glVertexPointer_p(3, $volume->{verts}); @@ -709,7 +733,7 @@ sub draw_mesh { } glDrawArrays(GL_TRIANGLES, 0, $volume->{verts}->elements / 3); - glTranslatef(0, 0, +$volume->{z_min}); + glTranslatef(@{$volume->{origin}}); } glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_BLEND); diff --git a/utils/view-mesh.pl b/utils/view-mesh.pl index 61d6748f6..58f641a92 100644 --- a/utils/view-mesh.pl +++ b/utils/view-mesh.pl @@ -20,6 +20,7 @@ my %opt = (); my %options = ( 'help' => sub { usage() }, 'cut=f' => \$opt{cut}, + 'enable-moving' => \$opt{enable_moving}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); @@ -33,6 +34,7 @@ my %opt = (); my $app = Slic3r::ViewMesh->new; $app->{canvas}->enable_picking(1); + $app->{canvas}->enable_moving($opt{enable_moving}); $app->{canvas}->load_object($model->objects->[0]); $app->{canvas}->set_bounding_box($model->objects->[0]->bounding_box); $app->{canvas}->set_auto_bed_shape; diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 5f3f1da01..63fe63cfb 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -218,6 +218,12 @@ Point::negative() const return Point(-this->x, -this->y); } +Vector +Point::vector_to(const Point &point) const +{ + return Vector(point.x - this->x, point.y - this->y); +} + Point operator+(const Point& point1, const Point& point2) { @@ -353,6 +359,27 @@ Pointf3::translate(double x, double y, double z) this->z += z; } +double +Pointf3::distance_to(const Pointf3 &point) const +{ + double dx = ((double)point.x - this->x); + double dy = ((double)point.y - this->y); + double dz = ((double)point.z - this->z); + return sqrt(dx*dx + dy*dy + dz*dz); +} + +Pointf3 +Pointf3::negative() const +{ + return Pointf3(-this->x, -this->y, -this->z); +} + +Vectorf3 +Pointf3::vector_to(const Pointf3 &point) const +{ + return Vectorf3(point.x - this->x, point.y - this->y, point.z - this->z); +} + #ifdef SLIC3RXS REGISTER_CLASS(Pointf3, "Pointf3"); #endif diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 271c9584e..6f4d317a4 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -53,6 +53,7 @@ class Point Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; Point negative() const; + Vector vector_to(const Point &point) const; #ifdef SLIC3RXS void from_SV(SV* point_sv); @@ -96,6 +97,9 @@ class Pointf3 : public Pointf void scale(double factor); void translate(const Vectorf3 &vector); void translate(double x, double y, double z); + double distance_to(const Pointf3 &point) const; + Pointf3 negative() const; + Vectorf3 vector_to(const Pointf3 &point) const; }; } diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index e02638a13..dbf2f9bac 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -121,4 +121,10 @@ Point::coincides_with(point_sv) void set_z(double val) %code{% THIS->z = val; %}; void translate(double x, double y, double z); + double distance_to(Pointf3* point) + %code{% RETVAL = THIS->distance_to(*point); %}; + Clone negative() + %code{% RETVAL = THIS->negative(); %}; + Clone vector_to(Pointf3* point) + %code{% RETVAL = THIS->vector_to(*point); %}; }; From 74b3be3c06a073e38bc51df2424ca258b021f945 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 14 Dec 2014 00:54:35 +0100 Subject: [PATCH 040/107] Refactoring in 3D interactive code. Highlight all copies of an object when hovering --- lib/Slic3r/GUI/Plater/3D.pm | 32 +++++++++----- lib/Slic3r/GUI/PreviewCanvas.pm | 76 ++++++++++++++++++++------------- 2 files changed, 68 insertions(+), 40 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index c7d6934a3..ed2864c65 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -21,22 +21,32 @@ sub new { $self->{model} = $model; $self->{config} = $config; $self->{on_select_object} = sub {}; - $self->{on_double_click} = sub {}; - $self->{on_right_click} = sub {}; - $self->{on_instance_moved} = sub {}; + + $self->on_select(sub { + my ($volume_idx) = @_; + + my $obj_idx = undef; + if ($volume_idx != -1) { + $obj_idx = $self->{_volumes_inv}{$volume_idx}; + $self->volumes->[$_]->selected(1) for @{$self->{_volumes}{$obj_idx}}; + $self->Refresh; + } + $self->{on_select_object}->($obj_idx) + if $self->{on_select_object}; + }); + $self->on_hover(sub { + my ($volume_idx) = @_; + + my $obj_idx = $self->{_volumes_inv}{$volume_idx}; + $self->volumes->[$_]->hover(1) for @{$self->{_volumes}{$obj_idx}}; + }); return $self; } sub set_on_select_object { my ($self, $cb) = @_; - $self->on_select_object(sub { - my ($volume_idx) = @_; - - return $cb->(undef) if $volume_idx == -1; - my $obj_idx = $self->{_volumes_inv}{$volume_idx}; - return $cb->($obj_idx); - }); + $self->{on_select_object} = $cb; } sub set_on_double_click { @@ -78,7 +88,7 @@ sub update { $self->{_volumes}{$obj_idx} = [ @volume_idxs ]; $self->{_volumes_inv}{$_} = $obj_idx for @volume_idxs; - if ($self->{objects}[$obj_idx]{selected}) { + if ($self->{objects}[$obj_idx]->selected) { $self->select_volume($_) for @volume_idxs; } } diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 0e0d22c8f..b25ebaf66 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -16,7 +16,8 @@ __PACKAGE__->mk_accessors( qw(quat dirty init mview_init object_bounding_box enable_picking enable_moving - on_select_object + on_hover + on_select on_double_click on_right_click on_instance_moved @@ -27,7 +28,8 @@ __PACKAGE__->mk_accessors( qw(quat dirty init mview_init bed_triangles bed_grid_lines origin - _mouse_gl_pos + _mouse_pos + _hover_volume_idx _drag_volume_idx _drag_start_pos ) ); @@ -90,16 +92,17 @@ sub new { $self->on_double_click->() if $self->on_double_click; } elsif ($e->LeftDown || $e->RightDown) { - my $volume_idx = first { $self->volumes->[$_]->{hover} } 0..$#{$self->volumes}; + my $volume_idx = first { $self->volumes->[$_]->hover } 0..$#{$self->volumes}; $volume_idx //= -1; # select volume in this 3D canvas + $self->deselect_volumes; $self->select_volume($volume_idx); $self->Refresh; # propagate event through callback - $self->on_select_object->($volume_idx) - if $self->on_select_object; + $self->on_select->($volume_idx) + if $self->on_select; if ($volume_idx != -1) { if ($e->LeftDown && $self->enable_moving) { @@ -120,8 +123,8 @@ sub new { my $vector = $self->_drag_start_pos->vector_to($cur_pos); # apply new temporary volume origin and ignore Z - $volume->{origin}->set_x(-$vector->x); - $volume->{origin}->set_y(-$vector->y); #)) + $volume->origin->set_x(-$vector->x); + $volume->origin->set_y(-$vector->y); #)) $self->Refresh; } elsif ($e->Dragging) { my $volume_idx = first { $self->volumes->[$_]->{hover} } 0..$#{$self->volumes}; @@ -139,14 +142,12 @@ sub new { if ($self->on_instance_moved && defined $self->_drag_volume_idx) { my $volume = $self->volumes->[$self->_drag_volume_idx]; - $self->on_instance_moved($self->_drag_volume_idx, $volume->{instance_idx}); + $self->on_instance_moved($self->_drag_volume_idx, $volume->instance_idx); } $self->_drag_volume_idx(undef); $self->_drag_start_pos(undef); } elsif ($e->Moving) { - my $glpos = $pos->clone; - $glpos->set_y($self->GetSize->GetHeight - $glpos->y); #)) - $self->_mouse_gl_pos($glpos); + $self->_mouse_pos($pos); $self->Refresh; } else { $e->Skip(); @@ -247,23 +248,23 @@ sub load_object { my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ]; push @$color, $volume->modifier ? 0.5 : 1; - push @{$self->volumes}, my $v = { + push @{$self->volumes}, my $v = Slic3r::GUI::PreviewCanvas::Volume->new( instance_idx => $instance_idx, mesh => $mesh, color => $color, origin => Slic3r::Pointf3->new(0,0,$z_min), - }; + ); push @volumes_idx, $#{$self->volumes}; { my $vertices = $mesh->vertices; my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets}; - $v->{verts} = OpenGL::Array->new_list(GL_FLOAT, @verts); + $v->verts(OpenGL::Array->new_list(GL_FLOAT, @verts)); } { my @norms = map { @$_, @$_, @$_ } @{$mesh->normals}; - $v->{norms} = OpenGL::Array->new_list(GL_FLOAT, @norms); + $v->norms(OpenGL::Array->new_list(GL_FLOAT, @norms)); } } } @@ -271,11 +272,15 @@ sub load_object { return @volumes_idx; } +sub deselect_volumes { + my ($self) = @_; + $_->selected(0) for @{$self->volumes}; +} + sub select_volume { my ($self, $volume_idx) = @_; - $_->{selected} = 0 for @{$self->volumes}; - $self->volumes->[$volume_idx]->{selected} = 1 + $self->volumes->[$volume_idx]->selected(1) if $volume_idx != -1; } @@ -288,7 +293,7 @@ sub SetCuttingPlane { my @verts = (); foreach my $volume (@{$self->volumes}) { foreach my $volume (@{$self->volumes}) { - my $expolygons = $volume->{mesh}->slice([ $z + $volume->{origin}->z ])->[0]; + my $expolygons = $volume->mesh->slice([ $z + $volume->origin->z ])->[0]; $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1); foreach my $line (map @{$_->lines}, map @$_, @$expolygons) { @@ -609,12 +614,13 @@ sub Render { glFlush(); glFinish(); - if (my $glpos = $self->_mouse_gl_pos) { - my $col = [ glReadPixels_p($glpos->x, $glpos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; + if (my $pos = $self->_mouse_pos) { + my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; $_->{hover} = 0 for @{$self->volumes}; if ($volume_idx <= $#{$self->volumes}) { - $self->volumes->[$volume_idx]{hover} = 1; + $self->volumes->[$volume_idx]->hover(1); + $self->on_hover->($volume_idx) if $self->on_hover; } } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -713,27 +719,27 @@ sub draw_volumes { foreach my $volume_idx (0..$#{$self->volumes}) { my $volume = $self->volumes->[$volume_idx]; - glTranslatef(@{$volume->{origin}->negative}); + glTranslatef(@{$volume->origin->negative}); - glVertexPointer_p(3, $volume->{verts}); + glVertexPointer_p(3, $volume->verts); glCullFace(GL_BACK); - glNormalPointer_p($volume->{norms}); + glNormalPointer_p($volume->norms); if ($fakecolor) { my $r = ($volume_idx & 0x000000FF) >> 0; my $g = ($volume_idx & 0x0000FF00) >> 8; my $b = ($volume_idx & 0x00FF0000) >> 16; glColor4f($r/255.0, $g/255.0, $b/255.0, 1); - } elsif ($volume->{selected}) { + } elsif ($volume->selected) { glColor4f(@{ &SELECTED_COLOR }); - } elsif ($volume->{hover}) { + } elsif ($volume->hover) { glColor4f(@{ &HOVER_COLOR }); } else { - glColor4f(@{ $volume->{color} }); + glColor4f(@{ $volume->color }); } - glDrawArrays(GL_TRIANGLES, 0, $volume->{verts}->elements / 3); + glDrawArrays(GL_TRIANGLES, 0, $volume->verts->elements / 3); - glTranslatef(@{$volume->{origin}}); + glTranslatef(@{$volume->origin}); } glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_BLEND); @@ -747,4 +753,16 @@ sub draw_volumes { glDisableClientState(GL_VERTEX_ARRAY); } +package Slic3r::GUI::PreviewCanvas::Volume; +use Moo; + +has 'mesh' => (is => 'ro', required => 1); +has 'color' => (is => 'ro', required => 1); +has 'instance_idx' => (is => 'ro', default => sub { 0 }); +has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) }); +has 'verts' => (is => 'rw'); +has 'norms' => (is => 'rw'); +has 'selected' => (is => 'rw', default => sub { 0 }); +has 'hover' => (is => 'rw', default => sub { 0 }); + 1; From 9cd0a6333175e3e0748292217649fadf381da09a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 15 Dec 2014 01:28:11 +0100 Subject: [PATCH 041/107] Refactoring of PreviewCanvas --- lib/Slic3r/GUI/PreviewCanvas.pm | 317 +++++++++++++++++--------------- xs/src/libslic3r/Point.cpp | 12 ++ xs/src/libslic3r/Point.hpp | 3 + xs/xsp/Point.xsp | 4 + 4 files changed, 185 insertions(+), 151 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index b25ebaf66..0489ef663 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -12,7 +12,7 @@ use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan scal use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl); use Wx::GLCanvas qw(:all); -__PACKAGE__->mk_accessors( qw(quat dirty init mview_init +__PACKAGE__->mk_accessors( qw(_quat _dirty init mview_init object_bounding_box enable_picking enable_moving @@ -21,17 +21,18 @@ __PACKAGE__->mk_accessors( qw(quat dirty init mview_init on_double_click on_right_click on_instance_moved - volumes initpos - sphi stheta + volumes + _sphi _stheta cutting_plane_z cut_lines_vertices bed_triangles bed_grid_lines origin _mouse_pos - _hover_volume_idx _drag_volume_idx _drag_start_pos + _camera_target + _zoom ) ); use constant TRACKBALLSIZE => 0.8; @@ -55,9 +56,11 @@ sub new { my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", [WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0]); - $self->quat((0, 0, 0, 1)); - $self->sphi(45); - $self->stheta(-45); + $self->_quat((0, 0, 0, 1)); + $self->_stheta(45); + $self->_sphi(45); + $self->_zoom(1); + $self->_camera_target(Slic3r::Pointf->new(0,0)); $self->reset_objects; @@ -66,9 +69,9 @@ sub new { return if !$self->object_bounding_box; $self->Render($dc); }); - EVT_SIZE($self, sub { $self->dirty(1) }); + EVT_SIZE($self, sub { $self->_dirty(1) }); EVT_IDLE($self, sub { - return unless $self->dirty; + return unless $self->_dirty; return if !$self->IsShownOnScreen; return if !$self->object_bounding_box; $self->Resize( $self->GetSizeWH ); @@ -77,91 +80,138 @@ sub new { EVT_MOUSEWHEEL($self, sub { my ($self, $e) = @_; - my $zoom = ($e->GetWheelRotation() / $e->GetWheelDelta() / 10); - $zoom = $zoom > 0 ? (1.0 + $zoom) : 1 / (1.0 - $zoom); - my $pos3d = $self->mouse_to_3d($e->GetX(), $e->GetY()); - $self->ZoomTo($zoom, $pos3d->x, $pos3d->y); #)) + # Calculate the zoom delta and apply it to the current zoom factor + my $zoom = 1 - ($e->GetWheelRotation() / $e->GetWheelDelta() / 10); + $self->_zoom($self->_zoom * $zoom); + # In order to zoom around the mouse point we need to translate + # the camera target + my $size = Slic3r::Pointf->new($self->GetSizeWH); + my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- + $self->_camera_target->translate( + # ($pos - $size/2) represents the vector from the viewport center + # to the mouse point. By multiplying it by $zoom we get the new, + # transformed, length of such vector. + # Since we want that point to stay fixed, we move our camera target + # in the opposite direction by the delta of the length of such vector + # ($zoom - 1). We then scale everything by 1/$self->_zoom since + # $self->_camera_target is expressed in terms of model units. + -($pos->x - $size->x/2) * ($zoom - 1) / $self->_zoom, + -($pos->y - $size->y/2) * ($zoom - 1) / $self->_zoom, + ); + $self->_dirty(1); $self->Refresh; }); - EVT_MOUSE_EVENTS($self, sub { - my ($self, $e) = @_; + EVT_MOUSE_EVENTS($self, \&mouse_event); + + return $self; +} + +sub mouse_event { + my ($self, $e) = @_; + + my $pos = Slic3r::Pointf->new($e->GetPositionXY); + if ($e->LeftDClick) { + $self->on_double_click->() + if $self->on_double_click; + } elsif ($e->LeftDown || $e->RightDown) { + # If user pressed left or right button we first check whether this happened + # on a volume or not. + my $volume_idx = first { $self->volumes->[$_]->hover } 0..$#{$self->volumes}; + $volume_idx //= -1; - my $pos = Slic3r::Pointf->new($e->GetPositionXY); - if ($e->LeftDClick) { - $self->on_double_click->() - if $self->on_double_click; - } elsif ($e->LeftDown || $e->RightDown) { - my $volume_idx = first { $self->volumes->[$_]->hover } 0..$#{$self->volumes}; - $volume_idx //= -1; - - # select volume in this 3D canvas + # select volume in this 3D canvas + if ($self->enable_picking) { $self->deselect_volumes; $self->select_volume($volume_idx); $self->Refresh; - - # propagate event through callback - $self->on_select->($volume_idx) - if $self->on_select; - - if ($volume_idx != -1) { - if ($e->LeftDown && $self->enable_moving) { - $self->_drag_volume_idx($volume_idx); - $self->_drag_start_pos($self->mouse_to_3d(@$pos)); - } elsif ($e->RightDown) { - # if right clicking on volume, propagate event through callback - $self->on_right_click->($e->GetPosition) - if $self->on_right_click; - } - } - } elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) { - # get volume being dragged - my $volume = $self->volumes->[$self->_drag_volume_idx]; - - # get new position and calculate the move vector - my $cur_pos = $self->mouse_to_3d(@$pos); - my $vector = $self->_drag_start_pos->vector_to($cur_pos); - - # apply new temporary volume origin and ignore Z - $volume->origin->set_x(-$vector->x); - $volume->origin->set_y(-$vector->y); #)) - $self->Refresh; - } elsif ($e->Dragging) { - my $volume_idx = first { $self->volumes->[$_]->{hover} } 0..$#{$self->volumes}; - $volume_idx //= -1; - - if ($e->LeftIsDown && $volume_idx == -1) { - # if dragging over blank area with left button, rotate - $self->handle_rotation($e); - } elsif ($e->RightIsDown && $volume_idx == -1) { - # if dragging over blank area with right button, translate - $self->handle_translation($e); - } - } elsif ($e->LeftUp || $e->RightUp) { - $self->initpos(undef); - - if ($self->on_instance_moved && defined $self->_drag_volume_idx) { - my $volume = $self->volumes->[$self->_drag_volume_idx]; - $self->on_instance_moved($self->_drag_volume_idx, $volume->instance_idx); - } - $self->_drag_volume_idx(undef); - $self->_drag_start_pos(undef); - } elsif ($e->Moving) { - $self->_mouse_pos($pos); - $self->Refresh; - } else { - $e->Skip(); } - }); - - return $self; + + # propagate event through callback + $self->on_select->($volume_idx) + if $self->on_select; + + if ($volume_idx != -1) { + if ($e->LeftDown && $self->enable_moving) { + $self->_drag_volume_idx($volume_idx); + $self->_drag_start_pos($self->mouse_to_3d(@$pos)); + } elsif ($e->RightDown) { + # if right clicking on volume, propagate event through callback + $self->on_right_click->($e->GetPosition) + if $self->on_right_click; + } + } + } elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) { + # get volume being dragged + my $volume = $self->volumes->[$self->_drag_volume_idx]; + + # get new position and calculate the move vector + my $cur_pos = $self->mouse_to_3d(@$pos); + my $vector = $self->_drag_start_pos->vector_to($cur_pos); + + # apply new temporary volume origin and ignore Z + $volume->origin->set_x(-$vector->x); + $volume->origin->set_y(-$vector->y); #)) + $self->Refresh; + } elsif ($e->Dragging) { + my $volume_idx = first { $self->volumes->[$_]->hover } 0..$#{$self->volumes}; + $volume_idx //= -1; + + if ($e->LeftIsDown && $volume_idx == -1) { + # if dragging over blank area with left button, rotate + if (defined $self->_drag_start_pos) { + my $orig = $self->_drag_start_pos; + if (TURNTABLE_MODE) { + $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); + $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- + } else { + my $size = $self->GetClientSize; + my @quat = trackball( + $orig->x / ($size->width / 2) - 1, + 1 - $orig->y / ($size->height / 2), #/ + $pos->x / ($size->width / 2) - 1, + 1 - $pos->y / ($size->height / 2), #/ + ); + $self->_quat(mulquats($self->_quat, \@quat)); + } + $self->Refresh; + } + $self->_drag_start_pos($pos); + } elsif ($e->RightIsDown && $volume_idx == -1) { + # if dragging over blank area with right button, translate + if (defined $self->_drag_start_pos) { + my $orig = $self->_drag_start_pos; + glMatrixMode(GL_MODELVIEW); + $self->_camera_target->translate( + ($pos->x - $orig->x) / $self->_zoom, + ($orig->y - $pos->y) / $self->_zoom, #- (converts to upwards Y) + ); + $self->Refresh; + } + $self->_drag_start_pos($pos); + } + } elsif ($e->LeftUp || $e->RightUp) { + $self->_drag_start_pos(undef); + + if ($self->on_instance_moved && defined $self->_drag_volume_idx) { + my $volume = $self->volumes->[$self->_drag_volume_idx]; + $self->on_instance_moved($self->_drag_volume_idx, $volume->instance_idx); + } + $self->_drag_volume_idx(undef); + $self->_drag_start_pos(undef); + } elsif ($e->Moving) { + $self->_mouse_pos($pos); + $self->Refresh; + } else { + $e->Skip(); + } } sub reset_objects { my ($self) = @_; $self->volumes([]); - $self->dirty(1); + $self->_dirty(1); } # this method accepts a Slic3r::BoudingBox3f object @@ -169,7 +219,22 @@ sub set_bounding_box { my ($self, $bb) = @_; $self->object_bounding_box($bb); - $self->dirty(1); + $self->zoom_to_bounding_box; + $self->_dirty(1); +} + +sub zoom_to_bounding_box { + my ($self) = @_; + + # calculate the zoom factor needed to adjust viewport to + # bounding box + my $max_size = max(@{$self->object_bounding_box->size}) * sqrt(2); + my $min_viewport_size = min($self->GetSizeWH); + $self->_zoom($min_viewport_size / $max_size / 1.3); + + # center view around bounding box center + $self->_camera_target->set_x(0); + $self->_camera_target->set_y(0); } sub set_auto_bed_shape { @@ -414,75 +479,19 @@ sub mulquats { @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2]) } -sub handle_rotation { - my ($self, $e) = @_; - - if (not defined $self->initpos) { - $self->initpos($e->GetPosition()); - } else { - my $orig = $self->initpos; - my $new = $e->GetPosition(); - my $size = $self->GetClientSize(); - if (TURNTABLE_MODE) { - $self->sphi($self->sphi + ($new->x - $orig->x)*TRACKBALLSIZE); - $self->stheta($self->stheta + ($new->y - $orig->y)*TRACKBALLSIZE); #- - } else { - my @quat = trackball($orig->x / ($size->width / 2) - 1, - 1 - $orig->y / ($size->height / 2), #/ - $new->x / ($size->width / 2) - 1, - 1 - $new->y / ($size->height / 2), #/ - ); - $self->quat(mulquats($self->quat, \@quat)); - } - $self->initpos($new); - $self->Refresh; - } -} - -sub handle_translation { - my ($self, $e) = @_; - - if (not defined $self->initpos) { - $self->initpos($e->GetPosition()); - } else { - my $new = $e->GetPosition(); - my $orig = $self->initpos; - my $orig3d = $self->mouse_to_3d($orig->x, $orig->y); #)() - my $new3d = $self->mouse_to_3d($new->x, $new->y); #)() - glTranslatef($new3d->x - $orig3d->x, $new3d->y - $orig3d->y, 0); #-- - $self->initpos($new); - $self->Refresh; - } -} - sub mouse_to_3d { my ($self, $x, $y) = @_; my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items - - my @projected = gluUnProject_p($x, $viewport[3] - $y, 1.0, @mview, @proj, @viewport); + + $y = $viewport[3] - $y; + my $z = glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); + my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport); return Slic3r::Pointf3->new(@projected); } -sub ZoomTo { - my ($self, $factor, $tox, $toy) = @_; - - return if !$self->init; - glTranslatef($tox, $toy, 0); - glMatrixMode(GL_MODELVIEW); - $self->Zoom($factor); - glTranslatef(-$tox, -$toy, 0); -} - -sub Zoom { - my ($self, $factor) = @_; - - glMatrixMode(GL_MODELVIEW); - glScalef($factor, $factor, 1); -} - sub GetContext { my ($self) = @_; @@ -517,11 +526,14 @@ sub Resize { my ($self, $x, $y) = @_; return unless $self->GetContext; - $self->dirty(0); + $self->_dirty(0); $self->SetCurrent($self->GetContext); glViewport(0, 0, $x, $y); + $x /= $self->_zoom; + $y /= $self->_zoom; + glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho( @@ -594,15 +606,18 @@ sub Render { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); - glPushMatrix(); + glLoadIdentity(); my $bb = $self->object_bounding_box; my $object_size = $bb->size; - glTranslatef(0, 0, -max(@$object_size[0..1])); - my @rotmat = quat_to_rotmatrix($self->quat); - glMultMatrixd_p(@rotmat[0..15]); - glRotatef($self->stheta, 1, 0, 0); - glRotatef($self->sphi, 0, 0, 1); + glTranslatef(@{ $self->_camera_target }, 0); + if (TURNTABLE_MODE) { + glRotatef(-$self->_stheta, 1, 0, 0); # pitch + glRotatef($self->_sphi, 0, 0, 1); # yaw + } else { + my @rotmat = quat_to_rotmatrix($self->quat); + glMultMatrixd_p(@rotmat[0..15]); + } # center everything around 0,0 since that's where we're looking at (glOrtho()) my $center = $bb->center; @@ -617,7 +632,7 @@ sub Render { if (my $pos = $self->_mouse_pos) { my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; - $_->{hover} = 0 for @{$self->volumes}; + $_->hover(0) for @{$self->volumes}; if ($volume_idx <= $#{$self->volumes}) { $self->volumes->[$volume_idx]->hover(1); $self->on_hover->($volume_idx) if $self->on_hover; @@ -703,7 +718,6 @@ sub Render { glEnable(GL_LIGHTING); - glPopMatrix(); glFlush(); $self->SwapBuffers(); @@ -719,6 +733,7 @@ sub draw_volumes { foreach my $volume_idx (0..$#{$self->volumes}) { my $volume = $self->volumes->[$volume_idx]; + glPushMatrix(); glTranslatef(@{$volume->origin->negative}); glVertexPointer_p(3, $volume->verts); @@ -739,7 +754,7 @@ sub draw_volumes { } glDrawArrays(GL_TRIANGLES, 0, $volume->verts->elements / 3); - glTranslatef(@{$volume->origin}); + glPopMatrix(); } glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_BLEND); diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 63fe63cfb..4eee03eff 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -299,6 +299,18 @@ Pointf::rotate(double angle, const Pointf ¢er) this->y = center.y + cos(angle) * (cur_y - center.y) + sin(angle) * (cur_x - center.x); } +Pointf +Pointf::negative() const +{ + return Pointf(-this->x, -this->y); +} + +Vectorf +Pointf::vector_to(const Pointf &point) const +{ + return Vectorf(point.x - this->x, point.y - this->y); +} + #ifdef SLIC3RXS REGISTER_CLASS(Pointf, "Pointf"); diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 6f4d317a4..368fb1af8 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -14,6 +14,7 @@ class Point; class Pointf; class Pointf3; typedef Point Vector; +typedef Pointf Vectorf; typedef Pointf3 Vectorf3; typedef std::vector Points; typedef std::vector PointPtrs; @@ -81,6 +82,8 @@ class Pointf void scale(double factor); void translate(double x, double y); void rotate(double angle, const Pointf ¢er); + Pointf negative() const; + Vectorf vector_to(const Pointf &point) const; #ifdef SLIC3RXS bool from_SV(SV* point_sv); diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index dbf2f9bac..5be5aef00 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -101,6 +101,10 @@ Point::coincides_with(point_sv) void scale(double factor); void rotate(double angle, Pointf* center) %code{% THIS->rotate(angle, *center); %}; + Clone negative() + %code{% RETVAL = THIS->negative(); %}; + Clone vector_to(Pointf* point) + %code{% RETVAL = THIS->vector_to(*point); %}; }; %name{Slic3r::Pointf3} class Pointf3 { From a34cd24fa138a3269e168d1518cbe64255f3c15f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 15 Dec 2014 12:42:11 +0100 Subject: [PATCH 042/107] Overlay object slices in toolpaths preview --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 46 ++++++++++++++++++++++++++-- utils/view-toolpaths.pl | 2 ++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 311e4dea4..d766803bb 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -111,7 +111,7 @@ sub set_z { package Slic3r::GUI::Plater::2DToolpaths::Canvas; use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); -use OpenGL qw(:glconstants :glfunctions :glufunctions); +use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use base qw(Wx::GLCanvas Class::Accessor); use Wx::GLCanvas qw(:all); use List::Util qw(min first); @@ -212,12 +212,51 @@ sub Render { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); + my $tess = gluNewTess(); + gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_END, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT'); + my $skirt_drawn = 0; my $brim_drawn = 0; foreach my $layer (@{$self->layers}) { my $object = $layer->object; my $print_z = $layer->print_z; + # draw slice contour + { + glLineWidth(1); + foreach my $copy (@{ $object->_shifted_copies }) { + glPushMatrix(); + glTranslatef(@$copy, 0); + + foreach my $slice (@{$layer->slices}) { + glColor3f(0.95, 0.95, 0.95); + gluTessBeginPolygon($tess); + foreach my $polygon (@$slice) { + gluTessBeginContour($tess); + gluTessVertex_p($tess, @$_, 0) for @$polygon; + gluTessEndContour($tess); + } + gluTessEndPolygon($tess); + + glColor3f(0.9, 0.9, 0.9); + foreach my $polygon (@$slice) { + foreach my $line (@{$polygon->lines}) { + glBegin(GL_LINES); + glVertex2f(@{$line->a}); + glVertex2f(@{$line->b}); + glEnd(); + } + } + } + glPopMatrix(); + } + } + # draw brim if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) { $self->color([0, 0, 0]); @@ -253,6 +292,7 @@ sub Render { } } + gluDeleteTess($tess); glFlush(); $self->SwapBuffers; } @@ -282,13 +322,15 @@ sub _draw_path { if (defined $object) { foreach my $copy (@{ $object->_shifted_copies }) { + glPushMatrix(); + glTranslatef(@$copy, 0); foreach my $line (@{$path->polyline->lines}) { - $line->translate(@$copy); glBegin(GL_LINES); glVertex2f(@{$line->a}); glVertex2f(@{$line->b}); glEnd(); } + glPopMatrix(); } } else { foreach my $line (@{$path->polyline->lines}) { diff --git a/utils/view-toolpaths.pl b/utils/view-toolpaths.pl index 4b03bbbea..0e05a15a7 100755 --- a/utils/view-toolpaths.pl +++ b/utils/view-toolpaths.pl @@ -20,6 +20,7 @@ my %opt = (); my %options = ( 'help' => sub { usage() }, 'load=s' => \$opt{load}, + 'duplicate=i' => \$opt{duplicate}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); @@ -38,6 +39,7 @@ my %opt = (); # init print my $sprint = Slic3r::Print::Simple->new; + $sprint->duplicate($opt{duplicate} // 1); $sprint->apply_config($config); $sprint->set_model($model); $sprint->process; From a82f95e9034c5bf59c2c40e24a57a6ee30692601 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 15 Dec 2014 15:19:42 +0100 Subject: [PATCH 043/107] Some initial work for 3D slice rendering --- lib/Slic3r/GUI/PreviewCanvas.pm | 72 ++++++++++++++++++++++++++++++++- utils/view-toolpaths.pl | 23 ++++++++++- xs/src/libslic3r/Line.cpp | 6 +++ xs/src/libslic3r/Line.hpp | 1 + xs/xsp/Line.xsp | 1 + 5 files changed, 100 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 0489ef663..282f2cb50 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -4,7 +4,7 @@ use warnings; use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); # must load OpenGL *before* Wx::GLCanvas -use OpenGL qw(:glconstants :glfunctions :glufunctions); +use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use base qw(Wx::GLCanvas Class::Accessor); use Math::Trig qw(asin); use List::Util qw(reduce min max first); @@ -22,6 +22,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init mview_init on_right_click on_instance_moved volumes + print _sphi _stheta cutting_plane_z cut_lines_vertices @@ -564,6 +565,11 @@ sub InitGL { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + # Set antialiasing/multisampling + glDisable(GL_LINE_SMOOTH); + glDisable(GL_POLYGON_SMOOTH); + glEnable(GL_MULTISAMPLE); + # ambient lighting glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1); @@ -728,6 +734,70 @@ sub draw_volumes { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (defined($self->print) && !$fakecolor) { + my $tess = gluNewTess(); + gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_END, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT'); + + foreach my $object (@{$self->print->objects}) { + foreach my $layer (@{$object->layers}) { + my $gap = 0; + my $top_z = $layer->print_z; + my $bottom_z = $layer->print_z - $layer->height + $gap; + + foreach my $copy (@{ $object->_shifted_copies }) { + glPushMatrix(); + glTranslatef(map unscale($_), @$copy, 0); + + foreach my $slice (@{$layer->slices}) { + glColor3f(@{COLORS->[0]}); + gluTessBeginPolygon($tess); + glNormal3f(0,0,1); + foreach my $polygon (@$slice) { + gluTessBeginContour($tess); + gluTessVertex_p($tess, (map unscale($_), @$_), $layer->print_z) for @$polygon; + gluTessEndContour($tess); + } + gluTessEndPolygon($tess); + + foreach my $polygon (@$slice) { + foreach my $line (@{$polygon->lines}) { + if (0) { + glLineWidth(1); + glColor3f(0,0,0); + glBegin(GL_LINES); + glVertex3f((map unscale($_), @{$line->a}), $bottom_z); + glVertex3f((map unscale($_), @{$line->b}), $bottom_z); + glEnd(); + } + + glLineWidth(0); + glColor3f(@{COLORS->[0]}); + glBegin(GL_QUADS); + glNormal3f((map $_/$line->length, @{$line->normal}), 0); + glVertex3f((map unscale($_), @{$line->a}), $bottom_z); + glVertex3f((map unscale($_), @{$line->b}), $bottom_z); + glVertex3f((map unscale($_), @{$line->b}), $top_z); + glVertex3f((map unscale($_), @{$line->a}), $top_z); + glEnd(); + } + } + } + + glPopMatrix(); # copy + } + } + } + + gluDeleteTess($tess); + return; + } + glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); diff --git a/utils/view-toolpaths.pl b/utils/view-toolpaths.pl index 0e05a15a7..66ee226db 100755 --- a/utils/view-toolpaths.pl +++ b/utils/view-toolpaths.pl @@ -20,6 +20,7 @@ my %opt = (); my %options = ( 'help' => sub { usage() }, 'load=s' => \$opt{load}, + '3D' => \$opt{d3}, 'duplicate=i' => \$opt{duplicate}, ); GetOptions(%options) or usage(1); @@ -46,6 +47,7 @@ my %opt = (); # visualize toolpaths $Slic3r::ViewToolpaths::print = $sprint->_print; + $Slic3r::ViewToolpaths::d3 = $opt{d3}; my $app = Slic3r::ViewToolpaths->new; $app->MainLoop; } @@ -67,9 +69,10 @@ EOF package Slic3r::ViewToolpaths; use Wx qw(:sizer); -use base qw(Wx::App); +use base qw(Wx::App Class::Accessor); our $print; +our $d3; sub OnInit { my $self = shift; @@ -77,8 +80,24 @@ sub OnInit { my $frame = Wx::Frame->new(undef, -1, 'Toolpaths', [-1, -1], [500, 500]); my $panel = Wx::Panel->new($frame, -1); + my $canvas; + if ($d3) { + $canvas = Slic3r::GUI::PreviewCanvas->new($panel); + $canvas->print($print); + + #$canvas->set_bounding_box($print->bounding_box); + $canvas->set_bed_shape($print->config->bed_shape); + $canvas->set_bounding_box($print->objects->[0]->model_object->bounding_box); + + foreach my $object (@{$print->objects}) { + $canvas->load_object($object->model_object); + } + } else { + $canvas = Slic3r::GUI::Plater::2DToolpaths->new($panel, $print); + } + my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add(Slic3r::GUI::Plater::2DToolpaths->new($panel, $print), 1, wxEXPAND, 0); + $sizer->Add($canvas, 1, wxEXPAND, 0); $panel->SetSizer($sizer); $frame->Show(1); diff --git a/xs/src/libslic3r/Line.cpp b/xs/src/libslic3r/Line.cpp index d1b0310a0..42d85bb42 100644 --- a/xs/src/libslic3r/Line.cpp +++ b/xs/src/libslic3r/Line.cpp @@ -133,6 +133,12 @@ Line::vector() const return Vector(this->b.x - this->a.x, this->b.y - this->a.y); } +Vector +Line::normal() const +{ + return Vector((this->b.y - this->a.y), -(this->b.x - this->a.x)); +} + #ifdef SLIC3RXS REGISTER_CLASS(Line, "Line"); diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp index 3f86ed4a7..8abbbdbcd 100644 --- a/xs/src/libslic3r/Line.hpp +++ b/xs/src/libslic3r/Line.hpp @@ -34,6 +34,7 @@ class Line double orientation() const; double direction() const; Vector vector() const; + Vector normal() const; #ifdef SLIC3RXS void from_SV(SV* line_sv); diff --git a/xs/xsp/Line.xsp b/xs/xsp/Line.xsp index d0552315f..bf31e6e95 100644 --- a/xs/xsp/Line.xsp +++ b/xs/xsp/Line.xsp @@ -32,6 +32,7 @@ Clone point_at(double distance); Polyline* as_polyline() %code{% RETVAL = new Polyline(*THIS); %}; + Clone normal(); %{ Line* From fcfb3b98bc0c13157f557999d753d70de8374d3a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 16 Dec 2014 01:12:37 +0100 Subject: [PATCH 044/107] 3D object positioning --- lib/Slic3r.pm | 1 + lib/Slic3r/GUI/Plater.pm | 11 +- lib/Slic3r/GUI/Plater/2D.pm | 1 - lib/Slic3r/GUI/Plater/3D.pm | 33 +++- lib/Slic3r/GUI/Plater/ObjectCutDialog.pm | 2 +- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 2 +- lib/Slic3r/GUI/PreviewCanvas.pm | 201 +++++++++++++--------- utils/view-mesh.pl | 2 +- xs/lib/Slic3r/XS.pm | 1 + xs/src/libslic3r/Line.cpp | 14 ++ xs/src/libslic3r/Line.hpp | 17 ++ xs/xsp/BoundingBox.xsp | 1 + xs/xsp/Line.xsp | 14 ++ xs/xsp/Point.xsp | 1 + xs/xsp/my.map | 4 + xs/xsp/typemap.xspt | 3 + 16 files changed, 217 insertions(+), 91 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9019607db..48cd733f9 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -197,6 +197,7 @@ sub thread_cleanup { *Slic3r::Geometry::BoundingBoxf::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {}; *Slic3r::Line::DESTROY = sub {}; + *Slic3r::Linef3::DESTROY = sub {}; *Slic3r::Model::DESTROY = sub {}; *Slic3r::Model::Object::DESTROY = sub {}; *Slic3r::Point::DESTROY = sub {}; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 82030388c..86d070cd0 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -281,6 +281,10 @@ sub new { }); $self->{canvas}->update_bed_size; + if ($self->{canvas3D}) { + $self->{canvas3D}->update_bed_size; + $self->{canvas3D}->zoom_to_bed; + } $self->update; { @@ -513,6 +517,11 @@ sub objects_loaded { } $self->arrange unless $params{no_arrange}; $self->update; + + # zoom to objects + $self->{canvas3D}->zoom_to_volumes + if $self->{canvas3D}; + $self->{list}->Update; $self->{list}->Select($obj_idxs->[-1], 1); $self->object_list_changed; @@ -665,7 +674,6 @@ sub rotate { $self->selection_changed; # refresh info (size etc.) $self->update; - $self->refresh_canvases; } sub flip { @@ -1200,6 +1208,7 @@ sub on_config_change { $self->{config}->set($opt_key, $config->get($opt_key)); if ($opt_key eq 'bed_shape') { $self->{canvas}->update_bed_size; + $self->{canvas3D}->update_bed_size if $self->{canvas3D}; $self->update; } } diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index c269420eb..a43f0a061 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -213,7 +213,6 @@ sub mouse_event { } elsif ($event->LeftUp) { $self->{on_instance_moved}->(@{ $self->{drag_object} }) if $self->{drag_object}; - $self->Refresh; $self->{drag_start_pos} = undef; $self->{drag_object} = undef; $self->SetCursor(wxSTANDARD_CURSOR); diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index ed2864c65..a2b72033b 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -16,11 +16,13 @@ sub new { my $self = $class->SUPER::new($parent); $self->enable_picking(1); + $self->enable_moving(1); $self->{objects} = $objects; $self->{model} = $model; $self->{config} = $config; $self->{on_select_object} = sub {}; + $self->{on_instance_moved} = sub {}; $self->on_select(sub { my ($volume_idx) = @_; @@ -40,6 +42,21 @@ sub new { my $obj_idx = $self->{_volumes_inv}{$volume_idx}; $self->volumes->[$_]->hover(1) for @{$self->{_volumes}{$obj_idx}}; }); + $self->on_move(sub { + my ($volume_idx) = @_; + + my $volume = $self->volumes->[$volume_idx]; + my $obj_idx = $self->{_volumes_inv}{$volume_idx}; + my $model_object = $self->{model}->get_object($obj_idx); + $model_object + ->instances->[$volume->instance_idx] + ->offset + ->translate($volume->origin->x, $volume->origin->y); #)) + $model_object->invalidate_bounding_box; + + $self->{on_instance_moved}->($obj_idx, $volume->instance_idx) + if $self->{on_instance_moved}; + }); return $self; } @@ -61,12 +78,7 @@ sub set_on_right_click { sub set_on_instance_moved { my ($self, $cb) = @_; - $self->on_instance_moved(sub { - my ($volume_idx, $instance_idx) = @_; - - my $obj_idx = $self->{_volumes_inv}{$volume_idx}; - return $cb->($obj_idx, $instance_idx); - }); + $self->{on_instance_moved} = $cb; } sub update { @@ -75,10 +87,8 @@ sub update { $self->{_volumes} = {}; # obj_idx => [ volume_idx, volume_idx ] $self->{_volumes_inv} = {}; # volume_idx => obj_idx $self->reset_objects; - return if $self->{model}->objects_count == 0; - $self->set_bounding_box($self->{model}->bounding_box); - $self->set_bed_shape($self->{config}->bed_shape); + $self->update_bed_size; foreach my $obj_idx (0..$#{$self->{model}->objects}) { my $model_object = $self->{model}->get_object($obj_idx); @@ -94,4 +104,9 @@ sub update { } } +sub update_bed_size { + my ($self) = @_; + $self->set_bed_shape($self->{config}->bed_shape); +} + 1; \ No newline at end of file diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 169fab717..24a580ab4 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -88,10 +88,10 @@ sub new { if ($Slic3r::GUI::have_OpenGL) { $canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self); $canvas->load_object($self->{model_object}); - $canvas->set_bounding_box($self->{model_object}->bounding_box); $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->SetMinSize($canvas->GetSize); + $canvas->zoom_to_volumes; } $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index bacc119d7..fb4b0291c 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -70,9 +70,9 @@ sub new { if ($Slic3r::GUI::have_OpenGL) { $canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self); $canvas->load_object($self->{model_object}); - $canvas->set_bounding_box($self->{model_object}->bounding_box); $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); + $canvas->zoom_to_volumes; } $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 282f2cb50..8ea3a3db6 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -12,26 +12,28 @@ use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan scal use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl); use Wx::GLCanvas qw(:all); -__PACKAGE__->mk_accessors( qw(_quat _dirty init mview_init - object_bounding_box +__PACKAGE__->mk_accessors( qw(_quat _dirty init enable_picking enable_moving on_hover on_select on_double_click on_right_click - on_instance_moved + on_move volumes print _sphi _stheta cutting_plane_z cut_lines_vertices + bed_shape bed_triangles bed_grid_lines origin _mouse_pos + _hover_volume_idx _drag_volume_idx _drag_start_pos + _drag_start_xy _camera_target _zoom ) ); @@ -61,20 +63,20 @@ sub new { $self->_stheta(45); $self->_sphi(45); $self->_zoom(1); - $self->_camera_target(Slic3r::Pointf->new(0,0)); + + # 3D point in model space + $self->_camera_target(Slic3r::Pointf3->new(0,0,0)); $self->reset_objects; EVT_PAINT($self, sub { my $dc = Wx::PaintDC->new($self); - return if !$self->object_bounding_box; $self->Render($dc); }); EVT_SIZE($self, sub { $self->_dirty(1) }); EVT_IDLE($self, sub { return unless $self->_dirty; return if !$self->IsShownOnScreen; - return if !$self->object_bounding_box; $self->Resize( $self->GetSizeWH ); $self->Refresh; }); @@ -82,8 +84,10 @@ sub new { my ($self, $e) = @_; # Calculate the zoom delta and apply it to the current zoom factor - my $zoom = 1 - ($e->GetWheelRotation() / $e->GetWheelDelta() / 10); - $self->_zoom($self->_zoom * $zoom); + my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); + $zoom = max(min($zoom, 4), -4); + $zoom /= 10; + $self->_zoom($self->_zoom * (1-$zoom)); # In order to zoom around the mouse point we need to translate # the camera target @@ -97,9 +101,10 @@ sub new { # in the opposite direction by the delta of the length of such vector # ($zoom - 1). We then scale everything by 1/$self->_zoom since # $self->_camera_target is expressed in terms of model units. - -($pos->x - $size->x/2) * ($zoom - 1) / $self->_zoom, - -($pos->y - $size->y/2) * ($zoom - 1) / $self->_zoom, - ); + -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, + -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, + 0, + ) if 0; $self->_dirty(1); $self->Refresh; }); @@ -118,8 +123,7 @@ sub mouse_event { } elsif ($e->LeftDown || $e->RightDown) { # If user pressed left or right button we first check whether this happened # on a volume or not. - my $volume_idx = first { $self->volumes->[$_]->hover } 0..$#{$self->volumes}; - $volume_idx //= -1; + my $volume_idx = $self->_hover_volume_idx // -1; # select volume in this 3D canvas if ($self->enable_picking) { @@ -146,25 +150,27 @@ sub mouse_event { # get volume being dragged my $volume = $self->volumes->[$self->_drag_volume_idx]; - # get new position and calculate the move vector - my $cur_pos = $self->mouse_to_3d(@$pos); + # get new position at the same Z of the initial click point + my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY); + my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z); + + # calculate the translation vector my $vector = $self->_drag_start_pos->vector_to($cur_pos); # apply new temporary volume origin and ignore Z - $volume->origin->set_x(-$vector->x); - $volume->origin->set_y(-$vector->y); #)) + $volume->origin->translate($vector->x, $vector->y, 0); #,, + $self->_drag_start_pos($cur_pos); $self->Refresh; - } elsif ($e->Dragging) { - my $volume_idx = first { $self->volumes->[$_]->hover } 0..$#{$self->volumes}; - $volume_idx //= -1; - - if ($e->LeftIsDown && $volume_idx == -1) { + } elsif ($e->Dragging && !defined $self->_hover_volume_idx) { + if ($e->LeftIsDown) { # if dragging over blank area with left button, rotate if (defined $self->_drag_start_pos) { my $orig = $self->_drag_start_pos; if (TURNTABLE_MODE) { $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- + $self->_stheta(150) if $self->_stheta > 150; + $self->_stheta(0) if $self->_stheta < 0; } else { my $size = $self->GetClientSize; my @quat = trackball( @@ -178,28 +184,27 @@ sub mouse_event { $self->Refresh; } $self->_drag_start_pos($pos); - } elsif ($e->RightIsDown && $volume_idx == -1) { + } elsif ($e->RightIsDown) { # if dragging over blank area with right button, translate - if (defined $self->_drag_start_pos) { - my $orig = $self->_drag_start_pos; - glMatrixMode(GL_MODELVIEW); + + if (defined $self->_drag_start_xy) { + # get point in model space at Z = 0 + my $cur_pos = $self->mouse_ray($e->GetX, $e->GetY)->intersect_plane(0); + my $orig = $self->mouse_ray(@{$self->_drag_start_xy})->intersect_plane(0); $self->_camera_target->translate( - ($pos->x - $orig->x) / $self->_zoom, - ($orig->y - $pos->y) / $self->_zoom, #- (converts to upwards Y) + @{$orig->vector_to($cur_pos)->negative}, ); $self->Refresh; } - $self->_drag_start_pos($pos); + $self->_drag_start_xy($pos); } } elsif ($e->LeftUp || $e->RightUp) { - $self->_drag_start_pos(undef); - - if ($self->on_instance_moved && defined $self->_drag_volume_idx) { - my $volume = $self->volumes->[$self->_drag_volume_idx]; - $self->on_instance_moved($self->_drag_volume_idx, $volume->instance_idx); + if ($self->on_move && defined $self->_drag_volume_idx) { + $self->on_move->($self->_drag_volume_idx); } $self->_drag_volume_idx(undef); $self->_drag_start_pos(undef); + $self->_drag_start_xy(undef); } elsif ($e->Moving) { $self->_mouse_pos($pos); $self->Refresh; @@ -215,35 +220,70 @@ sub reset_objects { $self->_dirty(1); } -# this method accepts a Slic3r::BoudingBox3f object -sub set_bounding_box { - my ($self, $bb) = @_; - - $self->object_bounding_box($bb); - $self->zoom_to_bounding_box; - $self->_dirty(1); -} - sub zoom_to_bounding_box { - my ($self) = @_; + my ($self, $bb) = @_; # calculate the zoom factor needed to adjust viewport to # bounding box - my $max_size = max(@{$self->object_bounding_box->size}) * sqrt(2); + my $max_size = max(@{$bb->size}) * 2; my $min_viewport_size = min($self->GetSizeWH); - $self->_zoom($min_viewport_size / $max_size / 1.3); + $self->_zoom($min_viewport_size / $max_size); # center view around bounding box center - $self->_camera_target->set_x(0); - $self->_camera_target->set_y(0); + $self->_camera_target($bb->center); +} + +sub zoom_to_bed { + my ($self) = @_; + + if ($self->bed_shape) { + $self->zoom_to_bounding_box($self->bed_bounding_box); + } +} + +sub zoom_to_volume { + my ($self, $volume_idx) = @_; + + my $volume = $self->volumes->[$volume_idx]; + my $bb = $volume->bounding_box; + $self->zoom_to_bounding_box($bb); +} + +sub zoom_to_volumes { + my ($self) = @_; + $self->zoom_to_bounding_box($self->volumes_bounding_box); +} + +sub volumes_bounding_box { + my ($self) = @_; + + my $bb = Slic3r::Geometry::BoundingBoxf3->new; + $bb->merge($_->bounding_box) for @{$self->volumes}; + return $bb; +} + +sub bed_bounding_box { + my ($self) = @_; + + my $bb = Slic3r::Geometry::BoundingBoxf3->new; + $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape}; + return $bb; +} + +sub max_bounding_box { + my ($self) = @_; + + my $bb = $self->bed_bounding_box; + $bb->merge($self->volumes_bounding_box); + return $bb; } sub set_auto_bed_shape { my ($self, $bed_shape) = @_; # draw a default square bed around object center - my $max_size = max(@{ $self->object_bounding_box->size }); - my $center = $self->object_bounding_box->center; + my $max_size = max(@{ $self->volumes_bounding_box->size }); + my $center = $self->volumes_bounding_box->center; $self->set_bed_shape([ [ $center->x - $max_size, $center->y - $max_size ], #-- [ $center->x + $max_size, $center->y - $max_size ], #-- @@ -256,6 +296,8 @@ sub set_auto_bed_shape { sub set_bed_shape { my ($self, $bed_shape) = @_; + $self->bed_shape($bed_shape); + # triangulate bed my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]); my $bed_bb = $expolygon->bounding_box; @@ -318,7 +360,7 @@ sub load_object { instance_idx => $instance_idx, mesh => $mesh, color => $color, - origin => Slic3r::Pointf3->new(0,0,$z_min), + origin => Slic3r::Pointf3->new(0,0,-$z_min), ); push @volumes_idx, $#{$self->volumes}; @@ -481,18 +523,27 @@ sub mulquats { } sub mouse_to_3d { - my ($self, $x, $y) = @_; + my ($self, $x, $y, $z) = @_; my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items $y = $viewport[3] - $y; - my $z = glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); + $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport); return Slic3r::Pointf3->new(@projected); } +sub mouse_ray { + my ($self, $x, $y) = @_; + + return Slic3r::Linef3->new( + $self->mouse_to_3d($x, $y, 0), + $self->mouse_to_3d($x, $y, 1), + ); +} + sub GetContext { my ($self) = @_; @@ -513,16 +564,6 @@ sub SetCurrent { } } -sub ResetModelView { - my ($self, $factor) = @_; - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - my $win_size = $self->GetClientSize(); - my $ratio = $factor * min($win_size->width, $win_size->height) / (2 * max(@{ $self->object_bounding_box->size })); - glScalef($ratio, $ratio, 1); -} - sub Resize { my ($self, $x, $y) = @_; @@ -539,14 +580,10 @@ sub Resize { glLoadIdentity(); glOrtho( -$x/2, $x/2, -$y/2, $y/2, - -200, 10 * max(@{ $self->object_bounding_box->size }), + -200, 10 * max(@{ $self->max_bounding_box->size }), ); glMatrixMode(GL_MODELVIEW); - unless ($self->mview_init) { - $self->mview_init(1); - $self->ResetModelView(0.9); - } } sub InitGL { @@ -614,9 +651,6 @@ sub Render { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - my $bb = $self->object_bounding_box; - my $object_size = $bb->size; - glTranslatef(@{ $self->_camera_target }, 0); if (TURNTABLE_MODE) { glRotatef(-$self->_stheta, 1, 0, 0); # pitch glRotatef($self->_sphi, 0, 0, 1); # yaw @@ -624,10 +658,7 @@ sub Render { my @rotmat = quat_to_rotmatrix($self->quat); glMultMatrixd_p(@rotmat[0..15]); } - - # center everything around 0,0 since that's where we're looking at (glOrtho()) - my $center = $bb->center; - glTranslatef(-$center->x, -$center->y, 0); #,, + glTranslatef(@{ $self->_camera_target->negative }); if ($self->enable_picking) { glDisable(GL_LIGHTING); @@ -638,8 +669,10 @@ sub Render { if (my $pos = $self->_mouse_pos) { my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; + $self->_hover_volume_idx(undef); $_->hover(0) for @{$self->volumes}; if ($volume_idx <= $#{$self->volumes}) { + $self->_hover_volume_idx($volume_idx); $self->volumes->[$volume_idx]->hover(1); $self->on_hover->($volume_idx) if $self->on_hover; } @@ -682,11 +715,16 @@ sub Render { glDisableClientState(GL_VERTEX_ARRAY); } + my $volumes_bb = $self->volumes_bounding_box; + { # draw axes $ground_z += 0.02; my $origin = $self->origin; - my $axis_len = 2 * max(@{ $object_size }); + my $axis_len = max( + 0.3 * max(@{ $self->bed_bounding_box->size }), + 2 * max(@{ $volumes_bb->size }), + ); glLineWidth(2); glBegin(GL_LINES); # draw line for x axis @@ -707,6 +745,7 @@ sub Render { # draw cutting plane if (defined $self->cutting_plane_z) { my $plane_z = $z0 + $self->cutting_plane_z; + my $bb = $volumes_bb; glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -804,7 +843,7 @@ sub draw_volumes { foreach my $volume_idx (0..$#{$self->volumes}) { my $volume = $self->volumes->[$volume_idx]; glPushMatrix(); - glTranslatef(@{$volume->origin->negative}); + glTranslatef(@{$volume->origin}); glVertexPointer_p(3, $volume->verts); @@ -850,4 +889,12 @@ has 'norms' => (is => 'rw'); has 'selected' => (is => 'rw', default => sub { 0 }); has 'hover' => (is => 'rw', default => sub { 0 }); +sub bounding_box { + my ($self) = @_; + + my $bb = $self->mesh->bounding_box; + $bb->translate(@{$self->origin}); + return $bb; +} + 1; diff --git a/utils/view-mesh.pl b/utils/view-mesh.pl index 58f641a92..25fbf3638 100644 --- a/utils/view-mesh.pl +++ b/utils/view-mesh.pl @@ -36,8 +36,8 @@ my %opt = (); $app->{canvas}->enable_picking(1); $app->{canvas}->enable_moving($opt{enable_moving}); $app->{canvas}->load_object($model->objects->[0]); - $app->{canvas}->set_bounding_box($model->objects->[0]->bounding_box); $app->{canvas}->set_auto_bed_shape; + $app->{canvas}->zoom_to_volumes; $app->{canvas}->SetCuttingPlane($opt{cut}) if defined $opt{cut}; $app->MainLoop; } diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 112c6ebc3..9ac0b35e9 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -210,6 +210,7 @@ for my $class (qw( Slic3r::Layer::Region Slic3r::Layer::Support Slic3r::Line + Slic3r::Linef3 Slic3r::Model Slic3r::Model::Instance Slic3r::Model::Material diff --git a/xs/src/libslic3r/Line.cpp b/xs/src/libslic3r/Line.cpp index 42d85bb42..ef8598ada 100644 --- a/xs/src/libslic3r/Line.cpp +++ b/xs/src/libslic3r/Line.cpp @@ -184,4 +184,18 @@ Line::to_SV_pureperl() const { } #endif +Pointf3 +Linef3::intersect_plane(double z) const +{ + return Pointf3( + this->a.x + (this->b.x - this->a.x) * (z - this->a.z) / (this->b.z - this->a.z), + this->a.y + (this->b.y - this->a.y) * (z - this->a.z) / (this->b.z - this->a.z), + z + ); +} + +#ifdef SLIC3RXS +REGISTER_CLASS(Linef3, "Linef3"); +#endif + } diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp index 8abbbdbcd..52f3ef77f 100644 --- a/xs/src/libslic3r/Line.hpp +++ b/xs/src/libslic3r/Line.hpp @@ -7,6 +7,7 @@ namespace Slic3r { class Line; +class Linef3; class Polyline; class Line @@ -46,6 +47,22 @@ class Line typedef std::vector Lines; +class Linef3 +{ + public: + Pointf3 a; + Pointf3 b; + Linef3() {}; + explicit Linef3(Pointf3 _a, Pointf3 _b): a(_a), b(_b) {}; + Pointf3 intersect_plane(double z) const; + + #ifdef SLIC3RXS + void from_SV(SV* line_sv); + void from_SV_check(SV* line_sv); + SV* to_SV_pureperl() const; + #endif +}; + } // start Boost diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp index 61995a328..eafe55766 100644 --- a/xs/xsp/BoundingBox.xsp +++ b/xs/xsp/BoundingBox.xsp @@ -83,6 +83,7 @@ new_from_points(CLASS, points) Clone clone() %code{% RETVAL = THIS; %}; void merge(BoundingBoxf3* bb) %code{% THIS->merge(*bb); %}; + void merge_point(Pointf3* point) %code{% THIS->merge(*point); %}; void scale(double factor); void translate(double x, double y, double z); Clone size(); diff --git a/xs/xsp/Line.xsp b/xs/xsp/Line.xsp index bf31e6e95..69d9fbce1 100644 --- a/xs/xsp/Line.xsp +++ b/xs/xsp/Line.xsp @@ -66,3 +66,17 @@ Line::coincides_with(line_sv) %} }; + + +%name{Slic3r::Linef3} class Linef3 { + Linef3(Pointf3* a, Pointf3* b) + %code{% RETVAL = new Linef3(*a, *b); %}; + ~Linef3(); + Clone clone() + %code{% RETVAL = THIS; %}; + Ref a() + %code{% RETVAL = &THIS->a; %}; + Ref b() + %code{% RETVAL = &THIS->b; %}; + Clone intersect_plane(double z); +}; diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 5be5aef00..3b244fe55 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -125,6 +125,7 @@ Point::coincides_with(point_sv) void set_z(double val) %code{% THIS->z = val; %}; void translate(double x, double y, double z); + void scale(double factor); double distance_to(Pointf3* point) %code{% RETVAL = THIS->distance_to(*point); %}; Clone negative() diff --git a/xs/xsp/my.map b/xs/xsp/my.map index b154488ae..e295dbeeb 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -73,6 +73,10 @@ Line* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +Linef3* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + Polyline* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 1d5ec4729..2dc1c3ca4 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -60,6 +60,9 @@ %typemap{Line*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{Linef3*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; %typemap{Polyline*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; From a200498eef2336bd206ac256c65cef51831675c2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 16 Dec 2014 18:13:31 +0100 Subject: [PATCH 045/107] Minor fix to view-toolpaths.pl --- utils/view-toolpaths.pl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/view-toolpaths.pl b/utils/view-toolpaths.pl index 66ee226db..847ba61d9 100755 --- a/utils/view-toolpaths.pl +++ b/utils/view-toolpaths.pl @@ -85,13 +85,12 @@ sub OnInit { $canvas = Slic3r::GUI::PreviewCanvas->new($panel); $canvas->print($print); - #$canvas->set_bounding_box($print->bounding_box); $canvas->set_bed_shape($print->config->bed_shape); - $canvas->set_bounding_box($print->objects->[0]->model_object->bounding_box); foreach my $object (@{$print->objects}) { $canvas->load_object($object->model_object); } + $canvas->zoom_to_volumes; } else { $canvas = Slic3r::GUI::Plater::2DToolpaths->new($panel, $print); } From 99deffef62aaa167722bf36d14c2ae919dac57dd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 16 Dec 2014 18:13:38 +0100 Subject: [PATCH 046/107] Bugfix: use the external motion planner when extruding skirt and brim. #2412 --- lib/Slic3r/GCode.pm | 24 ++++++++++++------------ lib/Slic3r/Print/GCode.pm | 11 ++++++++--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 0f322ca15..662d95a86 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -349,17 +349,19 @@ sub travel_to { ) { # Just perform a straight travel move without any retraction. $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment); - } elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->straight_once) { + } elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) { + # If avoid_crossing_perimeters is enabled and the disable_once flag is not set + # we need to plan a multi-segment travel move inside the configuration space. $gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment); } else { - # If avoid_crossing_perimeters is disabled or the straight_once flag is set, + # If avoid_crossing_perimeters is disabled or the disable_once flag is set, # perform a straight move with a retraction. $gcode .= $self->retract; $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || ''); } # Re-allow avoid_crossing_perimeters for the next travel moves - $self->avoid_crossing_perimeters->straight_once(0); + $self->avoid_crossing_perimeters->disable_once(0); return $gcode; } @@ -554,10 +556,11 @@ sub wipe { package Slic3r::GCode::AvoidCrossingPerimeters; use Moo; -has '_external_mp' => (is => 'rw'); -has '_layer_mp' => (is => 'rw'); -has 'new_object' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move -has 'straight_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move +has '_external_mp' => (is => 'rw'); +has '_layer_mp' => (is => 'rw'); +has 'use_external_mp' => (is => 'rw', default => sub {0}); +has 'use_external_mp_once' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move +has 'disable_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move use Slic3r::Geometry qw(scale); @@ -576,11 +579,8 @@ sub travel_to { my $gcode = ""; - # If avoid_crossing_perimeters is enabled and the straight_once flag is not set - # we need to plan a multi-segment travel move inside the configuration space. - if ($self->new_object) { - # If we're moving to a new object we need to use the external configuration space. - $self->new_object(0); + if ($self->use_external_mp || $self->use_external_mp_once) { + $self->use_external_mp_once(0); # represent $point in G-code coordinates $point = $point->clone; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index c1b1a6813..b8391b5e8 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -309,6 +309,7 @@ sub process_layer { if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) && !$self->_skirt_done->{$layer->print_z}) { $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); + $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders}; $gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]); # skip skirt if we have a large brim @@ -326,21 +327,25 @@ sub process_layer { } } $self->_skirt_done->{$layer->print_z} = 1; - $self->_gcodegen->avoid_crossing_perimeters->straight_once(1); + $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0); + $self->_gcodegen->avoid_crossing_perimeters->disable_once(1); } # extrude brim if (!$self->_brim_done) { $gcode .= $self->_gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1); $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); + $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); $gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) for @{$self->print->brim}; $self->_brim_done(1); - $self->_gcodegen->avoid_crossing_perimeters->straight_once(1); + $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0); + $self->_gcodegen->avoid_crossing_perimeters->disable_once(1); } for my $copy (@$object_copies) { - $self->_gcodegen->avoid_crossing_perimeters->new_object(1) if ($self->_last_obj_copy // '') ne "$copy"; + # when starting a new object, use the external motion planner for the first travel move + $self->_gcodegen->avoid_crossing_perimeters->use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy"; $self->_last_obj_copy("$copy"); $self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); From d9cffeca4a400c8f534fce56f4d17a0ce7134ce5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 16 Dec 2014 18:55:16 +0100 Subject: [PATCH 047/107] Bugfix: adjust skirt flow according to each layer's height. #2307 --- lib/Slic3r/Print.pm | 4 ++-- lib/Slic3r/Print/GCode.pm | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 09ebd40a2..db7309591 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -280,9 +280,9 @@ sub make_skirt { Slic3r::ExtrusionPath->new( polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point, role => EXTR_ROLE_SKIRT, - mm3_per_mm => $mm3_per_mm, + mm3_per_mm => $mm3_per_mm, # this will be overridden at G-code export time width => $flow->width, - height => $first_layer_height, + height => $first_layer_height, # this will be overridden at G-code export time ), )); diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index b8391b5e8..ca572fc72 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -314,6 +314,8 @@ sub process_layer { $gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]); # skip skirt if we have a large brim if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) { + my $skirt_flow = $self->print->skirt_flow; + # distribute skirt loops across all extruders my @skirt_loops = @{$self->print->skirt}; for my $i (0 .. $#skirt_loops) { @@ -323,7 +325,20 @@ 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', $object->config->support_material_speed); + + # adjust flow according to this layer's layer height + my $loop = $skirt_loops[$i]->clone; + { + my $layer_skirt_flow = $skirt_flow->clone; + $layer_skirt_flow->set_height($layer->height); + my $mm3_per_mm = $layer_skirt_flow->mm3_per_mm; + foreach my $path (@$loop) { + $path->height($layer->height); + $path->mm3_per_mm($mm3_per_mm); + } + } + + $gcode .= $self->_gcodegen->extrude_loop($loop, 'skirt', $object->config->support_material_speed); } } $self->_skirt_done->{$layer->print_z} = 1; From e79aa2e81c3087d44c46d0fcb65cd81a42f0585d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Dec 2014 00:34:00 +0100 Subject: [PATCH 048/107] New --solid-infill-extruder option. Includes a refactoring of the strategy used to order layer extrusions according to their extruder and island; toolchanges and travel moves should be more optimized now. #618 --- README.md | 1 + lib/Slic3r/Fill.pm | 36 ++-- lib/Slic3r/GCode.pm | 4 +- lib/Slic3r/GUI/Tab.pm | 4 +- lib/Slic3r/Layer/Region.pm | 4 +- lib/Slic3r/Print/GCode.pm | 154 +++++++++++------- lib/Slic3r/Print/Object.pm | 5 +- lib/Slic3r/Print/SupportMaterial.pm | 2 +- slic3r.pl | 1 + t/fill.t | 2 + xs/lib/Slic3r/XS.pm | 4 +- xs/src/libslic3r/ExtrusionEntity.cpp | 13 +- xs/src/libslic3r/ExtrusionEntity.hpp | 3 +- .../libslic3r/ExtrusionEntityCollection.cpp | 3 +- .../libslic3r/ExtrusionEntityCollection.hpp | 2 + xs/src/libslic3r/Layer.hpp | 2 + xs/src/libslic3r/Print.cpp | 1 + xs/src/libslic3r/PrintConfig.cpp | 7 + xs/src/libslic3r/PrintConfig.hpp | 7 + xs/src/libslic3r/PrintObject.cpp | 1 + xs/src/libslic3r/PrintRegion.cpp | 4 +- xs/t/12_extrusionpathcollection.t | 7 +- xs/t/15_config.t | 9 +- xs/xsp/ExtrusionEntityCollection.xsp | 2 +- xs/xsp/ExtrusionPath.xsp | 3 +- 25 files changed, 186 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 4e35d5572..fe8c267d6 100644 --- a/README.md +++ b/README.md @@ -353,6 +353,7 @@ The author of the Silk icon set is Mark James. --perimeter-extruder Extruder to use for perimeters (1+, default: 1) --infill-extruder Extruder to use for infill (1+, default: 1) + --solid-infill-extruder Extruder to use for solid infill (1+, default: 1) --support-material-extruder Extruder to use for support material (1+, default: 1) --support-material-interface-extruder diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 82c2ec6cc..4547a846b 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -225,28 +225,32 @@ sub make_fill { my $mm3_per_mm = $flow->mm3_per_mm; # save into layer - push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new; - $collection->no_sort($params->{no_sort}); + { + my $role = $is_bridge ? EXTR_ROLE_BRIDGE + : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) + : EXTR_ROLE_FILL; + + my $extrusion_height = $is_bridge ? $flow->width : $h; + + push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new($role); + $collection->no_sort($params->{no_sort}); + $collection->append( + map Slic3r::ExtrusionPath->new( + polyline => $_, + role => $role, + mm3_per_mm => $mm3_per_mm, + width => $flow->width, + height => $extrusion_height, + ), @polylines, + ); + } - $collection->append( - map Slic3r::ExtrusionPath->new( - polyline => $_, - role => ($is_bridge - ? EXTR_ROLE_BRIDGE - : $is_solid - ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) - : EXTR_ROLE_FILL), - mm3_per_mm => $mm3_per_mm, - width => $flow->width, - height => ($is_bridge ? $flow->width : $h), - ), @polylines, - ); push @fills_ordering_points, $polylines[0]->first_point; } # add thin fill regions foreach my $thin_fill (@{$layerm->thin_fills}) { - push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill); + push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill->role, $thin_fill); push @fills_ordering_points, $thin_fill->first_point; } diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 662d95a86..9dc4e29bd 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -256,10 +256,10 @@ sub _extrude_path { $acceleration = $self->config->first_layer_acceleration; } elsif ($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->bridge_acceleration && $path->is_bridge) { $acceleration = $self->config->bridge_acceleration; + } elsif ($self->config->infill_acceleration && $path->is_infill) { + $acceleration = $self->config->infill_acceleration; } else { $acceleration = $self->config->default_acceleration; } diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index ecb476fd5..ad589e2b5 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -454,7 +454,8 @@ sub build { complete_objects extruder_clearance_radius extruder_clearance_height gcode_comments output_filename_format post_process - perimeter_extruder infill_extruder support_material_extruder support_material_interface_extruder + perimeter_extruder infill_extruder solid_infill_extruder + support_material_extruder support_material_interface_extruder ooze_prevention standby_temperature_delta interface_shells extrusion_width first_layer_extrusion_width perimeter_extrusion_width @@ -648,6 +649,7 @@ sub build { my $optgroup = $page->new_optgroup('Extruders'); $optgroup->append_single_option_line('perimeter_extruder'); $optgroup->append_single_option_line('infill_extruder'); + $optgroup->append_single_option_line('solid_infill_extruder'); $optgroup->append_single_option_line('support_material_extruder'); $optgroup->append_single_option_line('support_material_interface_extruder'); } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index fbc665df3..bc09f77ca 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -246,7 +246,7 @@ sub make_perimeters { my ($polynodes, $depth, $is_contour) = @_; # convert all polynodes to ExtrusionLoop objects - my $collection = Slic3r::ExtrusionPath::Collection->new; + my $collection = Slic3r::ExtrusionPath::Collection->new(EXTR_ROLE_PERIMETER); # temporary collection my @children = (); foreach my $polynode (@$polynodes) { my $polygon = ($polynode->{outer} // $polynode->{hole})->clone; @@ -303,7 +303,7 @@ sub make_perimeters { # (clone because the collection gets DESTROY'ed) # We allow polyline reversal because Clipper may have randomly # reversed polylines during clipping. - my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); + my $collection = Slic3r::ExtrusionPath::Collection->new(EXTR_ROLE_PERIMETER, @paths); # temporary collection @paths = map $_->clone, @{$collection->chained_path(0)}; } else { push @paths, Slic3r::ExtrusionPath->new( diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index ca572fc72..7417c48ec 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -380,54 +380,87 @@ sub process_layer { } } - # tweak region ordering to save toolchanges - my @region_ids = 0 .. ($self->print->region_count-1); - if ($self->_gcodegen->writer->multiple_extruders) { - my $last_extruder_id = $self->_gcodegen->writer->extruder->id; - my $best_region_id = first { $self->print->regions->[$_]->config->perimeter_extruder-1 == $last_extruder_id } @region_ids; - @region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id; + # We now define a strategy for building perimeters and fills. The separation + # between regions doesn't matter in terms of printing order, as we follow + # another logic instead: + # - we group all extrusions by extruder so that we minimize toolchanges + # - we start from the last used extruder + # - for each extruder, we group extrusions by island + # - for each island, we extrude perimeters first, unless user set the infill_first + # option + + # group extrusions by extruder and then by island + my %by_extruder = (); # extruder_id => [ { perimeters => \@perimeters, infill => \@infill } ] + + foreach my $region_id (0..($self->print->region_count-1)) { + my $layerm = $layer->regions->[$region_id] or next; + my $region = $self->print->get_region($region_id); + + # process perimeters + { + my $extruder_id = $region->config->perimeter_extruder-1; + foreach my $perimeter (@{$layerm->perimeters}) { + # init by_extruder item only if we actually use the extruder + $by_extruder{$extruder_id} //= []; + + # $perimeter is an ExtrusionLoop or ExtrusionPath object + for my $i (0 .. $#{$layer->slices}) { + if ($i == $#{$layer->slices} + || $layer->slices->[$i]->contour->contains_point($perimeter->first_point)) { + $by_extruder{$extruder_id}[$i] //= { perimeters => {} }; + $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= []; + push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, $perimeter; + last; + } + } + } + } + + # process infill + { + foreach my $fill (@{$layerm->fills}) { + # init by_extruder item only if we actually use the extruder + my $extruder_id = $fill->[0]->is_solid_infill + ? $region->config->solid_infill_extruder-1 + : $region->config->infill_extruder-1; + + $by_extruder{$extruder_id} //= []; + + # $fill is an ExtrusionPath::Collection object + for my $i (0 .. $#{$layer->slices}) { + if ($i == $#{$layer->slices} + || $layer->slices->[$i]->contour->contains_point($fill->first_point)) { + $by_extruder{$extruder_id}[$i] //= { infill => {} }; + $by_extruder{$extruder_id}[$i]{infill}{$region_id} //= []; + push @{ $by_extruder{$extruder_id}[$i]{infill}{$region_id} }, $fill; + last; + } + } + } + } } - foreach my $region_id (@region_ids) { - my $layerm = $layer->regions->[$region_id] or next; - my $region = $self->print->regions->[$region_id]; - $self->_gcodegen->config->apply_region_config($region->config); - - # group extrusions by island - my @perimeters_by_island = map [], 0..$#{$layer->slices}; # slice idx => @perimeters - my @infill_by_island = map [], 0..$#{$layer->slices}; # slice idx => @fills - - # NOTE: we assume $layer->slices was already ordered with chained_path()! - - PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { - for my $i (0 .. $#{$layer->slices}-1) { - if ($layer->slices->[$i]->contour->contains_point($perimeter->first_point)) { - push @{ $perimeters_by_island[$i] }, $perimeter; - next PERIMETER; - } - } - push @{ $perimeters_by_island[-1] }, $perimeter; # optimization + # tweak extruder ordering to save toolchanges + my @extruders = sort keys %by_extruder; + if (@extruders > 1) { + my $last_extruder_id = $self->_gcodegen->writer->extruder->id; + if (exists $by_extruder{$last_extruder_id}) { + @extruders = ( + $last_extruder_id, + grep $_ != $last_extruder_id, @extruders, + ); } - FILL: foreach my $fill (@{$layerm->fills}) { - for my $i (0 .. $#{$layer->slices}-1) { - if ($layer->slices->[$i]->contour->contains_point($fill->first_point)) { - push @{ $infill_by_island[$i] }, $fill; - next FILL; - } - } - push @{ $infill_by_island[-1] }, $fill; # optimization - } - - for my $i (0 .. $#{$layer->slices}) { - # give priority to infill if we were already using its extruder and it wouldn't - # be good for perimeters - if ($self->print->config->infill_first - || ($self->_gcodegen->writer->multiple_extruders && $region->config->infill_extruder-1 == $self->_gcodegen->writer->extruder->id && $region->config->infill_extruder != $region->config->perimeter_extruder)) { - $gcode .= $self->_extrude_infill($infill_by_island[$i], $region); - $gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region); + } + + foreach my $extruder_id (@extruders) { + $gcode .= $self->_gcodegen->set_extruder($extruder_id); + foreach my $island (@{ $by_extruder{$extruder_id} }) { + if ($self->print->config->infill_first) { + $gcode .= $self->_extrude_infill($island->{infill} // {}); + $gcode .= $self->_extrude_perimeters($island->{perimeters} // {}); } else { - $gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region); - $gcode .= $self->_extrude_infill($infill_by_island[$i], $region); + $gcode .= $self->_extrude_perimeters($island->{perimeters} // {}); + $gcode .= $self->_extrude_infill($island->{infill} // {}); } } } @@ -452,31 +485,30 @@ sub process_layer { } sub _extrude_perimeters { - my $self = shift; - my ($island_perimeters, $region) = @_; - - return "" if !@$island_perimeters; + my ($self, $entities_by_region) = @_; my $gcode = ""; - $gcode .= $self->_gcodegen->set_extruder($region->config->perimeter_extruder-1); - $gcode .= $self->_gcodegen->extrude($_, 'perimeter') for @$island_perimeters; + foreach my $region_id (sort keys %$entities_by_region) { + $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config); + $gcode .= $self->_gcodegen->extrude($_, 'perimeter') + for @{ $entities_by_region->{$region_id} }; + } return $gcode; } sub _extrude_infill { - my $self = shift; - my ($island_fills, $region) = @_; - - return "" if !@$island_fills; + my ($self, $entities_by_region) = @_; my $gcode = ""; - $gcode .= $self->_gcodegen->set_extruder($region->config->infill_extruder-1); - for my $fill (@$island_fills) { - if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { - $gcode .= $self->_gcodegen->extrude($_, 'fill') - for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)}; - } else { - $gcode .= $self->_gcodegen->extrude($fill, 'fill') ; + foreach my $region_id (sort keys %$entities_by_region) { + $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config); + for my $fill (@{ $entities_by_region->{$region_id} }) { + if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { + $gcode .= $self->_gcodegen->extrude($_, 'infill') + for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)}; + } else { + $gcode .= $self->_gcodegen->extrude($fill, 'infill') ; + } } } return $gcode; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 166f18792..be819fefb 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -1004,7 +1004,10 @@ sub combine_infill { next unless $every > 1 && $region->config->fill_density > 0; # limit the number of combined layers to the maximum height allowed by this regions' nozzle - my $nozzle_diameter = $self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1); + my $nozzle_diameter = min( + $self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1), + $self->print->config->get_at('nozzle_diameter', $region->config->solid_infill_extruder-1), + ); # define the combinations my %combine = (); # layer_idx => number of additional combined lower layers diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 01df21a55..5771fcc66 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -262,7 +262,7 @@ sub contact_area { { # get the average nozzle diameter used on this layer my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_), - map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1 } + map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1, $_->config->solid_infill_extruder-1 } @{$layer->regions}; my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; diff --git a/slic3r.pl b/slic3r.pl index 70e109215..3568f92e3 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -505,6 +505,7 @@ $j --perimeter-extruder Extruder to use for perimeters (1+, default: $config->{perimeter_extruder}) --infill-extruder Extruder to use for infill (1+, default: $config->{infill_extruder}) + --solid-infill-extruder Extruder to use for solid infill (1+, default: $config->{solid_infill_extruder}) --support-material-extruder Extruder to use for support material (1+, default: $config->{support_material_extruder}) --support-material-interface-extruder diff --git a/t/fill.t b/t/fill.t index 24370b660..15f693a66 100644 --- a/t/fill.t +++ b/t/fill.t @@ -146,6 +146,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $collection = Slic3r::ExtrusionPath::Collection->new( + EXTR_ROLE_PERIMETER, map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), @@ -158,6 +159,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $collection = Slic3r::ExtrusionPath::Collection->new( + EXTR_ROLE_PERIMETER, map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 9ac0b35e9..08bb625cb 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -64,9 +64,9 @@ use overload 'fallback' => 1; sub new { - my ($class, @paths) = @_; + my ($class, $type, @paths) = @_; - my $self = $class->_new; + my $self = $class->_new($type); $self->append(@paths); return $self; } diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index 1bc5b4b24..7901317a3 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -76,9 +76,18 @@ ExtrusionPath::is_perimeter() const } bool -ExtrusionPath::is_fill() const +ExtrusionPath::is_infill() const { - return this->role == erInternalInfill + return this->role == erBridgeInfill + || this->role == erInternalInfill + || this->role == erSolidInfill + || this->role == erTopSolidInfill; +} + +bool +ExtrusionPath::is_solid_infill() const +{ + return this->role == erBridgeInfill || this->role == erSolidInfill || this->role == erTopSolidInfill; } diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index aad36cb3d..da7658b76 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -64,7 +64,8 @@ class ExtrusionPath : public ExtrusionEntity void simplify(double tolerance); double length() const; bool is_perimeter() const; - bool is_fill() const; + bool is_infill() const; + bool is_solid_infill() const; bool is_bridge() const; std::string gcode(Extruder* extruder, double e, double F, double xofs, double yofs, std::string extrusion_axis, diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index a958e53cf..89748bc86 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -5,7 +5,7 @@ namespace Slic3r { ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionEntityCollection& collection) - : no_sort(collection.no_sort), orig_indices(collection.orig_indices) + : no_sort(collection.no_sort), role(collection.role), orig_indices(collection.orig_indices) { this->entities.reserve(collection.entities.size()); for (ExtrusionEntitiesPtr::const_iterator it = collection.entities.begin(); it != collection.entities.end(); ++it) @@ -23,6 +23,7 @@ void ExtrusionEntityCollection::swap (ExtrusionEntityCollection &c) { std::swap(this->entities, c.entities); + std::swap(this->role, c.role); std::swap(this->orig_indices, c.orig_indices); std::swap(this->no_sort, c.no_sort); } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index bc660611b..a72563f47 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -13,7 +13,9 @@ class ExtrusionEntityCollection : public ExtrusionEntity ExtrusionEntitiesPtr entities; std::vector orig_indices; // handy for XS bool no_sort; + ExtrusionRole role; ExtrusionEntityCollection(): no_sort(false) {}; + ExtrusionEntityCollection(ExtrusionRole _role): no_sort(false), role(_role) {}; ExtrusionEntityCollection(const ExtrusionEntityCollection &collection); ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other); void swap (ExtrusionEntityCollection &c); diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index ff8cca1d9..115a728e0 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -46,9 +46,11 @@ class LayerRegion PolylineCollection unsupported_bridge_edges; // ordered collection of extrusion paths/loops to build all perimeters + // (this collection contains both ExtrusionPath and ExtrusionLoop objects) ExtrusionEntityCollection perimeters; // ordered collection of extrusion paths to fill surfaces + // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection fills; Flow flow(FlowRole role, bool bridge = false, double width = -1) const; diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 6edd32609..9d834c605 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -301,6 +301,7 @@ Print::extruders() const FOREACH_REGION(this, region) { extruders.insert((*region)->config.perimeter_extruder - 1); extruders.insert((*region)->config.infill_extruder - 1); + extruders.insert((*region)->config.solid_infill_extruder - 1); } FOREACH_OBJECT(this, object) { extruders.insert((*object)->config.support_material_extruder - 1); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 8e9720b76..649c941e9 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -694,6 +694,13 @@ PrintConfigDef::build_def() { Options["solid_infill_below_area"].cli = "solid-infill-below-area=f"; Options["solid_infill_below_area"].min = 0; + Options["solid_infill_extruder"].type = coInt; + Options["solid_infill_extruder"].label = "Solid infill extruder"; + Options["solid_infill_extruder"].category = "Extruders"; + Options["solid_infill_extruder"].tooltip = "The extruder to use when printing solid infill."; + Options["solid_infill_extruder"].cli = "solid-infill-extruder=i"; + Options["solid_infill_extruder"].min = 1; + Options["solid_infill_every_layers"].type = coInt; Options["solid_infill_every_layers"].label = "Solid infill every"; Options["solid_infill_every_layers"].category = "Infill"; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 447948a26..48235c7d1 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -93,6 +93,10 @@ class DynamicPrintConfig : public DynamicConfig this->option("support_material_interface_extruder", true)->setInt(extruder); } } + + if (!this->has("solid_infill_extruder") && this->has("infill_extruder")) + this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt()); + if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { { // this should be actually done only on the spiral layers instead of all @@ -225,6 +229,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig ConfigOptionInt perimeters; ConfigOptionFloatOrPercent small_perimeter_speed; ConfigOptionFloat solid_infill_below_area; + ConfigOptionInt solid_infill_extruder; ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionInt solid_infill_every_layers; ConfigOptionFloatOrPercent solid_infill_speed; @@ -259,6 +264,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig this->perimeter_extrusion_width.percent = false; this->perimeter_speed.value = 30; this->perimeters.value = 3; + this->solid_infill_extruder.value = 1; this->small_perimeter_speed.value = 30; this->small_perimeter_speed.percent = false; this->solid_infill_below_area.value = 70; @@ -299,6 +305,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig if (opt_key == "perimeters") return &this->perimeters; if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed; if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area; + if (opt_key == "solid_infill_extruder") return &this->solid_infill_extruder; if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width; if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers; if (opt_key == "solid_infill_speed") return &this->solid_infill_speed; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 0da9d589b..da7e25a48 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -255,6 +255,7 @@ PrintObject::invalidate_state_by_config_options(const std::vectorconfig.perimeter_extruder; - } else if (role == frInfill || role == frSolidInfill || role == frTopSolidInfill) { + } else if (role == frInfill) { extruder = this->config.infill_extruder; + } else if (role == frSolidInfill || role == frTopSolidInfill) { + extruder = this->config.solid_infill_extruder; } else { CONFESS("Unknown role $role"); } diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t index 6933f965a..7349ca941 100644 --- a/xs/t/12_extrusionpathcollection.t +++ b/xs/t/12_extrusionpathcollection.t @@ -26,7 +26,10 @@ my $loop = Slic3r::ExtrusionLoop->new_from_paths( ), ); -my $collection = Slic3r::ExtrusionPath::Collection->new($path); +my $collection = Slic3r::ExtrusionPath::Collection->new( + Slic3r::ExtrusionPath::EXTR_ROLE_FILL, + $path, +); isa_ok $collection, 'Slic3r::ExtrusionPath::Collection', 'collection object with items in constructor'; ok !$collection->no_sort, 'no_sort is false by default'; @@ -55,6 +58,7 @@ is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { my $collection = Slic3r::ExtrusionPath::Collection->new( + Slic3r::ExtrusionPath::EXTR_ROLE_FILL, map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), @@ -71,6 +75,7 @@ is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { my $collection = Slic3r::ExtrusionPath::Collection->new( + Slic3r::ExtrusionPath::EXTR_ROLE_FILL, map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), diff --git a/xs/t/15_config.t b/xs/t/15_config.t index cd4dc2654..e051d9a86 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 => 107; +use Test::More tests => 108; foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { $config->set('layer_height', 0.3); @@ -182,6 +182,13 @@ 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('infill_extruder', 2); + $config->normalize; + is $config->get('solid_infill_extruder'), 2, 'undefined solid infill extruder is populated with infill extruder'; +} + { my $config = Slic3r::Config->new; $config->set('spiral_vase', 1); diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp index fa1d26eed..290bcea50 100644 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ b/xs/xsp/ExtrusionEntityCollection.xsp @@ -6,7 +6,7 @@ %} %name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { - %name{_new} ExtrusionEntityCollection(); + %name{_new} ExtrusionEntityCollection(ExtrusionRole role); void reverse(); void clear() %code{% THIS->entities.clear(); %}; diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp index d3a48ac24..4fbaea2d6 100644 --- a/xs/xsp/ExtrusionPath.xsp +++ b/xs/xsp/ExtrusionPath.xsp @@ -23,7 +23,8 @@ void simplify(double tolerance); double length(); bool is_perimeter(); - bool is_fill(); + bool is_infill(); + bool is_solid_infill(); bool is_bridge(); std::string gcode(Extruder* extruder, double e, double F, double xofs, double yofs, std::string extrusion_axis, From c00061678b0dabaf0aa61bc4a5245ebb945cbcf0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Dec 2014 00:45:05 +0100 Subject: [PATCH 049/107] Use perimeter extruder for brim. #618 --- README.md | 4 ++-- lib/Slic3r/Print.pm | 4 ++-- lib/Slic3r/Print/GCode.pm | 2 +- slic3r.pl | 4 ++-- xs/src/libslic3r/Print.cpp | 30 ++++++++++++++++++++++++++++-- xs/src/libslic3r/Print.hpp | 1 + xs/src/libslic3r/PrintConfig.cpp | 4 ++-- xs/xsp/Print.xsp | 1 + 8 files changed, 39 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fe8c267d6..653e40246 100644 --- a/README.md +++ b/README.md @@ -351,11 +351,11 @@ The author of the Silk icon set is Mark James. --extruder-offset Offset of each extruder, if firmware doesn't handle the displacement (can be specified multiple times, default: 0x0) --perimeter-extruder - Extruder to use for perimeters (1+, default: 1) + Extruder to use for perimeters and brim (1+, default: 1) --infill-extruder Extruder to use for infill (1+, default: 1) --solid-infill-extruder Extruder to use for solid infill (1+, default: 1) --support-material-extruder - Extruder to use for support material (1+, default: 1) + Extruder to use for support material, raft and skirt (1+, default: 1) --support-material-interface-extruder Extruder to use for support material interface (1+, default: 1) --ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index db7309591..69df1c900 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -332,9 +332,9 @@ sub make_brim { } $self->status_cb->(88, "Generating brim"); - # brim is only printed on first layer and uses support material extruder + # brim is only printed on first layer and uses perimeter extruder my $first_layer_height = $self->skirt_first_layer_height; - my $flow = $self->skirt_flow; + my $flow = $self->brim_flow; my $mm3_per_mm = $flow->mm3_per_mm; my $grow_distance = $flow->scaled_width / 2; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 7417c48ec..daa665bfd 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -348,7 +348,7 @@ sub process_layer { # extrude brim if (!$self->_brim_done) { - $gcode .= $self->_gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1); + $gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1); $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); $gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) diff --git a/slic3r.pl b/slic3r.pl index 3568f92e3..debc5ff79 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -503,11 +503,11 @@ $j --extruder-offset Offset of each extruder, if firmware doesn't handle the displacement (can be specified multiple times, default: 0x0) --perimeter-extruder - Extruder to use for perimeters (1+, default: $config->{perimeter_extruder}) + Extruder to use for perimeters and brim (1+, default: $config->{perimeter_extruder}) --infill-extruder Extruder to use for infill (1+, default: $config->{infill_extruder}) --solid-infill-extruder Extruder to use for solid infill (1+, default: $config->{solid_infill_extruder}) --support-material-extruder - Extruder to use for support material (1+, default: $config->{support_material_extruder}) + Extruder to use for support material, raft and skirt (1+, default: $config->{support_material_extruder}) --support-material-interface-extruder Extruder to use for support material interface (1+, default: $config->{support_material_interface_extruder}) --ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 9d834c605..1d186d3ab 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -669,11 +669,12 @@ Print::total_bounding_box() const } // consider brim and skirt - Flow skirt_flow = this->skirt_flow(); if (this->config.brim_width.value > 0) { - extra = std::max(extra, this->config.brim_width.value + skirt_flow.width/2); + Flow brim_flow = this->brim_flow(); + extra = std::max(extra, this->config.brim_width.value + brim_flow.width/2); } if (this->config.skirts.value > 0) { + Flow skirt_flow = this->skirt_flow(); extra = std::max( extra, this->config.brim_width.value @@ -696,12 +697,37 @@ Print::skirt_first_layer_height() const return this->objects.front()->config.get_abs_value("first_layer_height"); } +Flow +Print::brim_flow() const +{ + ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; + if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; + + /* We currently use a random region's perimeter extruder. + While this works for most cases, we should probably consider all of the perimeter + extruders and take the one with, say, the smallest index. + The same logic should be applied to the code that selects the extruder during G-code + generation as well. */ + return Flow::new_from_config_width( + frPerimeter, + width, + this->config.nozzle_diameter.get_at(this->regions.front()->config.perimeter_extruder-1), + this->skirt_first_layer_height(), + 0 + ); +} + Flow Print::skirt_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; + /* We currently use a random object's support material extruder. + While this works for most cases, we should probably consider all of the support material + extruders and take the one with, say, the smallest index; + The same logic should be applied to the code that selects the extruder during G-code + generation as well. */ return Flow::new_from_config_width( frPerimeter, width, diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index fd0e6a19d..de93385f6 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -188,6 +188,7 @@ class Print BoundingBox bounding_box() const; BoundingBox total_bounding_box() const; double skirt_first_layer_height() const; + Flow brim_flow() const; Flow skirt_flow() const; std::set extruders() const; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 649c941e9..c0ebb9bd5 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -529,7 +529,7 @@ PrintConfigDef::build_def() { Options["perimeter_extruder"].type = coInt; Options["perimeter_extruder"].label = "Perimeter extruder"; Options["perimeter_extruder"].category = "Extruders"; - Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters. First extruder is 1."; + Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters and brim. First extruder is 1."; Options["perimeter_extruder"].cli = "perimeter-extruder=i"; Options["perimeter_extruder"].aliases.push_back("perimeters_extruder"); Options["perimeter_extruder"].min = 1; @@ -781,7 +781,7 @@ PrintConfigDef::build_def() { Options["support_material_extruder"].type = coInt; Options["support_material_extruder"].label = "Support material extruder"; Options["support_material_extruder"].category = "Extruders"; - Options["support_material_extruder"].tooltip = "The extruder to use when printing support material. This affects brim and raft too."; + Options["support_material_extruder"].tooltip = "The extruder to use when printing support material, raft and skirt."; Options["support_material_extruder"].cli = "support-material-extruder=i"; Options["support_material_extruder"].min = 1; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index aed6f6e4e..baf0a59ed 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -190,6 +190,7 @@ _constant() Clone bounding_box(); Clone total_bounding_box(); double skirt_first_layer_height(); + Clone brim_flow(); Clone skirt_flow(); %{ From 9264db7ecd1b433267af81ea1b641d0d80fad1c2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Dec 2014 00:52:01 +0100 Subject: [PATCH 050/107] Removed useless thing added in e79aa2e81c3087d44c46d0fcb65cd81a42f0585d --- lib/Slic3r/Fill.pm | 4 ++-- lib/Slic3r/Layer/Region.pm | 4 ++-- t/fill.t | 2 -- xs/lib/Slic3r/XS.pm | 4 ++-- xs/src/libslic3r/ExtrusionEntityCollection.cpp | 3 +-- xs/src/libslic3r/ExtrusionEntityCollection.hpp | 2 -- xs/t/12_extrusionpathcollection.t | 3 --- xs/xsp/ExtrusionEntityCollection.xsp | 2 +- 8 files changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 4547a846b..5d2f4c31f 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -232,7 +232,7 @@ sub make_fill { my $extrusion_height = $is_bridge ? $flow->width : $h; - push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new($role); + push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new; $collection->no_sort($params->{no_sort}); $collection->append( map Slic3r::ExtrusionPath->new( @@ -250,7 +250,7 @@ sub make_fill { # add thin fill regions foreach my $thin_fill (@{$layerm->thin_fills}) { - push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill->role, $thin_fill); + push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill); push @fills_ordering_points, $thin_fill->first_point; } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index bc09f77ca..06345ac86 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -246,7 +246,7 @@ sub make_perimeters { my ($polynodes, $depth, $is_contour) = @_; # convert all polynodes to ExtrusionLoop objects - my $collection = Slic3r::ExtrusionPath::Collection->new(EXTR_ROLE_PERIMETER); # temporary collection + my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection my @children = (); foreach my $polynode (@$polynodes) { my $polygon = ($polynode->{outer} // $polynode->{hole})->clone; @@ -303,7 +303,7 @@ sub make_perimeters { # (clone because the collection gets DESTROY'ed) # We allow polyline reversal because Clipper may have randomly # reversed polylines during clipping. - my $collection = Slic3r::ExtrusionPath::Collection->new(EXTR_ROLE_PERIMETER, @paths); # temporary collection + my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection @paths = map $_->clone, @{$collection->chained_path(0)}; } else { push @paths, Slic3r::ExtrusionPath->new( diff --git a/t/fill.t b/t/fill.t index 15f693a66..24370b660 100644 --- a/t/fill.t +++ b/t/fill.t @@ -146,7 +146,6 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $collection = Slic3r::ExtrusionPath::Collection->new( - EXTR_ROLE_PERIMETER, map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), @@ -159,7 +158,6 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $collection = Slic3r::ExtrusionPath::Collection->new( - EXTR_ROLE_PERIMETER, map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 08bb625cb..9ac0b35e9 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -64,9 +64,9 @@ use overload 'fallback' => 1; sub new { - my ($class, $type, @paths) = @_; + my ($class, @paths) = @_; - my $self = $class->_new($type); + my $self = $class->_new; $self->append(@paths); return $self; } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index 89748bc86..a958e53cf 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -5,7 +5,7 @@ namespace Slic3r { ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionEntityCollection& collection) - : no_sort(collection.no_sort), role(collection.role), orig_indices(collection.orig_indices) + : no_sort(collection.no_sort), orig_indices(collection.orig_indices) { this->entities.reserve(collection.entities.size()); for (ExtrusionEntitiesPtr::const_iterator it = collection.entities.begin(); it != collection.entities.end(); ++it) @@ -23,7 +23,6 @@ void ExtrusionEntityCollection::swap (ExtrusionEntityCollection &c) { std::swap(this->entities, c.entities); - std::swap(this->role, c.role); std::swap(this->orig_indices, c.orig_indices); std::swap(this->no_sort, c.no_sort); } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index a72563f47..bc660611b 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -13,9 +13,7 @@ class ExtrusionEntityCollection : public ExtrusionEntity ExtrusionEntitiesPtr entities; std::vector orig_indices; // handy for XS bool no_sort; - ExtrusionRole role; ExtrusionEntityCollection(): no_sort(false) {}; - ExtrusionEntityCollection(ExtrusionRole _role): no_sort(false), role(_role) {}; ExtrusionEntityCollection(const ExtrusionEntityCollection &collection); ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other); void swap (ExtrusionEntityCollection &c); diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t index 7349ca941..73608cba7 100644 --- a/xs/t/12_extrusionpathcollection.t +++ b/xs/t/12_extrusionpathcollection.t @@ -27,7 +27,6 @@ my $loop = Slic3r::ExtrusionLoop->new_from_paths( ); my $collection = Slic3r::ExtrusionPath::Collection->new( - Slic3r::ExtrusionPath::EXTR_ROLE_FILL, $path, ); isa_ok $collection, 'Slic3r::ExtrusionPath::Collection', 'collection object with items in constructor'; @@ -58,7 +57,6 @@ is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { my $collection = Slic3r::ExtrusionPath::Collection->new( - Slic3r::ExtrusionPath::EXTR_ROLE_FILL, map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), @@ -75,7 +73,6 @@ is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { my $collection = Slic3r::ExtrusionPath::Collection->new( - Slic3r::ExtrusionPath::EXTR_ROLE_FILL, map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp index 290bcea50..fa1d26eed 100644 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ b/xs/xsp/ExtrusionEntityCollection.xsp @@ -6,7 +6,7 @@ %} %name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { - %name{_new} ExtrusionEntityCollection(ExtrusionRole role); + %name{_new} ExtrusionEntityCollection(); void reverse(); void clear() %code{% THIS->entities.clear(); %}; From 21ea100d0e000dd246195ca72596570ee7506169 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Dec 2014 01:15:47 +0100 Subject: [PATCH 051/107] Fixed tests --- t/support.t | 6 +++--- xs/src/libslic3r/ExtrusionEntity.cpp | 25 +++++++++++++++++++++++++ xs/src/libslic3r/ExtrusionEntity.hpp | 3 +++ xs/xsp/ExtrusionLoop.xsp | 3 +++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/t/support.t b/t/support.t index 0fadc1d14..72749915b 100644 --- a/t/support.t +++ b/t/support.t @@ -67,12 +67,12 @@ use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('raft_layers', 3); - $config->set('brim_width', 6); + $config->set('brim_width', 0); $config->set('skirts', 0); $config->set('support_material_extruder', 2); $config->set('support_material_interface_extruder', 2); $config->set('layer_height', 0.4); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', 0.4); my $print = Slic3r::Test::init_print('overhang', config => $config); ok my $gcode = Slic3r::Test::gcode($print), 'no conflict between raft/support and brim'; @@ -84,7 +84,7 @@ use Slic3r::Test; $tool = $1; } elsif ($info->{extruding}) { if ($self->Z <= ($config->raft_layers * $config->layer_height)) { - fail 'not extruding raft/brim with support material extruder' + fail 'not extruding raft with support material extruder' if $tool != ($config->support_material_extruder-1); } else { fail 'support material exceeds raft layers' diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index 7901317a3..c84b08489 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -341,6 +341,31 @@ ExtrusionLoop::has_overhang_point(const Point &point) const return false; } +bool +ExtrusionLoop::is_perimeter() const +{ + return this->paths.front().role == erPerimeter + || this->paths.front().role == erExternalPerimeter + || this->paths.front().role == erOverhangPerimeter; +} + +bool +ExtrusionLoop::is_infill() const +{ + return this->paths.front().role == erBridgeInfill + || this->paths.front().role == erInternalInfill + || this->paths.front().role == erSolidInfill + || this->paths.front().role == erTopSolidInfill; +} + +bool +ExtrusionLoop::is_solid_infill() const +{ + return this->paths.front().role == erBridgeInfill + || this->paths.front().role == erSolidInfill + || this->paths.front().role == erTopSolidInfill; +} + #ifdef SLIC3RXS REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); #endif diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index da7658b76..65a1aba59 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -97,6 +97,9 @@ class ExtrusionLoop : public ExtrusionEntity void split_at(const Point &point); void clip_end(double distance, ExtrusionPaths* paths) const; bool has_overhang_point(const Point &point) const; + bool is_perimeter() const; + bool is_infill() const; + bool is_solid_infill() const; }; } diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp index 6767c7d3b..2a8d1f9f4 100644 --- a/xs/xsp/ExtrusionLoop.xsp +++ b/xs/xsp/ExtrusionLoop.xsp @@ -28,6 +28,9 @@ %code{% THIS->clip_end(distance, &RETVAL); %}; bool has_overhang_point(Point* point) %code{% RETVAL = THIS->has_overhang_point(*point); %}; + bool is_perimeter(); + bool is_infill(); + bool is_solid_infill(); %{ SV* From a0dda36df029c47202895d5e6fb791b8445e3f60 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Dec 2014 01:21:12 +0100 Subject: [PATCH 052/107] Minor fix to BridgeDetector --- lib/Slic3r/Layer/Region.pm | 3 ++- xs/src/libslic3r/Layer.hpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 06345ac86..6ce755b7b 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -496,7 +496,8 @@ sub process_external_surfaces { $angle = $bridge_detector->angle; if (defined $angle && $self->object->config->support_material) { - $self->bridged->append($_) for @{ $bridge_detector->coverage_by_angle($angle) }; + $self->bridged->append(Slic3r::ExPolygon->new($_)) + for @{ $bridge_detector->coverage_by_angle($angle) }; $self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges }; } } diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 115a728e0..bd87ab0cb 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -40,6 +40,7 @@ class LayerRegion // collection of expolygons representing the bridged areas (thus not // needing support material) + // (this could be just a Polygons object) ExPolygonCollection bridged; // collection of polylines representing the unsupported bridge edges From 34a49086e50bd1412be89c87e05b7802d5c26717 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Dec 2014 14:28:25 +0100 Subject: [PATCH 053/107] Added code for potential antialiasing --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index d766803bb..9daefa7bd 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -212,6 +212,14 @@ sub Render { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); + # anti-alias + if (0) { + glEnable(GL_LINE_SMOOTH); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE); + glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE); + } + my $tess = gluNewTess(); gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_END, 'DEFAULT'); From 38ecce0ce9a6c5aa5db82e4fa30b6d7b1362d6df Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Dec 2014 14:41:58 +0100 Subject: [PATCH 054/107] Disable the GLU tesselator on MSW --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 36 +++++++++++++++++----------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 9daefa7bd..2faf59fa0 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -220,13 +220,18 @@ sub Render { glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE); } - my $tess = gluNewTess(); - gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT'); - gluTessCallback($tess, GLU_TESS_END, 'DEFAULT'); - gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT'); - gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT'); - gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT'); - gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT'); + my $tess; + if (!&Wx::wxMSW) { + # We can't use the GLU tesselator on MSW because of an upstream bug: + # http://sourceforge.net/p/pogl/bugs/16/ + $tess = gluNewTess(); + gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_END, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT'); + gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT'); + } my $skirt_drawn = 0; my $brim_drawn = 0; @@ -243,13 +248,16 @@ sub Render { foreach my $slice (@{$layer->slices}) { glColor3f(0.95, 0.95, 0.95); - gluTessBeginPolygon($tess); - foreach my $polygon (@$slice) { - gluTessBeginContour($tess); - gluTessVertex_p($tess, @$_, 0) for @$polygon; - gluTessEndContour($tess); + + if ($tess) { + gluTessBeginPolygon($tess); + foreach my $polygon (@$slice) { + gluTessBeginContour($tess); + gluTessVertex_p($tess, @$_, 0) for @$polygon; + gluTessEndContour($tess); + } + gluTessEndPolygon($tess); } - gluTessEndPolygon($tess); glColor3f(0.9, 0.9, 0.9); foreach my $polygon (@$slice) { @@ -300,7 +308,7 @@ sub Render { } } - gluDeleteTess($tess); + gluDeleteTess($tess) if $tess; glFlush(); $self->SwapBuffers; } From 632e3d30677aae5d40193e4016495525b3b09b3b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 17 Dec 2014 14:53:36 +0100 Subject: [PATCH 055/107] Added an explicit warning when user has old threads.pm version. #2348 --- lib/Slic3r.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 48cd733f9..9a003521b 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -19,6 +19,7 @@ our $have_threads; BEGIN { use Config; $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; + warn "threads.pm >= 1.96 is required, please update\n" if $have_threads && $threads::VERSION < 1.96; ### temporarily disable threads if using the broken Moo version use Moo; From 79ac29b435d95cb6185db941399b2221f2733b97 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 20 Dec 2014 22:40:34 +0100 Subject: [PATCH 056/107] Increase inset overlap tolerance --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9a003521b..f1e8f2235 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -80,7 +80,7 @@ use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.45; use constant EXTERNAL_INFILL_MARGIN => 3; -use constant INSET_OVERLAP_TOLERANCE => 0.2; +use constant INSET_OVERLAP_TOLERANCE => 0.4; # keep track of threads we created my @my_threads = (); From 795c85d30e431274c95edef20552c7ee61cfa4c5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 20 Dec 2014 22:40:43 +0100 Subject: [PATCH 057/107] Apply a true double-ended nearest-neightbor search to thin fills in order to minimize travel moves between them. #2213 --- lib/Slic3r/Fill.pm | 7 ------- lib/Slic3r/Print/GCode.pm | 44 +++++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 5d2f4c31f..97c49419c 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -179,7 +179,6 @@ sub make_fill { } my @fills = (); - my @fills_ordering_points = (); SURFACE: foreach my $surface (@surfaces) { next if $surface->surface_type == S_TYPE_INTERNALVOID; my $filler = $layerm->config->fill_pattern; @@ -244,19 +243,13 @@ sub make_fill { ), @polylines, ); } - - push @fills_ordering_points, $polylines[0]->first_point; } # add thin fill regions foreach my $thin_fill (@{$layerm->thin_fills}) { push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill); - push @fills_ordering_points, $thin_fill->first_point; } - # organize infill paths using a nearest-neighbor search - @fills = @fills[ @{chained_path(\@fills_ordering_points)} ]; - return @fills; } diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index daa665bfd..dae9eb3a7 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -417,24 +417,26 @@ sub process_layer { } # process infill - { - foreach my $fill (@{$layerm->fills}) { - # init by_extruder item only if we actually use the extruder - my $extruder_id = $fill->[0]->is_solid_infill - ? $region->config->solid_infill_extruder-1 - : $region->config->infill_extruder-1; - - $by_extruder{$extruder_id} //= []; - - # $fill is an ExtrusionPath::Collection object - for my $i (0 .. $#{$layer->slices}) { - if ($i == $#{$layer->slices} - || $layer->slices->[$i]->contour->contains_point($fill->first_point)) { - $by_extruder{$extruder_id}[$i] //= { infill => {} }; - $by_extruder{$extruder_id}[$i]{infill}{$region_id} //= []; - push @{ $by_extruder{$extruder_id}[$i]{infill}{$region_id} }, $fill; - last; - } + # $layerm->fills is a collection of ExtrusionPath::Collection objects, each one containing + # the ExtrusionPath objects of a certain infill "group" (also called "surface" + # throughout the code). We can redefine the order of such Collections but we have to + # do each one completely at once. + foreach my $fill (@{$layerm->fills}) { + # init by_extruder item only if we actually use the extruder + my $extruder_id = $fill->[0]->is_solid_infill + ? $region->config->solid_infill_extruder-1 + : $region->config->infill_extruder-1; + + $by_extruder{$extruder_id} //= []; + + # $fill is an ExtrusionPath::Collection object + for my $i (0 .. $#{$layer->slices}) { + if ($i == $#{$layer->slices} + || $layer->slices->[$i]->contour->contains_point($fill->first_point)) { + $by_extruder{$extruder_id}[$i] //= { infill => {} }; + $by_extruder{$extruder_id}[$i]{infill}{$region_id} //= []; + push @{ $by_extruder{$extruder_id}[$i]{infill}{$region_id} }, $fill; + last; } } } @@ -479,7 +481,7 @@ sub process_layer { $layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers $layer->id, $layer->print_z, - ) if defined $self->_cooling_buffer && defined $layer; + ) if defined $self->_cooling_buffer; print {$self->fh} $self->filter($gcode); } @@ -502,7 +504,9 @@ sub _extrude_infill { my $gcode = ""; foreach my $region_id (sort keys %$entities_by_region) { $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config); - for my $fill (@{ $entities_by_region->{$region_id} }) { + + my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} }); + for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) { if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { $gcode .= $self->_gcodegen->extrude($_, 'infill') for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)}; From 9ac60dca1a452aed7769ff48bb3f6f940c8fe8dc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 21 Dec 2014 12:39:19 +0100 Subject: [PATCH 058/107] Fixed vibration limit. --- lib/Slic3r/GCode/Reader.pm | 2 +- lib/Slic3r/GCode/VibrationLimit.pm | 7 +++---- t/vibrationlimit.t | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm index cdcaeeeb1..bd82fc5b1 100644 --- a/lib/Slic3r/GCode/Reader.pm +++ b/lib/Slic3r/GCode/Reader.pm @@ -58,7 +58,7 @@ sub parse { $info{"new_$axis"} = $self->$axis; } } - $info{dist_XY} = Slic3r::Geometry::unscale(Slic3r::Line->new_scale([0,0], [@info{qw(dist_X dist_Y)}])->length); + $info{dist_XY} = sqrt(($info{dist_X}**2) + ($info{dist_Y}**2)); if (exists $args{E}) { if ($info{dist_E} > 0) { $info{extruding} = 1; diff --git a/lib/Slic3r/GCode/VibrationLimit.pm b/lib/Slic3r/GCode/VibrationLimit.pm index 0680a3b82..496d1e73a 100644 --- a/lib/Slic3r/GCode/VibrationLimit.pm +++ b/lib/Slic3r/GCode/VibrationLimit.pm @@ -3,7 +3,6 @@ use Moo; extends 'Slic3r::GCode::Reader'; -has 'config' => (is => 'ro', required => 1); has '_min_time' => (is => 'lazy'); has '_last_dir' => (is => 'ro', default => sub { [0,0] }); has '_dir_time' => (is => 'ro', default => sub { [0,0] }); @@ -11,7 +10,6 @@ has '_dir_time' => (is => 'ro', default => sub { [0,0] }); # inspired by http://hydraraptor.blogspot.it/2010/12/frequency-limit.html use List::Util qw(max); -use Slic3r::Geometry qw(X Y); sub _build__min_time { my ($self) = @_; @@ -27,15 +25,16 @@ sub process { my ($reader, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{dist_XY} > 0) { - my $point = Slic3r::Point->new($args->{X} // $reader->X, $args->{Y} // $reader->Y); + my $point = Slic3r::Pointf->new($args->{X} // $reader->X, $args->{Y} // $reader->Y); my @dir = ( ($point->x <=> $reader->X), ($point->y <=> $reader->Y), #$ ); my $time = $info->{dist_XY} / ($args->{F} // $reader->F); # in minutes + if ($time > 0) { my @pause = (); - foreach my $axis (X,Y) { + foreach my $axis (0..$#dir) { if ($dir[$axis] != 0 && $self->_last_dir->[$axis] != $dir[$axis]) { if ($self->_last_dir->[$axis] != 0) { # this axis is changing direction: check whether we need to pause diff --git a/t/vibrationlimit.t b/t/vibrationlimit.t index 2553e2260..882769c02 100644 --- a/t/vibrationlimit.t +++ b/t/vibrationlimit.t @@ -15,7 +15,7 @@ my $config = Slic3r::Config->new_from_defaults; # tolerance, in minutes # (our time estimation differs from the internal one because of decimals truncation) -my $epsilon = 0.002; +my $epsilon = 0.003; my $test = sub { my ($conf) = @_; From 5b51832b622238cd332de77f26799e1afab6f51a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 28 Oct 2014 21:49:01 +0100 Subject: [PATCH 059/107] Wireframe.pl wireframe --- utils/wireframe.pl | 102 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 utils/wireframe.pl diff --git a/utils/wireframe.pl b/utils/wireframe.pl new file mode 100644 index 000000000..931897563 --- /dev/null +++ b/utils/wireframe.pl @@ -0,0 +1,102 @@ +#!/usr/bin/perl +# This script exports experimental G-code for wireframe printing +# (inspired by the brilliant WirePrint concept) + +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Getopt::Long qw(:config no_auto_abbrev); +use PDF::API2; +use Slic3r; +use Slic3r::ExtrusionPath ':roles'; +use Slic3r::Geometry qw(scale unscale X Y); + +my %opt = ( + step_height => 10, +); +{ + my %options = ( + 'help' => sub { usage() }, + 'output|o=s' => \$opt{output_file}, + 'step-height|h=f' => \$opt{step_height}, + 'nozzle-angle|a=f' => \$opt{nozzle_angle}, + ); + GetOptions(%options) or usage(1); + $opt{output_file} or usage(1); + ### Input file is not needed until we use hard-coded geometry: + ### $ARGV[0] or usage(1); +} + +{ + my $flow = Slic3r::Flow->new( + width => 0.35, + height => 0.35, + nozzle_diameter => 0.35, + bridge => 1, + ); + + my $section; + + # build a square section + { + my $dist = 2 * $opt{step_height}; # horizontal step + my $side_modules = 3; + my @points = ( + [0,0], + (map [$_*$dist, 0], 1..$side_modules), + (map [$side_modules*$dist, $_*$dist], 1..$side_modules), + (map [($_-1)*$dist, $side_modules*$dist], reverse 1..$side_modules), + (map [0, ($_-1)*$dist], reverse 1..$side_modules), + ); + pop @points; # prevent coinciding endpoints + $section = Slic3r::Polygon->new_scale(@points); + } + my $section_loop = Slic3r::ExtrusionLoop->new_from_paths( + Slic3r::ExtrusionPath->new( + polyline => $section->split_at_first_point, + role => EXTR_ROLE_BRIDGE, + mm3_per_mm => $flow->mm3_per_mm, + width => $flow->width, + height => $flow->height, + ) + ); + + my $vertical_steps = 3; + + open my $fh, '>', $opt{output_file}; + my $gcodegen = Slic3r::GCode->new( + enable_loop_clipping => 0, # better bonding + ); + $gcodegen->set_extruders([0]); + print $fh $gcodegen->set_extruder(0); + print $fh $gcodegen->writer->preamble; + + foreach my $z (map $_*$opt{step_height}, 0..($vertical_steps-1)) { + print $fh $gcodegen->writer->travel_to_z($z + $flow->height); + print $fh $gcodegen->extrude_loop($section_loop, "contour"); + } + + close $fh; +} + +sub usage { + my ($exit_code) = @_; + + print <<"EOF"; +Usage: wireframe.pl [ OPTIONS ] file.stl + + --help Output this usage screen and exit + --output, -o Write to the specified file + --step-height, -h Use the specified step height + --nozzle-angle, -a Max nozzle angle + +EOF + exit ($exit_code || 0); +} + +__END__ From c21a254480b992812e24e08564b7f1597de71e83 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 21 Dec 2014 14:51:50 +0100 Subject: [PATCH 060/107] Incomplete work for wireframe --- utils/wireframe.pl | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/utils/wireframe.pl b/utils/wireframe.pl index 931897563..ad5a27753 100644 --- a/utils/wireframe.pl +++ b/utils/wireframe.pl @@ -18,6 +18,7 @@ use Slic3r::Geometry qw(scale unscale X Y); my %opt = ( step_height => 10, + first_layer_height => 0.3, ); { my %options = ( @@ -25,14 +26,27 @@ my %opt = ( 'output|o=s' => \$opt{output_file}, 'step-height|h=f' => \$opt{step_height}, 'nozzle-angle|a=f' => \$opt{nozzle_angle}, + 'nozzle-width|w=f' => \$opt{nozzle_width}, + 'first-layer-height=f' => \$opt{first_layer_height}, ); GetOptions(%options) or usage(1); - $opt{output_file} or usage(1); - ### Input file is not needed until we use hard-coded geometry: - ### $ARGV[0] or usage(1); + $opt{output_file} or usage(1);ì + $ARGV[0] or usage(1); } { + # load model + my $model = Slic3r::Model->read_from_file($ARGV[0]); + my $mesh = $model->mesh; + $mesh->translate(0, 0, -$mesh->bounding_box->z_min); + + # get slices + my @z = (); + my $z_max = $mesh->bounding_box->z_max; + for (my $z = $opt{first_layer_height}; $z <= $z_max; $z += $opt{step_height}) { + push @z, $z; + } + my $flow = Slic3r::Flow->new( width => 0.35, height => 0.35, @@ -94,6 +108,7 @@ Usage: wireframe.pl [ OPTIONS ] file.stl --output, -o Write to the specified file --step-height, -h Use the specified step height --nozzle-angle, -a Max nozzle angle + --nozzle-width, -w External nozzle diameter EOF exit ($exit_code || 0); From 7253dc699af4ec91c060e44a2ad3d46f5b261190 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 21 Dec 2014 22:51:45 +0100 Subject: [PATCH 061/107] Some more work on wireframe --- lib/Slic3r/GCode.pm | 2 +- utils/wireframe.pl | 125 ++++++++++++++++++++++--------- xs/src/libslic3r/GCodeWriter.cpp | 6 ++ xs/src/libslic3r/GCodeWriter.hpp | 1 + xs/src/libslic3r/Point.cpp | 7 +- xs/xsp/GCodeWriter.xsp | 1 + xs/xsp/Point.xsp | 2 + 7 files changed, 107 insertions(+), 37 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 9dc4e29bd..5ebca1db9 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -344,7 +344,7 @@ sub travel_to { if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id) || ($self->config->only_retract_when_crossing_perimeters && $self->config->fill_density > 0 - && $self->layer->any_internal_region_slice_contains_line($travel)) + && defined($self->layer) && $self->layer->any_internal_region_slice_contains_line($travel)) || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel)) ) { # Just perform a straight travel move without any retraction. diff --git a/utils/wireframe.pl b/utils/wireframe.pl index ad5a27753..053581dec 100644 --- a/utils/wireframe.pl +++ b/utils/wireframe.pl @@ -11,14 +11,15 @@ BEGIN { } use Getopt::Long qw(:config no_auto_abbrev); -use PDF::API2; use Slic3r; use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale X Y); +use Slic3r::Geometry qw(scale unscale X Y PI); my %opt = ( - step_height => 10, - first_layer_height => 0.3, + step_height => 5, + nozzle_angle => 30, + nozzle_width => 10, + first_layer_height => 0.3, ); { my %options = ( @@ -30,13 +31,15 @@ my %opt = ( 'first-layer-height=f' => \$opt{first_layer_height}, ); GetOptions(%options) or usage(1); - $opt{output_file} or usage(1);ì + $opt{output_file} or usage(1); $ARGV[0] or usage(1); } { # load model my $model = Slic3r::Model->read_from_file($ARGV[0]); + $model->add_default_instances; + $model->center_instances_around_point(Slic3r::Pointf->new(100,100)); my $mesh = $model->mesh; $mesh->translate(0, 0, -$mesh->bounding_box->z_min); @@ -46,6 +49,7 @@ my %opt = ( for (my $z = $opt{first_layer_height}; $z <= $z_max; $z += $opt{step_height}) { push @z, $z; } + my @slices = @{$mesh->slice(\@z)}; my $flow = Slic3r::Flow->new( width => 0.35, @@ -54,45 +58,96 @@ my %opt = ( bridge => 1, ); - my $section; - - # build a square section - { - my $dist = 2 * $opt{step_height}; # horizontal step - my $side_modules = 3; - my @points = ( - [0,0], - (map [$_*$dist, 0], 1..$side_modules), - (map [$side_modules*$dist, $_*$dist], 1..$side_modules), - (map [($_-1)*$dist, $side_modules*$dist], reverse 1..$side_modules), - (map [0, ($_-1)*$dist], reverse 1..$side_modules), - ); - pop @points; # prevent coinciding endpoints - $section = Slic3r::Polygon->new_scale(@points); - } - my $section_loop = Slic3r::ExtrusionLoop->new_from_paths( - Slic3r::ExtrusionPath->new( - polyline => $section->split_at_first_point, - role => EXTR_ROLE_BRIDGE, - mm3_per_mm => $flow->mm3_per_mm, - width => $flow->width, - height => $flow->height, - ) - ); - - my $vertical_steps = 3; + my $config = Slic3r::Config::Print->new; + $config->set('gcode_comments', 1); open my $fh, '>', $opt{output_file}; my $gcodegen = Slic3r::GCode->new( enable_loop_clipping => 0, # better bonding ); + $gcodegen->apply_print_config($config); $gcodegen->set_extruders([0]); print $fh $gcodegen->set_extruder(0); print $fh $gcodegen->writer->preamble; - foreach my $z (map $_*$opt{step_height}, 0..($vertical_steps-1)) { - print $fh $gcodegen->writer->travel_to_z($z + $flow->height); - print $fh $gcodegen->extrude_loop($section_loop, "contour"); + my $e = $gcodegen->writer->extruder->e_per_mm3 * $flow->mm3_per_mm; + + foreach my $layer_id (0..$#z) { + my $z = $z[$layer_id]; + + foreach my $island (@{$slices[$layer_id]}) { + foreach my $polygon (@$island) { + if ($layer_id > 0) { + # find the lower polygon that we want to connect to this one + my $lower = $slices[$layer_id-1]->[0]->contour; # 't was easy, wasn't it? + my $lower_z = $z[$layer_id-1]; + + { + my @points = (); + + # keep all points with strong angles + { + my @pp = @$polygon; + foreach my $i (0..$#pp) { + push @points, $pp[$i-1] if abs($pp[$i-1]->ccw_angle($pp[$i-2], $pp[$i]) - PI) > PI/3; + } + } + + $polygon = Slic3r::Polygon->new(@points); + } + #$polygon = Slic3r::Polygon->new(@{$polygon->split_at_first_point->equally_spaced_points(scale $opt{nozzle_width})}); + + # find vertical lines + my @vertical = (); + foreach my $point (@{$polygon}) { + push @vertical, Slic3r::Line->new($point->projection_onto_polygon($lower), $point); + } + + next if !@vertical; + + my @points = (); + foreach my $line (@vertical) { + push @points, Slic3r::Pointf3->new( + unscale($line->a->x), + unscale($line->a->y), #)) + $lower_z, + ); + push @points, Slic3r::Pointf3->new( + unscale($line->b->x), + unscale($line->b->y), #)) + $z, + ); + } + + # reappend first point as destination of the last diagonal segment + push @points, Slic3r::Pointf3->new( + unscale($vertical[0]->a->x), + unscale($vertical[0]->a->y), #)) + $lower_z, + ); + + # move to the position of the first vertical line + print $fh $gcodegen->writer->travel_to_xyz(shift @points); + + # extrude segments + foreach my $point (@points) { + print $fh $gcodegen->writer->extrude_to_xyz($point, $e * $gcodegen->writer->get_position->distance_to($point)); + } + } + } + + print $fh $gcodegen->writer->travel_to_z($z); + foreach my $polygon (@$island) { + #my $polyline = $polygon->split_at_vertex(Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1])); + my $polyline = $polygon->split_at_first_point; + print $fh $gcodegen->writer->travel_to_xy(Slic3r::Pointf->new_unscale(@{ $polyline->first_point }), "move to first contour point"); + + foreach my $line (@{$polyline->lines}) { + my $point = Slic3r::Pointf->new_unscale(@{ $line->b }); + print $fh $gcodegen->writer->extrude_to_xy($point, $e * unscale($line->length)); + } + } + } } close $fh; diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp index c36ca4f45..bb8035ac3 100644 --- a/xs/src/libslic3r/GCodeWriter.cpp +++ b/xs/src/libslic3r/GCodeWriter.cpp @@ -491,6 +491,12 @@ GCodeWriter::unlift() return gcode; } +Pointf3 +GCodeWriter::get_position() const +{ + return this->_pos; +} + #ifdef SLIC3RXS REGISTER_CLASS(GCodeWriter, "GCode::Writer"); #endif diff --git a/xs/src/libslic3r/GCodeWriter.hpp b/xs/src/libslic3r/GCodeWriter.hpp index 5500a19f5..03de197d1 100644 --- a/xs/src/libslic3r/GCodeWriter.hpp +++ b/xs/src/libslic3r/GCodeWriter.hpp @@ -45,6 +45,7 @@ class GCodeWriter { std::string unretract(); std::string lift(); std::string unlift(); + Pointf3 get_position() const; private: std::string _extrusion_axis; diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 4eee03eff..77f4224d5 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -161,10 +161,15 @@ Point::ccw(const Line &line) const } // returns the CCW angle between this-p1 and this-p2 +// i.e. this assumes a CCW rotation from p1 to p2 around this double Point::ccw_angle(const Point &p1, const Point &p2) const { - return Line(*this, p1).orientation() - Line(*this, p2).orientation(); + double angle = atan2(p1.x - this->x, p1.y - this->y) + - atan2(p2.x - this->x, p2.y - this->y); + + // we only want to return only positive angles + return angle <= 0 ? angle + 2*PI : angle; } Point diff --git a/xs/xsp/GCodeWriter.xsp b/xs/xsp/GCodeWriter.xsp index ee9049e56..a76c0e55f 100644 --- a/xs/xsp/GCodeWriter.xsp +++ b/xs/xsp/GCodeWriter.xsp @@ -44,6 +44,7 @@ std::string unretract(); std::string lift(); std::string unlift(); + Clone get_position() const; %{ SV* diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 3b244fe55..e45c41213 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -31,6 +31,8 @@ %code{% RETVAL = THIS->distance_to(*line); %}; double ccw(Point* p1, Point* p2) %code{% RETVAL = THIS->ccw(*p1, *p2); %}; + double ccw_angle(Point* p1, Point* p2) + %code{% RETVAL = THIS->ccw_angle(*p1, *p2); %}; Clone projection_onto_polygon(Polygon* polygon) %code{% RETVAL = new Point(THIS->projection_onto(*polygon)); %}; Clone projection_onto_polyline(Polyline* polyline) From 4fc955a0fd75675603453ce2de442dee928a6ef0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 21 Dec 2014 23:10:38 +0100 Subject: [PATCH 062/107] Fixed concave_points() and convex_points() after recent change of ccw_angle() --- xs/src/libslic3r/Polygon.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 36c644243..4c73de092 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -226,20 +226,23 @@ Polygon::wkt() const return wkt.str(); } +// find all concave vertices (i.e. having an internal angle greater than the supplied angle) */ void Polygon::concave_points(double angle, Points* points) const { + angle = 2*PI - angle; + // check whether first point forms a concave angle - if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle) + if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle) points->push_back(this->points.front()); // check whether points 1..(n-1) form concave angles for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { - if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points->push_back(*p); + if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points->push_back(*p); } // check whether last point forms a concave angle - if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle) + if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle) points->push_back(this->points.back()); } @@ -249,20 +252,23 @@ Polygon::concave_points(Points* points) const this->concave_points(PI, points); } +// find all convex vertices (i.e. having an internal angle smaller than the supplied angle) */ void Polygon::convex_points(double angle, Points* points) const { + angle = 2*PI - angle; + // check whether first point forms a convex angle - if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle) + if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle) points->push_back(this->points.front()); // check whether points 1..(n-1) form convex angles for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { - if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points->push_back(*p); + if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points->push_back(*p); } // check whether last point forms a convex angle - if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle) + if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle) points->push_back(this->points.back()); } From 583b009d1b29b7053f95b48b7facb0930fbe2962 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 21 Dec 2014 23:29:31 +0100 Subject: [PATCH 063/107] Honor print bed center in Quick Slice. #2440 --- lib/Slic3r/GUI/MainFrame.pm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index cb3426f80..2d92f2c52 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -319,7 +319,14 @@ sub quick_slice { $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); wxTheApp->save_settings; + my $print_center; + { + my $bed_shape = Slic3r::Polygon->new_scale(@{$config->bed_shape}); + $print_center = Slic3r::Pointf->new_unscale(@{$bed_shape->bounding_box->center}); + } + my $sprint = Slic3r::Print::Simple->new( + print_center => $print_center, status_cb => sub { my ($percent, $message) = @_; return if &Wx::wxVERSION_STRING !~ / 2\.(8\.|9\.[2-9])/; From 14856597265d72e07a86acc66f28c471ea4c8c21 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 21 Dec 2014 23:40:05 +0100 Subject: [PATCH 064/107] Minor rewording. #2411 --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 86d070cd0..0b7d0ec3a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -804,7 +804,7 @@ sub split_object { my @model_objects = @{$current_model_object->split_object}; if (@model_objects == 1) { - Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it already contains a single part."); + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains only one part."); return; } From 74f25ef52fc3a15eb84ee37a397cfdc873f28a6e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 21 Dec 2014 23:43:53 +0100 Subject: [PATCH 065/107] Consolidated all PlanePath classes in a single file --- lib/Slic3r/Fill.pm | 4 --- lib/Slic3r/Fill/ArchimedeanChords.pm | 7 ----- lib/Slic3r/Fill/Flowsnake.pm | 18 ------------- lib/Slic3r/Fill/HilbertCurve.pm | 7 ----- lib/Slic3r/Fill/OctagramSpiral.pm | 9 ------- lib/Slic3r/Fill/PlanePath.pm | 38 ++++++++++++++++++++++++++++ 6 files changed, 38 insertions(+), 45 deletions(-) delete mode 100644 lib/Slic3r/Fill/ArchimedeanChords.pm delete mode 100644 lib/Slic3r/Fill/Flowsnake.pm delete mode 100644 lib/Slic3r/Fill/HilbertCurve.pm delete mode 100644 lib/Slic3r/Fill/OctagramSpiral.pm diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 97c49419c..780717586 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -3,14 +3,10 @@ use Moo; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Fill::3DHoneycomb; -use Slic3r::Fill::ArchimedeanChords; use Slic3r::Fill::Base; use Slic3r::Fill::Concentric; -use Slic3r::Fill::Flowsnake; -use Slic3r::Fill::HilbertCurve; use Slic3r::Fill::Honeycomb; use Slic3r::Fill::Line; -use Slic3r::Fill::OctagramSpiral; use Slic3r::Fill::PlanePath; use Slic3r::Fill::Rectilinear; use Slic3r::Flow ':roles'; diff --git a/lib/Slic3r/Fill/ArchimedeanChords.pm b/lib/Slic3r/Fill/ArchimedeanChords.pm deleted file mode 100644 index 3c71f0e36..000000000 --- a/lib/Slic3r/Fill/ArchimedeanChords.pm +++ /dev/null @@ -1,7 +0,0 @@ -package Slic3r::Fill::ArchimedeanChords; -use Moo; - -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::ArchimedeanChords; - -1; diff --git a/lib/Slic3r/Fill/Flowsnake.pm b/lib/Slic3r/Fill/Flowsnake.pm deleted file mode 100644 index c852b81c7..000000000 --- a/lib/Slic3r/Fill/Flowsnake.pm +++ /dev/null @@ -1,18 +0,0 @@ -package Slic3r::Fill::Flowsnake; -use Moo; - -extends 'Slic3r::Fill::PlanePath'; - -use Math::PlanePath::Flowsnake; -use Slic3r::Geometry qw(X); - -# Sorry, this fill is currently broken. - -sub process_polyline { - my $self = shift; - my ($polyline, $bounding_box) = @_; - - $_->[X] += $bounding_box->center->[X] for @$polyline; -} - -1; diff --git a/lib/Slic3r/Fill/HilbertCurve.pm b/lib/Slic3r/Fill/HilbertCurve.pm deleted file mode 100644 index eff1a44ac..000000000 --- a/lib/Slic3r/Fill/HilbertCurve.pm +++ /dev/null @@ -1,7 +0,0 @@ -package Slic3r::Fill::HilbertCurve; -use Moo; - -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::HilbertCurve; - -1; diff --git a/lib/Slic3r/Fill/OctagramSpiral.pm b/lib/Slic3r/Fill/OctagramSpiral.pm deleted file mode 100644 index 9c1272444..000000000 --- a/lib/Slic3r/Fill/OctagramSpiral.pm +++ /dev/null @@ -1,9 +0,0 @@ -package Slic3r::Fill::OctagramSpiral; -use Moo; - -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::OctagramSpiral; - -sub multiplier () { sqrt(2) } - -1; diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm index dd942aba9..bf61a70e0 100644 --- a/lib/Slic3r/Fill/PlanePath.pm +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -59,4 +59,42 @@ sub fill_surface { return { flow => $flow }, @paths; } + +package Slic3r::Fill::ArchimedeanChords; +use Moo; +extends 'Slic3r::Fill::PlanePath'; +use Math::PlanePath::ArchimedeanChords; + + +package Slic3r::Fill::Flowsnake; +use Moo; +extends 'Slic3r::Fill::PlanePath'; +use Math::PlanePath::Flowsnake; +use Slic3r::Geometry qw(X); + +# Sorry, this fill is currently broken. + +sub process_polyline { + my $self = shift; + my ($polyline, $bounding_box) = @_; + + $_->[X] += $bounding_box->center->[X] for @$polyline; +} + + +package Slic3r::Fill::HilbertCurve; +use Moo; +extends 'Slic3r::Fill::PlanePath'; +use Math::PlanePath::HilbertCurve; + + +package Slic3r::Fill::OctagramSpiral; +use Moo; +extends 'Slic3r::Fill::PlanePath'; +use Math::PlanePath::OctagramSpiral; + +sub multiplier () { sqrt(2) } + + + 1; From 5cd3ef7b8a1b0cf6dff5ebacc5880e60fea9ed7b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 00:18:45 +0100 Subject: [PATCH 066/107] Bugfix: PlanePath infills sometimes did not cover the whole area. #863 #1162 --- lib/Slic3r/Fill/PlanePath.pm | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm index bf61a70e0..5b4b5a6ad 100644 --- a/lib/Slic3r/Fill/PlanePath.pm +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -9,14 +9,6 @@ use Slic3r::Geometry::Clipper qw(intersection_pl); sub multiplier () { 1 } -sub get_n { - my $self = shift; - my ($path, $bounding_box) = @_; - - my ($n_lo, $n_hi) = $path->rect_to_n_range(@$bounding_box); - return ($n_lo .. $n_hi); -} - sub process_polyline {} sub fill_surface { @@ -32,12 +24,21 @@ sub fill_surface { my $distance_between_lines = $flow->scaled_spacing / $params{density} * $self->multiplier; my $bounding_box = $expolygon->bounding_box; + # since not all PlanePath infills extend in negative coordinate space, + # move expolygon in positive coordinate space + $expolygon->translate(-$bounding_box->x_min, -$bounding_box->y_min); + (ref $self) =~ /::([^:]+)$/; my $path = "Math::PlanePath::$1"->new; - my @n = $self->get_n($path, [ map +($_ / $distance_between_lines), @{$bounding_box->min_point}, @{$bounding_box->max_point} ]); + + my ($n_lo, $n_hi) = $path->rect_to_n_range( + map { $_ / $distance_between_lines } + 0, 0, + @{$bounding_box->size}, + ); my $polyline = Slic3r::Polyline->new( - map [ map {$_*$distance_between_lines} $path->n_to_xy($_) ], @n, + map [ map { $_ * $distance_between_lines } $path->n_to_xy($_) ], ($n_lo..$n_hi) ); return {} if @$polyline <= 1; @@ -48,12 +49,14 @@ sub fill_surface { if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("fill.svg", - polygons => $expolygon, - polylines => [map $_->p, @paths], + no_arrows => 1, + polygons => \@$expolygon, + polylines => \@paths, ); } - # paths must be rotated back + # paths must be repositioned and rotated back + $_->translate($bounding_box->x_min, $bounding_box->y_min) for @paths; $self->rotate_points_back(\@paths, $rotate_vector); return { flow => $flow }, @paths; From bdce1ded7e3b3f5cd6f3406f2368f05d520a4877 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 10:47:09 +0100 Subject: [PATCH 067/107] Disable cross-hatching for hilbertcurve, archimedeanchords, octagramspiral --- lib/Slic3r/Fill/PlanePath.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm index 5b4b5a6ad..036d2d781 100644 --- a/lib/Slic3r/Fill/PlanePath.pm +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -7,6 +7,7 @@ with qw(Slic3r::Fill::WithDirection); use Slic3r::Geometry qw(scale X1 Y1 X2 Y2); use Slic3r::Geometry::Clipper qw(intersection_pl); +sub angles () { [0] } sub multiplier () { 1 } sub process_polyline {} From 1b582ea66a30d74262876651dd67f63b9dbf2642 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 10:48:05 +0100 Subject: [PATCH 068/107] Remove the "(slow)" mark on infill patterns that used to be slow in the past --- xs/src/libslic3r/PrintConfig.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index c0ebb9bd5..f50269909 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -298,9 +298,9 @@ PrintConfigDef::build_def() { Options["fill_pattern"].enum_labels.push_back("concentric"); Options["fill_pattern"].enum_labels.push_back("honeycomb"); Options["fill_pattern"].enum_labels.push_back("3D honeycomb"); - Options["fill_pattern"].enum_labels.push_back("hilbertcurve (slow)"); - Options["fill_pattern"].enum_labels.push_back("archimedeanchords (slow)"); - Options["fill_pattern"].enum_labels.push_back("octagramspiral (slow)"); + Options["fill_pattern"].enum_labels.push_back("hilbertcurve"); + Options["fill_pattern"].enum_labels.push_back("archimedeanchords"); + Options["fill_pattern"].enum_labels.push_back("octagramspiral"); Options["first_layer_acceleration"].type = coFloat; Options["first_layer_acceleration"].label = "First layer"; From 45cc204f74fe47e24143d8fa4ba16450f5ec3c7b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 11:16:01 +0100 Subject: [PATCH 069/107] Center hilbertcurve, archimedeanchords, octagramspiral around object's center and align them across layers --- lib/Slic3r/Fill/PlanePath.pm | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm index 036d2d781..5d46937c0 100644 --- a/lib/Slic3r/Fill/PlanePath.pm +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -23,19 +23,32 @@ sub fill_surface { my $flow = $params{flow}; my $distance_between_lines = $flow->scaled_spacing / $params{density} * $self->multiplier; - my $bounding_box = $expolygon->bounding_box; - # since not all PlanePath infills extend in negative coordinate space, - # move expolygon in positive coordinate space - $expolygon->translate(-$bounding_box->x_min, -$bounding_box->y_min); + # align infill across layers using the object's bounding box + my $bb_polygon = $self->bounding_box->polygon; + $self->rotate_points($bb_polygon, $rotate_vector); + my $bounding_box = $bb_polygon->bounding_box; (ref $self) =~ /::([^:]+)$/; my $path = "Math::PlanePath::$1"->new; + my $translate = Slic3r::Point->new(0,0); # vector + if ($path->x_negative || $path->y_negative) { + # if the curve extends on both positive and negative coordinate space, + # center our expolygon around origin + $translate = $bounding_box->center->negative; + } else { + # if the curve does not extend in negative coordinate space, + # move expolygon entirely in positive coordinate space + $translate = $bounding_box->min_point->negative; + } + $expolygon->translate(@$translate); + $bounding_box->translate(@$translate); + my ($n_lo, $n_hi) = $path->rect_to_n_range( map { $_ / $distance_between_lines } - 0, 0, - @{$bounding_box->size}, + @{$bounding_box->min_point}, + @{$bounding_box->max_point}, ); my $polyline = Slic3r::Polyline->new( @@ -50,14 +63,15 @@ sub fill_surface { if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("fill.svg", - no_arrows => 1, - polygons => \@$expolygon, - polylines => \@paths, + no_arrows => 1, + polygons => \@$expolygon, + red_polygons => [ $bounding_box->polygon ], + polylines => \@paths, ); } # paths must be repositioned and rotated back - $_->translate($bounding_box->x_min, $bounding_box->y_min) for @paths; + $_->translate(@{$translate->negative}) for @paths; $self->rotate_points_back(\@paths, $rotate_vector); return { flow => $flow }, @paths; From 540c2b870533881c15e06c9fc4a0b3e84dfe35a7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 11:37:28 +0100 Subject: [PATCH 070/107] Minor additions for debugging --- lib/Slic3r/Fill/PlanePath.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm index 5d46937c0..3f7e1118f 100644 --- a/lib/Slic3r/Fill/PlanePath.pm +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -65,8 +65,9 @@ sub fill_surface { Slic3r::SVG::output("fill.svg", no_arrows => 1, polygons => \@$expolygon, - red_polygons => [ $bounding_box->polygon ], - polylines => \@paths, + green_polygons => [ $bounding_box->polygon ], + polylines => [ $polyline ], + red_polylines => \@paths, ); } From 93507bfd499eae228213eb3861e1380fede5115a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 11:46:16 +0100 Subject: [PATCH 071/107] Bugfix: only_retract_when_crossing_perimeters was not correctly applied on bottom layer when no bottom solid layers were printed --- lib/Slic3r/GCode.pm | 4 ++-- xs/src/libslic3r/Layer.cpp | 8 ++++---- xs/src/libslic3r/Layer.hpp | 2 +- xs/xsp/Layer.xsp | 16 ++++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 5ebca1db9..2e189c499 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -344,7 +344,7 @@ sub travel_to { if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id) || ($self->config->only_retract_when_crossing_perimeters && $self->config->fill_density > 0 - && defined($self->layer) && $self->layer->any_internal_region_slice_contains_line($travel)) + && defined($self->layer) && $self->layer->any_internal_region_fill_surface_contains_line($travel)) || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel)) ) { # Just perform a straight travel move without any retraction. @@ -607,7 +607,7 @@ sub _plan { # if the path is not contained in a single island we need to retract $gcode .= $gcodegen->retract if !$gcodegen->config->only_retract_when_crossing_perimeters - || !$gcodegen->layer->any_internal_region_slice_contains_polyline($travel); + || !$gcodegen->layer->any_internal_region_fill_surface_contains_polyline($travel); # append the actual path and return # use G1 because we rely on paths being straight (G0 may make round paths) diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 26e9ab7f8..8f8334d1f 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -122,15 +122,15 @@ Layer::make_slices() template bool -Layer::any_internal_region_slice_contains(const T &item) const +Layer::any_internal_region_fill_surface_contains(const T &item) const { FOREACH_LAYERREGION(this, layerm) { - if ((*layerm)->slices.any_internal_contains(item)) return true; + if ((*layerm)->fill_surfaces.any_internal_contains(item)) return true; } return false; } -template bool Layer::any_internal_region_slice_contains(const Line &item) const; -template bool Layer::any_internal_region_slice_contains(const Polyline &item) const; +template bool Layer::any_internal_region_fill_surface_contains(const Line &item) const; +template bool Layer::any_internal_region_fill_surface_contains(const Polyline &item) const; #ifdef SLIC3RXS diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index bd87ab0cb..63e4819c8 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -93,7 +93,7 @@ class Layer { LayerRegion* add_region(PrintRegion* print_region); void make_slices(); - template bool any_internal_region_slice_contains(const T &item) const; + template bool any_internal_region_fill_surface_contains(const T &item) const; protected: int _id; // sequential number of layer, 0-based diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index a84d4464f..b2996dc03 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -69,10 +69,10 @@ %code%{ RETVAL = (int)(intptr_t)THIS; %}; void make_slices(); - bool any_internal_region_slice_contains_line(Line* line) - %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; - bool any_internal_region_slice_contains_polyline(Polyline* polyline) - %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; + bool any_internal_region_fill_surface_contains_line(Line* line) + %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %}; + bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline) + %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %}; }; %name{Slic3r::Layer::Support} class SupportLayer { @@ -119,8 +119,8 @@ Ref slices() %code%{ RETVAL = &THIS->slices; %}; - bool any_internal_region_slice_contains_line(Line* line) - %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; - bool any_internal_region_slice_contains_polyline(Polyline* polyline) - %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; + bool any_internal_region_fill_surface_contains_line(Line* line) + %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %}; + bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline) + %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %}; }; From 64061267c84c98e6508bdb4b7e7ad4ef288c864d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 16:47:35 +0100 Subject: [PATCH 072/107] Align infill across layers regardless of first-layer-specific extrusion width. Includes a good internal API refactoring and a fix to 3D honeycomb flow --- lib/Slic3r/Fill.pm | 52 ++++++++++++++++++++++++++++------ lib/Slic3r/Fill/3DHoneycomb.pm | 15 ++++------ lib/Slic3r/Fill/Base.pm | 5 ++++ lib/Slic3r/Fill/Concentric.pm | 16 +++-------- lib/Slic3r/Fill/Honeycomb.pm | 6 ++-- lib/Slic3r/Fill/PlanePath.pm | 5 ++-- lib/Slic3r/Fill/Rectilinear.pm | 12 ++------ xs/src/libslic3r/Flow.cpp | 2 +- 8 files changed, 67 insertions(+), 46 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 780717586..fb2dfe858 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -195,28 +195,66 @@ sub make_fill { next SURFACE unless $density > 0; } + # get filler object + my $f = $self->filler($filler); + + # calculate the actual flow we'll be using for this infill my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness; my $flow = $layerm->region->flow( $role, $h, - $is_bridge, + $is_bridge || $f->use_bridge_flow, $layerm->id == 0, -1, $layerm->object, ); - my $f = $self->filler($filler); + # calculate flow spacing for infill pattern generation + my $using_internal_flow = 0; + if (!$is_solid && !$is_bridge) { + # it's internal infill, so we can calculate a generic flow spacing + # for all layers, for avoiding the ugly effect of + # misaligned infill on first layer because of different extrusion width and + # layer height + my $internal_flow = $layerm->region->flow( + FLOW_ROLE_INFILL, + $layerm->object->config->layer_height, # TODO: handle infill_every_layers? + 0, # no bridge + 0, # no first layer + -1, # auto width + $layerm->object, + ); + $f->spacing($internal_flow->spacing); + $using_internal_flow = 1; + } else { + $f->spacing($flow->spacing); + } + $f->layer_id($layerm->id); $f->z($layerm->print_z); $f->angle(deg2rad($layerm->config->fill_angle)); - my ($params, @polylines) = $f->fill_surface( + $f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + my @polylines = $f->fill_surface( $surface, density => $density/100, - flow => $flow, layer_height => $h, ); next unless @polylines; + # calculate actual flow from spacing (which might have been adjusted by the infill + # pattern generator) + if ($using_internal_flow) { + # if we used the internal flow we're not doing a solid infill + # so we can safely ignore the slight variation that might have + # been applied to $f->flow_spacing + } else { + $flow = Slic3r::Flow->new_from_spacing( + spacing => $f->spacing, + nozzle_diameter => $flow->nozzle_diameter, + layer_height => $h, + bridge => $is_bridge || $f->use_bridge_flow, + ); + } my $mm3_per_mm = $flow->mm3_per_mm; # save into layer @@ -224,18 +262,16 @@ sub make_fill { my $role = $is_bridge ? EXTR_ROLE_BRIDGE : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) : EXTR_ROLE_FILL; - - my $extrusion_height = $is_bridge ? $flow->width : $h; push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new; - $collection->no_sort($params->{no_sort}); + $collection->no_sort($f->no_sort); $collection->append( map Slic3r::ExtrusionPath->new( polyline => $_, role => $role, mm3_per_mm => $mm3_per_mm, width => $flow->width, - height => $extrusion_height, + height => $flow->height, ), @polylines, ); } diff --git a/lib/Slic3r/Fill/3DHoneycomb.pm b/lib/Slic3r/Fill/3DHoneycomb.pm index bb37d0ea7..256437707 100644 --- a/lib/Slic3r/Fill/3DHoneycomb.pm +++ b/lib/Slic3r/Fill/3DHoneycomb.pm @@ -7,22 +7,17 @@ use POSIX qw(ceil fmod); use Slic3r::Geometry qw(scale scaled_epsilon); use Slic3r::Geometry::Clipper qw(intersection_pl); +# require bridge flow since most of this pattern hangs in air +sub use_bridge_flow { 1 } + sub fill_surface { my ($self, $surface, %params) = @_; - # use bridge flow since most of this pattern hangs in air - my $flow = Slic3r::Flow->new( - width => $params{flow}->width, - height => $params{flow}->height, - nozzle_diameter => $params{flow}->nozzle_diameter, - bridge => 1, - ); - my $expolygon = $surface->expolygon; my $bb = $expolygon->bounding_box; my $size = $bb->size; - my $distance = $flow->scaled_spacing / $params{density}; + my $distance = scale($self->spacing) / $params{density}; # align bounding box to a multiple of our honeycomb grid { @@ -71,7 +66,7 @@ sub fill_surface { } # TODO: return ExtrusionLoop objects to get better chained paths - return { flow => $flow}, @polylines; + return @polylines; } diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm index 34b47243b..75c8e03e6 100644 --- a/lib/Slic3r/Fill/Base.pm +++ b/lib/Slic3r/Fill/Base.pm @@ -4,6 +4,8 @@ use Moo; has 'layer_id' => (is => 'rw'); has 'z' => (is => 'rw'); # in unscaled coordinates has 'angle' => (is => 'rw'); # in radians, ccw, 0 = East +has 'spacing' => (is => 'rw'); # in unscaled coordinates +has 'loop_clipping' => (is => 'rw', default => sub { 0 }); # in scaled coordinates has 'bounding_box' => (is => 'ro', required => 0); # Slic3r::Geometry::BoundingBox object sub adjust_solid_spacing { @@ -17,6 +19,9 @@ sub adjust_solid_spacing { return $params{distance} + $extra_space / ($number_of_lines - 1); } +sub no_sort { 0 } +sub use_bridge_flow { 0 } + package Slic3r::Fill::WithDirection; use Moo::Role; diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm index f122bdbd5..17f3fa225 100644 --- a/lib/Slic3r/Fill/Concentric.pm +++ b/lib/Slic3r/Fill/Concentric.pm @@ -15,22 +15,15 @@ sub fill_surface { my $expolygon = $surface->expolygon; my $bounding_box = $expolygon->bounding_box; - my $flow = $params{flow}; - my $min_spacing = $flow->scaled_spacing; + my $min_spacing = scale($self->spacing); my $distance = $min_spacing / $params{density}; - my $flow_spacing = $flow->spacing; if ($params{density} == 1 && !$params{dont_adjust}) { $distance = $self->adjust_solid_spacing( width => $bounding_box->size->[X], distance => $distance, ); - $flow = Slic3r::Flow->new_from_spacing( - spacing => unscale($distance), - nozzle_diameter => $flow->nozzle_diameter, - layer_height => ($params{layer_height} or die "No layer_height supplied to fill_surface()"), - bridge => $flow->bridge, - ); + $self->spacing(unscale $distance); } # compensate the overlap which is good for rectilinear but harmful for concentric @@ -54,12 +47,11 @@ sub fill_surface { } # clip the paths to prevent the extruder from getting exactly on the first point of the loop - my $clip_length = scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; - $_->clip_end($clip_length) for @paths; + $_->clip_end($self->loop_clipping) for @paths; @paths = grep $_->is_valid, @paths; # remove empty paths (too short, thus eaten by clipping) # TODO: return ExtrusionLoop objects to get better chained paths - return { flow => $flow, no_sort => 1 }, @paths; + return @paths; } 1; diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 08c0b0710..0fb47db4d 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -18,11 +18,11 @@ sub fill_surface { my $rotate_vector = $self->infill_direction($surface); # cache hexagons math - my $cache_id = sprintf "d%s_s%s", $params{density}, $params{flow}->spacing; + my $cache_id = sprintf "d%s_s%s", $params{density}, $self->spacing; my $m; if (!($m = $self->cache->{$cache_id})) { $m = $self->cache->{$cache_id} = {}; - my $min_spacing = $params{flow}->scaled_spacing; + my $min_spacing = scale($self->spacing); $m->{distance} = $min_spacing / $params{density}; $m->{hex_side} = $m->{distance} / (sqrt(3)/2); $m->{hex_width} = $m->{distance} * 2; # $m->{hex_width} == $m->{hex_side} * sqrt(3); @@ -123,7 +123,7 @@ sub fill_surface { )}; } - return { flow => $params{flow} }, @paths; + return @paths; } 1; diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm index 3f7e1118f..556835ec4 100644 --- a/lib/Slic3r/Fill/PlanePath.pm +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -21,8 +21,7 @@ sub fill_surface { my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($expolygon, $rotate_vector); - my $flow = $params{flow}; - my $distance_between_lines = $flow->scaled_spacing / $params{density} * $self->multiplier; + my $distance_between_lines = scale($self->spacing) / $params{density} * $self->multiplier; # align infill across layers using the object's bounding box my $bb_polygon = $self->bounding_box->polygon; @@ -75,7 +74,7 @@ sub fill_surface { $_->translate(@{$translate->negative}) for @paths; $self->rotate_points_back(\@paths, $rotate_vector); - return { flow => $flow }, @paths; + return @paths; } diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index 8e3eee3a8..ef459ab9d 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -18,8 +18,7 @@ sub fill_surface { my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($expolygon, $rotate_vector); - my $flow = $params{flow} or die "No flow supplied to fill_surface()"; - my $min_spacing = $flow->scaled_spacing; + my $min_spacing = scale($self->spacing); my $line_spacing = $min_spacing / $params{density}; my $line_oscillation = $line_spacing - $min_spacing; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); @@ -31,12 +30,7 @@ sub fill_surface { width => $bounding_box->size->[X], distance => $line_spacing, ); - $flow = Slic3r::Flow->new_from_spacing( - spacing => unscale($line_spacing), - nozzle_diameter => $flow->nozzle_diameter, - layer_height => ($params{layer_height} or die "No layer_height supplied to fill_surface()"), - bridge => $flow->bridge, - ); + $self->spacing(unscale $line_spacing); } else { # extend bounding box so that our pattern will be aligned with other layers $bounding_box->merge_point(Slic3r::Point->new( @@ -105,7 +99,7 @@ sub fill_surface { # paths must be rotated back $self->rotate_points_back(\@polylines, $rotate_vector); - return { flow => $flow }, @polylines; + return @polylines; } 1; diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp index 7f87f146e..b9af18184 100644 --- a/xs/src/libslic3r/Flow.cpp +++ b/xs/src/libslic3r/Flow.cpp @@ -13,7 +13,7 @@ Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &wid float w; if (bridge_flow_ratio > 0) { // if bridge flow was requested, calculate bridge width - w = Flow::_bridge_width(nozzle_diameter, bridge_flow_ratio); + height = w = Flow::_bridge_width(nozzle_diameter, bridge_flow_ratio); } else if (!width.percent && width.value == 0) { // if user left option to 0, calculate a sane default width w = Flow::_auto_width(role, nozzle_diameter, height); From e49cf2c55f0d9c686b3aafd535da5f371ec26c9d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 17:25:52 +0100 Subject: [PATCH 073/107] Refactored Line infill implementation --- lib/Slic3r/Fill.pm | 1 - lib/Slic3r/Fill/Line.pm | 8 --- lib/Slic3r/Fill/Rectilinear.pm | 109 +++++++++++++++++++++------------ 3 files changed, 71 insertions(+), 47 deletions(-) delete mode 100644 lib/Slic3r/Fill/Line.pm diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index fb2dfe858..504230933 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -6,7 +6,6 @@ use Slic3r::Fill::3DHoneycomb; use Slic3r::Fill::Base; use Slic3r::Fill::Concentric; use Slic3r::Fill::Honeycomb; -use Slic3r::Fill::Line; use Slic3r::Fill::PlanePath; use Slic3r::Fill::Rectilinear; use Slic3r::Flow ':roles'; diff --git a/lib/Slic3r/Fill/Line.pm b/lib/Slic3r/Fill/Line.pm deleted file mode 100644 index 97dc17cfd..000000000 --- a/lib/Slic3r/Fill/Line.pm +++ /dev/null @@ -1,8 +0,0 @@ -package Slic3r::Fill::Line; -use Moo; - -extends 'Slic3r::Fill::Rectilinear'; - -# Sorry for breaking OOP, but Line is implemented inside Rectilinear. - -1; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index ef459ab9d..a50e12b09 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -4,10 +4,13 @@ use Moo; extends 'Slic3r::Fill::Base'; with qw(Slic3r::Fill::WithDirection); -has 'cache' => (is => 'rw', default => sub {{}}); +has '_min_spacing' => (is => 'rw'); +has '_line_spacing' => (is => 'rw'); +has '_diagonal_distance' => (is => 'rw'); +has '_line_oscillation' => (is => 'rw'); -use Slic3r::Geometry qw(A B X Y MIN scale unscale scaled_epsilon); -use Slic3r::Geometry::Clipper qw(intersection_pl offset); +use Slic3r::Geometry qw(scale unscale scaled_epsilon); +use Slic3r::Geometry::Clipper qw(intersection_pl); sub fill_surface { my $self = shift; @@ -18,41 +21,32 @@ sub fill_surface { my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($expolygon, $rotate_vector); - my $min_spacing = scale($self->spacing); - my $line_spacing = $min_spacing / $params{density}; - my $line_oscillation = $line_spacing - $min_spacing; - my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); - my $bounding_box = $expolygon->bounding_box; + $self->_min_spacing(scale $self->spacing); + $self->_line_spacing($self->_min_spacing / $params{density}); + $self->_diagonal_distance($self->_line_spacing * 2); + $self->_line_oscillation($self->_line_spacing - $self->_min_spacing); # only for Line infill + my $bounding_box = $expolygon->bounding_box; # define flow spacing according to requested density if ($params{density} == 1 && !$params{dont_adjust}) { - $line_spacing = $self->adjust_solid_spacing( - width => $bounding_box->size->[X], - distance => $line_spacing, - ); - $self->spacing(unscale $line_spacing); + $self->_line_spacing($self->adjust_solid_spacing( + width => $bounding_box->size->x, + distance => $self->_line_spacing, + )); + $self->spacing(unscale $self->_line_spacing); } else { # extend bounding box so that our pattern will be aligned with other layers $bounding_box->merge_point(Slic3r::Point->new( - $bounding_box->x_min - ($bounding_box->x_min % $line_spacing), - $bounding_box->y_min - ($bounding_box->y_min % $line_spacing), + $bounding_box->x_min - ($bounding_box->x_min % $self->_line_spacing), + $bounding_box->y_min - ($bounding_box->y_min % $self->_line_spacing), )); } # generate the basic pattern - my $i = 0; - my $x = $bounding_box->x_min; - my $x_max = $bounding_box->x_max + scaled_epsilon; + my $x_max = $bounding_box->x_max + scaled_epsilon; my @vertical_lines = (); - while ($x <= $x_max) { - my $vertical_line = [ [$x, $bounding_box->y_max], [$x, $bounding_box->y_min] ]; - if ($is_line_pattern && $i % 2) { - $vertical_line->[A][X] += $line_oscillation; - $vertical_line->[B][X] -= $line_oscillation; - } - push @vertical_lines, Slic3r::Polyline->new(@$vertical_line); - $i++; - $x += $line_spacing; + for (my $x = $bounding_box->x_min; $x <= $x_max; $x += $self->_line_spacing) { + push @vertical_lines, $self->_line($#vertical_lines, $x, $bounding_box->y_min, $bounding_box->y_max); } # clip paths against a slightly larger expolygon, so that the first and last paths @@ -64,19 +58,10 @@ sub fill_surface { # connect lines unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections - my ($expolygon_off) = @{$expolygon->offset_ex($min_spacing/2)}; + my ($expolygon_off) = @{$expolygon->offset_ex($self->_min_spacing/2)}; my $collection = Slic3r::Polyline::Collection->new(@polylines); @polylines = (); - my $tolerance = 10 * scaled_epsilon; - my $diagonal_distance = $line_spacing * 2; - my $can_connect = $is_line_pattern - ? sub { - ($_[X] >= ($line_spacing - $line_oscillation) - $tolerance) && ($_[X] <= ($line_spacing + $line_oscillation) + $tolerance) - && $_[Y] <= $diagonal_distance - } - : sub { $_[X] <= $diagonal_distance && $_[Y] <= $diagonal_distance }; - foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { if (@polylines) { my $first_point = $polyline->first_point; @@ -85,7 +70,7 @@ sub fill_surface { # TODO: we should also check that both points are on a fill_boundary to avoid # connecting paths on the boundaries of internal regions - if ($can_connect->(@distance) && $expolygon_off->contains_line(Slic3r::Line->new($last_point, $first_point))) { + if ($self->_can_connect(@distance) && $expolygon_off->contains_line(Slic3r::Line->new($last_point, $first_point))) { $polylines[-1]->append_polyline($polyline); next; } @@ -102,4 +87,52 @@ sub fill_surface { return @polylines; } +sub _line { + my ($self, $i, $x, $y_min, $y_max) = @_; + + return Slic3r::Polyline->new( + [$x, $y_min], + [$x, $y_max], + ); +} + +sub _can_connect { + my ($self, $dist_X, $dist_Y) = @_; + + return $dist_X <= $self->_diagonal_distance + && $dist_Y <= $self->_diagonal_distance; +} + + +package Slic3r::Fill::Line; +use Moo; +extends 'Slic3r::Fill::Rectilinear'; + +use Slic3r::Geometry qw(scaled_epsilon); + +sub _line { + my ($self, $i, $x, $y_min, $y_max) = @_; + + if ($i % 2) { + return Slic3r::Polyline->new( + [$x - $self->_line_oscillation, $y_min], + [$x + $self->_line_oscillation, $y_max], + ); + } else { + return Slic3r::Polyline->new( + [$x, $y_min], + [$x, $y_max], + ); + } +} + +sub _can_connect { + my ($self, $dist_X, $dist_Y) = @_; + + my $TOLERANCE = 10 * scaled_epsilon; + return ($dist_X >= ($self->_line_spacing - $self->_line_oscillation) - $TOLERANCE) + && ($dist_X <= ($self->_line_spacing + $self->_line_oscillation) + $TOLERANCE) + && $dist_Y <= $self->_diagonal_distance; +} + 1; From 0ded18207b1f9fc8ca70e539e70173ad2976fad2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 17:29:23 +0100 Subject: [PATCH 074/107] Adapt t/fill.t to the new infill internal API --- t/fill.t | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/t/fill.t b/t/fill.t index 24370b660..63c3c087f 100644 --- a/t/fill.t +++ b/t/fill.t @@ -49,9 +49,10 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } height => 0.4, nozzle_diameter => 0.50, ); + $filler->spacing($flow->spacing); foreach my $angle (0, 45) { $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); - my ($params, @paths) = $filler->fill_surface($surface, flow => $flow, layer_height => 0.4, density => 0.4); + my @paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); is scalar @paths, 1, 'one continuous path'; } } @@ -73,15 +74,15 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } height => 0.4, nozzle_diameter => $flow_spacing, ); - my ($params, @paths) = $filler->fill_surface( + $filler->spacing($flow->spacing); + my @paths = $filler->fill_surface( $surface, - flow => $flow, layer_height => $flow->height, density => $density // 1, ); # check whether any part was left uncovered - my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $params->{flow}->spacing/2)}, @paths; + my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @paths; my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); # ignore very small dots From faed500520f7f52e71a6aa82c45d7659e2a7d54e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 18:56:16 +0100 Subject: [PATCH 075/107] Refresh slider when reloading print in toolpath preview canvas --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 2faf59fa0..208d85a59 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -91,10 +91,15 @@ sub reload_print { $z{$layer->print_z} = 1; } } + $self->enabled(1); $self->{layers_z} = [ sort { $a <=> $b } keys %z ]; $self->{slider}->SetRange(0, scalar(@{$self->{layers_z}})-1); - $self->enabled(1); - $self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}}; + if ((my $z_idx = $self->{slider}->GetValue) <= $#{$self->{layers_z}}) { + $self->set_z($self->{layers_z}[$z_idx]); + } else { + $self->{slider}->SetValue(0); + $self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}}; + } $self->{slider}->Show; $self->Layout; } From bb907fb40599e118c67fc38352149a46469edb4f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 19:34:19 +0100 Subject: [PATCH 076/107] Don't trigger the on_move callback upon simple object selection with no dragging action --- lib/Slic3r/GUI/PreviewCanvas.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 8ea3a3db6..7ff0075ac 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -34,6 +34,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init _drag_volume_idx _drag_start_pos _drag_start_xy + _dragged _camera_target _zoom ) ); @@ -160,6 +161,7 @@ sub mouse_event { # apply new temporary volume origin and ignore Z $volume->origin->translate($vector->x, $vector->y, 0); #,, $self->_drag_start_pos($cur_pos); + $self->_dragged(1); $self->Refresh; } elsif ($e->Dragging && !defined $self->_hover_volume_idx) { if ($e->LeftIsDown) { @@ -200,11 +202,12 @@ sub mouse_event { } } elsif ($e->LeftUp || $e->RightUp) { if ($self->on_move && defined $self->_drag_volume_idx) { - $self->on_move->($self->_drag_volume_idx); + $self->on_move->($self->_drag_volume_idx) if $self->_dragged; } $self->_drag_volume_idx(undef); $self->_drag_start_pos(undef); $self->_drag_start_xy(undef); + $self->_dragged(undef); } elsif ($e->Moving) { $self->_mouse_pos($pos); $self->Refresh; From 6a939eb250857fe0eaa86dffe2be3a056c6ed12d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 22 Dec 2014 19:47:39 +0100 Subject: [PATCH 077/107] Fight deadlocks --- lib/Slic3r.pm | 19 +++++++++++-------- lib/Slic3r/GUI/Plater.pm | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index f1e8f2235..dbee4c84b 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -99,9 +99,9 @@ sub spawn_thread { my $thread = threads->create(sub { @my_threads = (); - Slic3r::debugf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid; + printf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid; local $SIG{'KILL'} = sub { - Slic3r::debugf "Exiting thread %d...\n", threads->tid; + printf "Exiting thread %d...\n", threads->tid; $parallel_sema->up if $parallel_sema; kill_all_threads(); Slic3r::thread_cleanup(); @@ -121,6 +121,7 @@ sub spawn_thread { sub parallelize { my %params = @_; + lock @threads; if (!$params{disable} && $Slic3r::have_threads && $params{threads} > 1) { my @items = (ref $params{items} eq 'CODE') ? $params{items}->() : @{$params{items}}; my $q = Thread::Queue->new; @@ -224,33 +225,35 @@ sub kill_all_threads { if (threads->tid == 0) { lock @threads; foreach my $thread (get_running_threads(@threads)) { - Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid; + printf "Thread %d killing %d...\n", threads->tid, $thread->tid; $thread->kill('KILL'); } # unlock semaphore before we block on wait # otherwise we'd get a deadlock if threads were paused - resume_threads(); + resume_all_threads(); } # in any thread we wait for our children foreach my $thread (get_running_threads(@my_threads)) { - Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid; + printf " Thread %d waiting for %d...\n", threads->tid, $thread->tid; $thread->join; # block until threads are killed - Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid; + printf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid; } @my_threads = (); } -sub pause_threads { +sub pause_all_threads { return if $paused; + lock @threads; $paused = 1; $pause_sema->down; $_->kill('STOP') for get_running_threads(@threads); } -sub resume_threads { +sub resume_all_threads { return unless $paused; + lock @threads; $paused = 0; $pause_sema->up; } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 0b7d0ec3a..eb82be789 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -933,7 +933,7 @@ sub pause_background_process { my ($self) = @_; if ($self->{process_thread} || $self->{export_thread}) { - Slic3r::pause_threads(); + Slic3r::pause_all_threads(); } elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) { $self->{apply_config_timer}->Stop; } @@ -943,7 +943,7 @@ sub resume_background_process { my ($self) = @_; if ($self->{process_thread} || $self->{export_thread}) { - Slic3r::resume_threads(); + Slic3r::resume_all_threads(); } } From a1a88baa6811100f672340dda404027d26a6bb5b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 23 Dec 2014 00:39:22 +0100 Subject: [PATCH 078/107] Revert printf to debugf --- lib/Slic3r.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index dbee4c84b..76b810a87 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -99,9 +99,9 @@ sub spawn_thread { my $thread = threads->create(sub { @my_threads = (); - printf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid; + Slic3r::debugf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid; local $SIG{'KILL'} = sub { - printf "Exiting thread %d...\n", threads->tid; + Slic3r::debugf "Exiting thread %d...\n", threads->tid; $parallel_sema->up if $parallel_sema; kill_all_threads(); Slic3r::thread_cleanup(); @@ -225,7 +225,7 @@ sub kill_all_threads { if (threads->tid == 0) { lock @threads; foreach my $thread (get_running_threads(@threads)) { - printf "Thread %d killing %d...\n", threads->tid, $thread->tid; + Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid; $thread->kill('KILL'); } @@ -236,9 +236,9 @@ sub kill_all_threads { # in any thread we wait for our children foreach my $thread (get_running_threads(@my_threads)) { - printf " Thread %d waiting for %d...\n", threads->tid, $thread->tid; + Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid; $thread->join; # block until threads are killed - printf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid; + Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid; } @my_threads = (); } From 473802ce8c7cd0dd4cc4af2822e1ae906d13d6d5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 23 Dec 2014 01:04:25 +0100 Subject: [PATCH 079/107] Use support material interface extruder for layers above object's top surfaces too. #1939 --- lib/Slic3r/Print/SupportMaterial.pm | 66 ++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 5771fcc66..1cb6b4188 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -66,6 +66,10 @@ sub generate { $self->clip_with_object($base, $support_z, $object); $self->clip_with_shape($base, $shape) if @$shape; + # Detect what part of base support layers are "reverse interfaces" because they + # lie above object's top surfaces. + $self->generate_bottom_interface_layers($support_z, $base, $top, $interface); + # Install support layers into object. for my $i (0 .. $#$support_z) { $object->add_support_layer( @@ -415,6 +419,48 @@ sub generate_interface_layers { return \%interface; } +sub generate_bottom_interface_layers { + my ($self, $support_z, $base, $top, $interface) = @_; + + my $area_threshold = $self->interface_flow->scaled_spacing ** 2; + + # loop through object's top surfaces + foreach my $top_z (sort keys %$top) { + my $this = $top->{$top_z}; + + # keep a count of the interface layers we generated for this top surface + my $interface_layers = 0; + + # loop through support layers until we find the one(s) right above the top + # surface + foreach my $layer_id (0 .. $#$support_z) { + my $z = $support_z->[$layer_id]; + next unless $z > $top_z; + + # get the support material area that should be considered interface + my $interface_area = intersection( + $base->{$layer_id}, + $this, + ); + + # discard too small areas + $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; + + # subtract new interface area from base + $base->{$layer_id} = diff( + $base->{$layer_id}, + $interface_area, + ); + + # add new interface area to interface + push @{$interface->{$layer_id}}, @$interface_area; + + $interface_layers++; + last if $interface_layers == $self->object_config->support_material_interface_layers; + } + } +} + sub generate_base_layers { my ($self, $support_z, $contact, $interface, $top) = @_; @@ -620,6 +666,7 @@ sub generate_toolpaths { # interface and contact infill if (@$interface || @$contact_infill) { $fillers{interface}->angle($interface_angle); + $fillers{interface}->spacing($_interface_flow->spacing); # find centerline of the external loop $interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $_interface_flow->scaled_width/2)); @@ -645,20 +692,19 @@ sub generate_toolpaths { my @paths = (); foreach my $expolygon (@{union_ex($interface)}) { - my ($params, @p) = $fillers{interface}->fill_surface( + my @p = $fillers{interface}->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), - density => $interface_density, - flow => $_interface_flow, + density => $interface_density, layer_height => $layer->height, - complete => 1, + complete => 1, ); - my $mm3_per_mm = $params->{flow}->mm3_per_mm; + my $mm3_per_mm = $_interface_flow->mm3_per_mm; push @paths, map Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$_), role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE, mm3_per_mm => $mm3_per_mm, - width => $params->{flow}->width, + width => $_interface_flow->width, height => $layer->height, ), @p; } @@ -699,22 +745,22 @@ sub generate_toolpaths { # TODO: use offset2_ex() $to_infill = offset_ex([ map @$_, @$to_infill ], -$_flow->scaled_spacing); } + $filler->spacing($base_flow->spacing); foreach my $expolygon (@$to_infill) { - my ($params, @p) = $filler->fill_surface( + my @p = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $density, - flow => $base_flow, layer_height => $layer->height, complete => 1, ); - my $mm3_per_mm = $params->{flow}->mm3_per_mm; + my $mm3_per_mm = $base_flow->mm3_per_mm; push @paths, map Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$_), role => EXTR_ROLE_SUPPORTMATERIAL, mm3_per_mm => $mm3_per_mm, - width => $params->{flow}->width, + width => $base_flow->width, height => $layer->height, ), @p; } From 092b1724bab1221cd1199eabffe0e01d97d2fe9b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 23 Dec 2014 11:23:12 +0100 Subject: [PATCH 080/107] Fixed t/vibrationlimit.t --- t/vibrationlimit.t | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/t/vibrationlimit.t b/t/vibrationlimit.t index 882769c02..7bfa27acb 100644 --- a/t/vibrationlimit.t +++ b/t/vibrationlimit.t @@ -15,7 +15,7 @@ my $config = Slic3r::Config->new_from_defaults; # tolerance, in minutes # (our time estimation differs from the internal one because of decimals truncation) -my $epsilon = 0.003; +my $epsilon = 0.002; my $test = sub { my ($conf) = @_; @@ -57,8 +57,11 @@ my $test = sub { my $one_axis_would_trigger_limit_without_pause = 0; foreach my $axis (qw(X Y)) { + # get the direction by comparing the new $axis coordinate with the current one + # 1 = positive, 0 = no change, -1 = negative + my $dir = $info->{"new_$axis"} <=> $self->$axis; + # are we changing direction on this axis? - my $dir = $info->{"dist_$axis"} <=> ($args->{$axis} // $self->$axis); if ($dir != 0 && $dir{$axis} != $dir) { # this move changes direction on this axis if ($dir{$axis} != 0) { From 25bc8e6f29262fedd3fea4e204fc1f65986ae882 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 23 Dec 2014 20:47:11 +0100 Subject: [PATCH 081/107] Smarter automatic disabling of GUI fields + reordering of Print Settings pages + minor adjustments to tooltips --- lib/Slic3r/GUI/Tab.pm | 188 ++++++++++++++++++------------- xs/src/libslic3r/PrintConfig.cpp | 7 +- 2 files changed, 111 insertions(+), 84 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index ad589e2b5..e56b093bf 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -523,39 +523,6 @@ sub build { } } - { - my $page = $self->add_options_page('Speed', 'time.png'); - { - my $optgroup = $page->new_optgroup('Speed for print moves'); - $optgroup->append_single_option_line('perimeter_speed'); - $optgroup->append_single_option_line('small_perimeter_speed'); - $optgroup->append_single_option_line('external_perimeter_speed'); - $optgroup->append_single_option_line('infill_speed'); - $optgroup->append_single_option_line('solid_infill_speed'); - $optgroup->append_single_option_line('top_solid_infill_speed'); - $optgroup->append_single_option_line('support_material_speed'); - $optgroup->append_single_option_line('support_material_interface_speed'); - $optgroup->append_single_option_line('bridge_speed'); - $optgroup->append_single_option_line('gap_fill_speed'); - } - { - my $optgroup = $page->new_optgroup('Speed for non-print moves'); - $optgroup->append_single_option_line('travel_speed'); - } - { - my $optgroup = $page->new_optgroup('Modifiers'); - $optgroup->append_single_option_line('first_layer_speed'); - } - { - my $optgroup = $page->new_optgroup('Acceleration control (advanced)'); - $optgroup->append_single_option_line('perimeter_acceleration'); - $optgroup->append_single_option_line('infill_acceleration'); - $optgroup->append_single_option_line('bridge_acceleration'); - $optgroup->append_single_option_line('first_layer_acceleration'); - $optgroup->append_single_option_line('default_acceleration'); - } - } - { my $page = $self->add_options_page('Skirt and brim', 'box.png'); { @@ -595,51 +562,35 @@ sub build { } { - my $page = $self->add_options_page('Notes', 'note.png'); + my $page = $self->add_options_page('Speed', 'time.png'); { - my $optgroup = $page->new_optgroup('Notes', - label_width => 0, - ); - my $option = $optgroup->get_option('notes'); - $option->full_width(1); - $option->height(250); - $optgroup->append_single_option_line($option); - } - } - - { - my $page = $self->add_options_page('Output options', 'page_white_go.png'); - { - my $optgroup = $page->new_optgroup('Sequential printing'); - $optgroup->append_single_option_line('complete_objects'); - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Extruder clearance (mm)', - ); - foreach my $opt_key (qw(extruder_clearance_radius extruder_clearance_height)) { - my $option = $optgroup->get_option($opt_key); - $option->width(60); - $line->append_option($option); - } - $optgroup->append_line($line); + my $optgroup = $page->new_optgroup('Speed for print moves'); + $optgroup->append_single_option_line('perimeter_speed'); + $optgroup->append_single_option_line('small_perimeter_speed'); + $optgroup->append_single_option_line('external_perimeter_speed'); + $optgroup->append_single_option_line('infill_speed'); + $optgroup->append_single_option_line('solid_infill_speed'); + $optgroup->append_single_option_line('top_solid_infill_speed'); + $optgroup->append_single_option_line('support_material_speed'); + $optgroup->append_single_option_line('support_material_interface_speed'); + $optgroup->append_single_option_line('bridge_speed'); + $optgroup->append_single_option_line('gap_fill_speed'); } { - my $optgroup = $page->new_optgroup('Output file'); - $optgroup->append_single_option_line('gcode_comments'); - - { - my $option = $optgroup->get_option('output_filename_format'); - $option->full_width(1); - $optgroup->append_single_option_line($option); - } + my $optgroup = $page->new_optgroup('Speed for non-print moves'); + $optgroup->append_single_option_line('travel_speed'); } { - my $optgroup = $page->new_optgroup('Post-processing scripts', - label_width => 0, - ); - my $option = $optgroup->get_option('post_process'); - $option->full_width(1); - $option->height(50); - $optgroup->append_single_option_line($option); + my $optgroup = $page->new_optgroup('Modifiers'); + $optgroup->append_single_option_line('first_layer_speed'); + } + { + my $optgroup = $page->new_optgroup('Acceleration control (advanced)'); + $optgroup->append_single_option_line('perimeter_acceleration'); + $optgroup->append_single_option_line('infill_acceleration'); + $optgroup->append_single_option_line('bridge_acceleration'); + $optgroup->append_single_option_line('first_layer_acceleration'); + $optgroup->append_single_option_line('default_acceleration'); } } @@ -690,6 +641,55 @@ sub build { $optgroup->append_single_option_line('resolution'); } } + + { + my $page = $self->add_options_page('Output options', 'page_white_go.png'); + { + my $optgroup = $page->new_optgroup('Sequential printing'); + $optgroup->append_single_option_line('complete_objects'); + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Extruder clearance (mm)', + ); + foreach my $opt_key (qw(extruder_clearance_radius extruder_clearance_height)) { + my $option = $optgroup->get_option($opt_key); + $option->width(60); + $line->append_option($option); + } + $optgroup->append_line($line); + } + { + my $optgroup = $page->new_optgroup('Output file'); + $optgroup->append_single_option_line('gcode_comments'); + + { + my $option = $optgroup->get_option('output_filename_format'); + $option->full_width(1); + $optgroup->append_single_option_line($option); + } + } + { + my $optgroup = $page->new_optgroup('Post-processing scripts', + label_width => 0, + ); + my $option = $optgroup->get_option('post_process'); + $option->full_width(1); + $option->height(50); + $optgroup->append_single_option_line($option); + } + } + + { + my $page = $self->add_options_page('Notes', 'note.png'); + { + my $optgroup = $page->new_optgroup('Notes', + label_width => 0, + ); + my $option = $optgroup->get_option('notes'); + $option->full_width(1); + $option->height(250); + $optgroup->append_single_option_line($option); + } + } } sub _update { @@ -715,11 +715,28 @@ sub _update { my $have_perimeters = $config->perimeters > 0; $self->get_field($_)->toggle($have_perimeters) - for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first); + for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first + external_perimeter_extrusion_width + perimeter_speed small_perimeter_speed external_perimeter_speed); my $have_infill = $config->fill_density > 0; $self->get_field($_)->toggle($have_infill) - for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers); + for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers + solid_infill_below_area infill_extruder); + + my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0); + $self->get_field($_)->toggle($have_solid_infill) + for qw(external_fill_pattern infill_first solid_infill_extruder solid_infill_extrusion_width + solid_infill_speed); + + $self->get_field($_)->toggle($have_infill || $have_solid_infill) + for qw(fill_angle infill_extrusion_width infill_speed bridge_speed); + + $self->get_field('gap_fill_speed')->toggle($have_perimeters && $have_infill); + + my $have_top_solid_infill = $config->top_solid_layers > 0; + $self->get_field($_)->toggle($have_top_solid_infill) + for qw(top_infill_extrusion_width top_solid_infill_speed); my $have_default_acceleration = $config->default_acceleration > 0; $self->get_field($_)->toggle($have_default_acceleration) @@ -729,15 +746,23 @@ sub _update { $self->get_field($_)->toggle($have_skirt) for qw(skirt_distance skirt_height); + my $have_brim = $config->brim_width > 0; + $self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim); + my $have_support_material = $config->support_material || $config->raft_layers > 0; my $have_support_interface = $config->support_material_interface_layers > 0; $self->get_field($_)->toggle($have_support_material) for qw(support_material_threshold support_material_enforce_layers support_material_pattern support_material_spacing support_material_angle support_material_interface_layers dont_support_bridges - support_material_extruder); + support_material_extrusion_width); $self->get_field($_)->toggle($have_support_material && $have_support_interface) - for qw(support_material_interface_spacing support_material_interface_extruder); + for qw(support_material_interface_spacing support_material_interface_extruder + support_material_interface_speed); + + $self->get_field('perimeter_extrusion_width')->toggle($have_perimeters || $have_skirt || $have_brim); + $self->get_field('support_material_extruder')->toggle($have_support_material || $have_skirt); + $self->get_field('support_material_speed')->toggle($have_support_material || $have_brim || $have_skirt); my $have_sequential_printing = $config->complete_objects; $self->get_field($_)->toggle($have_sequential_printing) @@ -1102,7 +1127,8 @@ sub _update { my $config = $self->{config}; - $self->get_field('toolchange_gcode')->toggle($self->{extruders_count} > 1); + my $have_multiple_extruders = $self->{extruders_count} > 1; + $self->get_field('toolchange_gcode')->toggle($have_multiple_extruders); for my $i (0 .. ($self->{extruders_count}-1)) { # disable extruder offset for first extruder @@ -1126,9 +1152,11 @@ sub _update { $self->get_field($_, $i)->toggle($retraction && !$config->use_firmware_retraction) for qw(retract_speed retract_restart_extra wipe); + $self->get_field('retract_length_toolchange', $i)->toggle($have_multiple_extruders); + my $toolchange_retraction = $config->get_at('retract_length_toolchange', $i) > 0; - $self->get_field($_, $i)->toggle($toolchange_retraction) - for qw(retract_restart_extra_toolchange); + $self->get_field('retract_restart_extra_toolchange', $i)->toggle + ($have_multiple_extruders && $toolchange_retraction); } } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index f50269909..7fcef82fd 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -779,7 +779,7 @@ PrintConfigDef::build_def() { Options["support_material_enforce_layers"].min = 0; Options["support_material_extruder"].type = coInt; - Options["support_material_extruder"].label = "Support material extruder"; + Options["support_material_extruder"].label = "Support material/raft/skirt extruder"; Options["support_material_extruder"].category = "Extruders"; Options["support_material_extruder"].tooltip = "The extruder to use when printing support material, raft and skirt."; Options["support_material_extruder"].cli = "support-material-extruder=i"; @@ -793,7 +793,7 @@ PrintConfigDef::build_def() { Options["support_material_extrusion_width"].cli = "support-material-extrusion-width=s"; Options["support_material_interface_extruder"].type = coInt; - Options["support_material_interface_extruder"].label = "Support material interface extruder"; + Options["support_material_interface_extruder"].label = "Support material/raft interface extruder"; Options["support_material_interface_extruder"].category = "Extruders"; Options["support_material_interface_extruder"].tooltip = "The extruder to use when printing support material interface. This affects raft too."; Options["support_material_interface_extruder"].cli = "support-material-interface-extruder=i"; @@ -879,8 +879,7 @@ PrintConfigDef::build_def() { Options["threads"].type = coInt; Options["threads"].label = "Threads"; - Options["threads"].tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors. Beware that more threads consume more memory."; - Options["threads"].sidetext = "(more speed but more memory usage)"; + Options["threads"].tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors."; Options["threads"].cli = "threads|j=i"; Options["threads"].readonly = true; Options["threads"].min = 1; From af446dc7d495e17bcca512d65349d7e8d435d369 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 23 Dec 2014 22:18:43 +0100 Subject: [PATCH 082/107] Apply contact distance to first support layer above object's top surfaces too. #1939 --- lib/Slic3r/Print/SupportMaterial.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 1cb6b4188..a738c1dd8 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -339,7 +339,12 @@ sub support_layers_z { # layer_height > nozzle_diameter * 0.75 my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->object_config->support_material_extruder-1); my $support_material_height = max($max_object_layer_height, $nozzle_diameter * 0.75); - my @z = sort { $a <=> $b } @$contact_z, @$top_z, (map $_ + $nozzle_diameter, @$top_z); + + # initialize known, fixed, support layers + my @z = sort { $a <=> $b } + @$contact_z, + @$top_z, # TODO: why we have this? + (map $_ + contact_distance($nozzle_diameter), @$top_z); # enforce first layer height my $first_layer_height = $self->object_config->get_value('first_layer_height'); From d47e12f05c10dcbc5e66721f37498d1c292860af Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 00:10:31 +0100 Subject: [PATCH 083/107] Bugfix: a wrong optimization caused some top-level perimeters not to be sorted using the nearest-neighbor search. #2322 --- lib/Slic3r/Layer/Region.pm | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 6ce755b7b..f48282ee3 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -350,16 +350,12 @@ sub make_perimeters { # use a nearest neighbor search to order these children # TODO: supply second argument to chained_path() too? - # Optimization: since islands are going to be sorted by slice anyway in the - # G-code export process, we skip chained_path here - my ($sorted_collection, @orig_indices); - if ($is_contour && $depth == 0) { - $sorted_collection = $collection; - @orig_indices = (0..$#$sorted_collection); - } else { - $sorted_collection = $collection->chained_path_indices(0); - @orig_indices = @{$sorted_collection->orig_indices}; - } + # (We used to skip this chiained_path() when $is_contour && + # $depth == 0 because slices are ordered at G_code export + # time, but multiple top-level perimeters might belong to + # the same slice actually, so that was a broken optimization.) + my $sorted_collection = $collection->chained_path_indices(0); + my @orig_indices = @{$sorted_collection->orig_indices}; my @loops = (); foreach my $loop (@$sorted_collection) { From c63d5da5c2259af514a7d69ab19f4de9f6e9cad0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 00:11:13 +0100 Subject: [PATCH 084/107] Move dump_perl() to Slic3r::Polyline --- lib/Slic3r/Polygon.pm | 5 ----- lib/Slic3r/Polyline.pm | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index 1d089189c..16b334b3a 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -7,11 +7,6 @@ use parent 'Slic3r::Polyline'; use Slic3r::Geometry qw(PI); -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/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index a42b5d1c4..9cc142409 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -10,4 +10,9 @@ sub new_scale { return $class->new(map [ Slic3r::Geometry::scale($_->[X]), Slic3r::Geometry::scale($_->[Y]) ], @points); } +sub dump_perl { + my $self = shift; + return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self; +} + 1; From 91bc4d8157ac4a75b741640f0f8002d40af5326f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 00:11:29 +0100 Subject: [PATCH 085/107] Bugfix: a typo caused wrong loop splitting, thus wrong ordering of perimeters having bridging parts. Includes regression test. #2258 --- xs/src/libslic3r/ExtrusionEntity.cpp | 7 ++----- xs/t/08_extrusionloop.t | 27 ++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index c84b08489..ee49e3357 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -244,9 +244,6 @@ ExtrusionLoop::split_at_vertex(const Point &point) path->polyline.points.insert(path->polyline.points.end(), path->polyline.points.begin() + 1, path->polyline.points.begin() + idx + 1); path->polyline.points.erase(path->polyline.points.begin(), path->polyline.points.begin() + idx); } else { - // if we have multiple paths we assume they have different types, so no need to - // check for continuity as we do for the single path case above - // new paths list starts with the second half of current path ExtrusionPaths new_paths; { @@ -256,10 +253,10 @@ ExtrusionLoop::split_at_vertex(const Point &point) } // then we add all paths until the end of current path list - new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path + new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path // then we add all paths since the beginning of current list up to the previous one - new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path + new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path // finally we add the first half of current path { diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t index dd657d156..0657766c8 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 => 45; +use Test::More tests => 46; { my $square = [ @@ -119,4 +119,29 @@ use Test::More tests => 45; } } +{ + my @polylines = ( + Slic3r::Polyline->new([59312736,4821067],[64321068,4821067],[64321068,4821067],[64321068,9321068],[59312736,9321068]), + Slic3r::Polyline->new([59312736,9321068],[9829401,9321068]), + Slic3r::Polyline->new([9829401,9321068],[4821067,9321068],[4821067,4821067],[9829401,4821067]), + Slic3r::Polyline->new([9829401,4821067],[59312736,4821067]), + ); + my $loop = Slic3r::ExtrusionLoop->new; + $loop->append($_) for ( + Slic3r::ExtrusionPath->new(polyline => $polylines[0], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), + Slic3r::ExtrusionPath->new(polyline => $polylines[1], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), + Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), + Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), + ); + my $point = Slic3r::Point->new(4821067,9321068); + $loop->split_at_vertex($point) or $loop->split_at($point); + is_deeply [ map $_->role, @$loop ], [ + Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, + Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, + Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, + Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, + Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, + ], 'order is correctly preserved after splitting'; +} + __END__ From 9cb6dc768fe187b0324927b5ec787307f36477cd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 00:19:20 +0100 Subject: [PATCH 086/107] Limit the Perl version warning to 5.16, as 5.18 seems to work fine --- lib/Slic3r.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 76b810a87..516eb2996 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -26,8 +26,8 @@ BEGIN { $have_threads = 0 if $Moo::VERSION == 1.003000; } -warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n" - if $^V >= v5.16; +warn "Running Slic3r under Perl 5.16 is not supported nor recommended\n" + if $^V == v5.16; use FindBin; our $var = "$FindBin::Bin/var"; From 350d6344338c332c9465174a111cc9f551e53982 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 00:34:17 +0100 Subject: [PATCH 087/107] Enable extruder_offset configuration for first extruder. #2224 --- lib/Slic3r/GUI/Tab.pm | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index e56b093bf..b591f6f94 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -1131,9 +1131,6 @@ sub _update { $self->get_field('toolchange_gcode')->toggle($have_multiple_extruders); for my $i (0 .. ($self->{extruders_count}-1)) { - # disable extruder offset for first extruder - $self->get_field('extruder_offset', $i)->toggle($i != 0); - my $have_retract_length = $config->get_at('retract_length', $i) > 0; # when using firmware retraction, firmware decides retraction length From 5d3cd792eb4ce982d896d163815bd0d5205517f1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 01:29:36 +0100 Subject: [PATCH 088/107] Make infill_only_where_needed idempotent. #2198 --- lib/Slic3r/Print/Object.pm | 29 ++++++++++++++++++++--------- xs/xsp/Surface.xsp | 1 + 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index be819fefb..0f6636a6c 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -693,6 +693,8 @@ sub detect_surfaces_type { } } +# Idempotence of this method is guaranteed by the fact that we don't remove things from +# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. sub clip_fill_surfaces { my $self = shift; return unless $self->config->infill_only_where_needed; @@ -710,13 +712,13 @@ sub clip_fill_surfaces { # clip this layer's internal surfaces to @overhangs foreach my $layerm (@{$layer->regions}) { - # we assume that this step is run before bridge_over_infill() and combine_infill() - # so these are the only internal types we might have my (@internal, @other) = (); foreach my $surface (map $_->clone, @{$layerm->fill_surfaces}) { - $surface->surface_type == S_TYPE_INTERNAL - ? push @internal, $surface - : push @other, $surface; + if ($surface->surface_type == S_TYPE_INTERNAL) { + push @internal, $surface; + } else { + push @other, $surface; + } } # keep all the original internal surfaces to detect overhangs in this layer @@ -726,10 +728,19 @@ sub clip_fill_surfaces { expolygon => $_, surface_type => S_TYPE_INTERNAL, ), - @{intersection_ex( - $overhangs, - [ map $_->p, @internal ], - )}; + @{intersection_ex( + [ map $_->p, @internal ], + $overhangs, + )}; + + push @new, map Slic3r::Surface->new( + expolygon => $_, + surface_type => S_TYPE_INTERNALVOID, + ), + @{diff_ex( + [ map $_->p, @internal ], + $overhangs, + )}; $layerm->fill_surfaces->clear; $layerm->fill_surfaces->append($_) for (@new, @other); diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp index cd114c0df..ffde83298 100644 --- a/xs/xsp/Surface.xsp +++ b/xs/xsp/Surface.xsp @@ -17,6 +17,7 @@ double area(); bool is_solid() const; bool is_external() const; + bool is_internal() const; bool is_bottom() const; bool is_bridge() const; %{ From ea40c4d1b0d7d67464f7cebdd98a669b4b8ffb4b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 01:34:35 +0100 Subject: [PATCH 089/107] Fixed rendering of multiple interlaced layers in toolpaths preview --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 208d85a59..399f4f21a 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -238,11 +238,8 @@ sub Render { gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT'); } - my $skirt_drawn = 0; - my $brim_drawn = 0; foreach my $layer (@{$self->layers}) { my $object = $layer->object; - my $print_z = $layer->print_z; # draw slice contour { @@ -277,6 +274,13 @@ sub Render { glPopMatrix(); } } + } + + my $skirt_drawn = 0; + my $brim_drawn = 0; + foreach my $layer (@{$self->layers}) { + my $object = $layer->object; + my $print_z = $layer->print_z; # draw brim if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) { From 4848cb7606a3f1f0741bcff9cae0cc19764c59f0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 10:20:55 +0100 Subject: [PATCH 090/107] Ported PrintObject::bridge_over_infill() to XS --- lib/Slic3r/Print/Object.pm | 76 ---------------------- xs/src/libslic3r/ClipperUtils.cpp | 12 ++++ xs/src/libslic3r/ClipperUtils.hpp | 3 + xs/src/libslic3r/Print.hpp | 2 + xs/src/libslic3r/PrintObject.cpp | 88 ++++++++++++++++++++++++++ xs/src/libslic3r/SurfaceCollection.cpp | 21 ++++++ xs/src/libslic3r/SurfaceCollection.hpp | 2 + xs/xsp/Print.xsp | 2 + 8 files changed, 130 insertions(+), 76 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 0f6636a6c..93b69a0fa 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -760,82 +760,6 @@ sub clip_fill_surfaces { } } -sub bridge_over_infill { - my $self = shift; - - for my $region_id (0..($self->print->region_count - 1)) { - my $fill_density = $self->print->regions->[$region_id]->config->fill_density; - next if $fill_density == 100 || $fill_density == 0; - - for my $layer_id (1..($self->layer_count - 1)) { - my $layer = $self->get_layer($layer_id); - my $layerm = $layer->regions->[$region_id]; - my $lower_layer = $self->get_layer($layer_id-1); - - # compute the areas needing bridge math - my @internal_solid = @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNALSOLID)}; - my @lower_internal = map @{$_->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)}, @{$lower_layer->regions}; - my $to_bridge = intersection_ex( - [ map $_->p, @internal_solid ], - [ map $_->p, @lower_internal ], - ); - next unless @$to_bridge; - Slic3r::debugf "Bridging %d internal areas at layer %d\n", scalar(@$to_bridge), $layer_id; - - # build the new collection of fill_surfaces - { - my @new_surfaces = map $_->clone, grep $_->surface_type != S_TYPE_INTERNALSOLID, @{$layerm->fill_surfaces}; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNALBRIDGE, - ), @$to_bridge; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNALSOLID, - ), @{diff_ex( - [ map $_->p, @internal_solid ], - [ map @$_, @$to_bridge ], - 1, - )}; - $layerm->fill_surfaces->clear; - $layerm->fill_surfaces->append($_) for @new_surfaces; - } - - # exclude infill from the layers below if needed - # see discussion at https://github.com/alexrj/Slic3r/issues/240 - # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. - if (0) { - my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; - for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { - Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; - foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { - my @new_surfaces = (); - # subtract the area from all types of surfaces - foreach my $group (@{$lower_layerm->fill_surfaces->group}) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), - @{diff_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNALVOID, - ), @{intersection_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - } - $lower_layerm->fill_surfaces->clear; - $lower_layerm->fill_surfaces->append($_) for @new_surfaces; - } - - $excess -= $self->get_layer($i)->height; - } - } - } - } -} - sub process_external_surfaces { my ($self) = @_; diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index 2a86f2a26..ba243ee82 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -425,6 +425,18 @@ template void diff(const Slic3r::Polygons & template void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); template void diff(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); +template +void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_) +{ + Slic3r::Polygons pp; + for (Slic3r::ExPolygons::const_iterator ex = clip.begin(); ex != clip.end(); ++ex) { + Slic3r::Polygons ppp = *ex; + pp.insert(pp.end(), ppp.begin(), ppp.end()); + } + diff(subject, pp, retval, safety_offset_); +} +template void diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); + template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_) { diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index 5b9d356b9..ab144f202 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -83,6 +83,9 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, template void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); +template +void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false); + template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index de93385f6..f0c1454fc 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -132,6 +132,8 @@ class PrintObject bool invalidate_step(PrintObjectStep step); bool invalidate_all_steps(); + void bridge_over_infill(); + private: Print* _print; ModelObject* _model_object; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index da7e25a48..5fbca0c6d 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -1,5 +1,6 @@ #include "Print.hpp" #include "BoundingBox.hpp" +#include "ClipperUtils.hpp" #include "Geometry.hpp" namespace Slic3r { @@ -338,6 +339,93 @@ PrintObject::invalidate_all_steps() return invalidated; } +void +PrintObject::bridge_over_infill() +{ + FOREACH_REGION(this->_print, region) { + size_t region_id = region - this->_print->regions.begin(); + + double fill_density = (*region)->config.fill_density.value; + if (fill_density == 100 || fill_density == 0) continue; + + FOREACH_LAYER(this, layer_it) { + if (layer_it == this->layers.begin()) continue; + + Layer* layer = *layer_it; + Layer* lower_layer = *(layer_it - 1); + LayerRegion* layerm = layer->get_region(region_id); + + // compute the areas needing bridge math + Polygons internal_solid, lower_internal; + layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); + FOREACH_LAYERREGION(lower_layer, lower_layerm_it) + (*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal); + + ExPolygons to_bridge; + intersection(internal_solid, lower_internal, &to_bridge); + if (to_bridge.empty()) continue; + + ExPolygons not_to_bridge; + diff(internal_solid, to_bridge, ¬_to_bridge, true); + + #ifdef SLIC3R_DEBUG + printf "Bridging %d internal areas at layer %d\n", scalar(@$to_bridge), $layer_id; + #endif + + // build the new collection of fill_surfaces + { + Surfaces new_surfaces; + for (Surfaces::const_iterator surface = layerm->fill_surfaces.surfaces.begin(); surface != layerm->fill_surfaces.surfaces.end(); ++surface) { + if (surface->surface_type != stInternalSolid) + new_surfaces.push_back(*surface); + } + + for (ExPolygons::const_iterator ex = to_bridge.begin(); ex != to_bridge.end(); ++ex) + new_surfaces.push_back(Surface(stInternalBridge, *ex)); + + for (ExPolygons::const_iterator ex = not_to_bridge.begin(); ex != not_to_bridge.end(); ++ex) + new_surfaces.push_back(Surface(stInternalSolid, *ex)); + + layerm->fill_surfaces.surfaces = new_surfaces; + } + + /* + # exclude infill from the layers below if needed + # see discussion at https://github.com/alexrj/Slic3r/issues/240 + # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. + if (0) { + my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; + for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { + Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; + foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { + my @new_surfaces = (); + # subtract the area from all types of surfaces + foreach my $group (@{$lower_layerm->fill_surfaces->group}) { + push @new_surfaces, map $group->[0]->clone(expolygon => $_), + @{diff_ex( + [ map $_->p, @$group ], + [ map @$_, @$to_bridge ], + )}; + push @new_surfaces, map Slic3r::Surface->new( + expolygon => $_, + surface_type => S_TYPE_INTERNALVOID, + ), @{intersection_ex( + [ map $_->p, @$group ], + [ map @$_, @$to_bridge ], + )}; + } + $lower_layerm->fill_surfaces->clear; + $lower_layerm->fill_surfaces->append($_) for @new_surfaces; + } + + $excess -= $self->get_layer($i)->height; + } + } + */ + } + } +} + #ifdef SLIC3RXS REGISTER_CLASS(PrintObject, "Print::Object"); diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index 027138818..ccf1eaa4e 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -80,6 +80,27 @@ SurfaceCollection::any_internal_contains(const T &item) const template bool SurfaceCollection::any_internal_contains(const Line &item) const; template bool SurfaceCollection::any_internal_contains(const Polyline &item) const; +SurfacesPtr +SurfaceCollection::filter_by_type(SurfaceType type) +{ + SurfacesPtr ss; + for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { + if (surface->surface_type == type) ss.push_back(&*surface); + } + return ss; +} + +void +SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) +{ + for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { + if (surface->surface_type == type) { + Polygons pp = surface->expolygon; + polygons->insert(polygons->end(), pp.begin(), pp.end()); + } + } +} + #ifdef SLIC3RXS REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); #endif diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index 494bb1a21..09a46449e 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -16,6 +16,8 @@ class SurfaceCollection void simplify(double tolerance); void group(std::vector *retval); template bool any_internal_contains(const T &item) const; + SurfacesPtr filter_by_type(SurfaceType type); + void filter_by_type(SurfaceType type, Polygons* polygons); }; } diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index baf0a59ed..c3c9a97c5 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -112,6 +112,8 @@ _constant() void set_step_started(PrintObjectStep step) %code%{ THIS->state.set_started(step); %}; + void bridge_over_infill(); + int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; }; From 9dd228df01859e2db19d65ec9ef4bef7562d49f9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 11:49:27 +0100 Subject: [PATCH 091/107] Enable the GLU tesselator on MSW if we have a recent OpenGL module --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 399f4f21a..9aec479d0 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -226,8 +226,9 @@ sub Render { } my $tess; - if (!&Wx::wxMSW) { - # We can't use the GLU tesselator on MSW because of an upstream bug: + if (!(&Wx::wxMSW && $OpenGL::VERSION < 0.6704)) { + # We can't use the GLU tesselator on MSW with older OpenGL versions + # because of an upstream bug: # http://sourceforge.net/p/pogl/bugs/16/ $tess = gluNewTess(); gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT'); From 19548fe301040b61e41ccbfa4a9bf7298038f096 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 12:02:42 +0100 Subject: [PATCH 092/107] Don't perform wiping if we have just changed layer and no extrusions were performed before the first retraction. Includes regression test. #2214 --- lib/Slic3r/GCode.pm | 4 ++++ t/gcode.t | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 2e189c499..6f6ba2dab 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -96,6 +96,10 @@ sub change_layer { $gcode .= $self->retract; } $gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer->id . ')'); + + # forget last wiping path as wiping after raising Z is pointless + $self->wipe->path(undef); + return $gcode; } diff --git a/t/gcode.t b/t/gcode.t index 17393e7cf..aa15098a0 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 19; +use Test::More tests => 20; use strict; use warnings; @@ -24,14 +24,24 @@ use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('wipe', [1]); + $config->set('retract_layer_change', [0]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $have_wipe = 0; my @retract_speeds = (); + my $extruded_on_this_layer = 0; + my $wiping_on_new_layer = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; - if ($info->{retracting} && $info->{dist_XY} > 0) { + + if ($info->{travel} && $info->{dist_Z}) { + # changing layer + $extruded_on_this_layer = 0; + } elsif ($info->{extruding} && $info->{dist_XY}) { + $extruded_on_this_layer = 1; + } elsif ($info->{retracting} && $info->{dist_XY} > 0) { $have_wipe = 1; + $wiping_on_new_layer = 1 if !$extruded_on_this_layer; my $move_time = $info->{dist_XY} / ($args->{F} // $self->F); push @retract_speeds, abs($info->{dist_E}) / $move_time; } @@ -39,6 +49,7 @@ use Slic3r::Test; ok $have_wipe, "wipe"; ok !defined (first { abs($_ - $config->retract_speed->[0]*60) < 5 } @retract_speeds), 'wipe moves don\'t retract faster than configured speed'; + ok !$wiping_on_new_layer, 'no wiping after layer change'; } { From 33f7b08c80c89d5a5ab50ac696204ebfe5d86366 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 12:11:30 +0100 Subject: [PATCH 093/107] Fix t/support.t after changing contact distance on top surfaces --- t/support.t | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/t/support.t b/t/support.t index 72749915b..edf5476c5 100644 --- a/t/support.t +++ b/t/support.t @@ -22,15 +22,15 @@ use Slic3r::Test; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); $print->print->init_extruders; my $flow = $print->print->objects->[0]->support_material_flow; - my $support_z = Slic3r::Print::SupportMaterial - ->new( - object_config => $print->print->objects->[0]->config, - print_config => $print->print->config, - flow => $flow, - interface_flow => $flow, - first_layer_flow => $flow, - ) - ->support_layers_z(\@contact_z, \@top_z, $config->layer_height); + my $support = Slic3r::Print::SupportMaterial->new( + object_config => $print->print->objects->[0]->config, + print_config => $print->print->config, + flow => $flow, + interface_flow => $flow, + first_layer_flow => $flow, + ); + my $support_z = $support->support_layers_z(\@contact_z, \@top_z, $config->layer_height); + my $expected_top_spacing = Slic3r::Print::SupportMaterial::contact_distance($config->nozzle_diameter->[0]); is $support_z->[0], $config->first_layer_height, 'first layer height is honored'; @@ -44,9 +44,10 @@ use Slic3r::Test; # find layer index of this top surface my $layer_id = first { abs($support_z->[$_] - $top_z) < epsilon } 0..$#$support_z; - # check that first support layer above this top surface is spaced with nozzle diameter + # check that first support layer above this top surface (or the next one) is spaced with nozzle diameter $wrong_top_spacing = 1 - if ($support_z->[$layer_id+1] - $support_z->[$layer_id]) != $config->nozzle_diameter->[0]; + if ($support_z->[$layer_id+1] - $support_z->[$layer_id]) != $expected_top_spacing + && ($support_z->[$layer_id+2] - $support_z->[$layer_id]) != $expected_top_spacing; } ok !$wrong_top_spacing, 'layers above top surfaces are spaced correctly'; }; From 5639132dae71fbcb8d84b9c9fc0b136ea021fdb2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Dec 2014 18:35:59 +0100 Subject: [PATCH 094/107] Bugfix: the region_volumes vector was not always extended after creating new regions, causing bad memory access in apply_config(). #2446 --- xs/src/libslic3r/Print.cpp | 2 +- xs/src/libslic3r/Print.hpp | 6 ++++-- xs/src/libslic3r/PrintObject.cpp | 8 +------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 1d186d3ab..b40430fec 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -463,7 +463,7 @@ Print::apply_config(DynamicPrintConfig config) std::vector ®ion_volumes = object->region_volumes[region_id]; for (std::vector::const_iterator volume_id = region_volumes.begin(); volume_id != region_volumes.end(); ++volume_id) { - ModelVolume* volume = object->model_object()->volumes[*volume_id]; + ModelVolume* volume = object->model_object()->volumes.at(*volume_id); PrintRegionConfig new_config = this->_region_config_from_model_volume(*volume); diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index f0c1454fc..0e7334eaf 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -76,8 +76,10 @@ class PrintObject friend class Print; public: - // vector of (vectors of volume ids), indexed by region_id - std::vector > region_volumes; + // map of (vectors of volume ids), indexed by region_id + /* (we use map instead of vector so that we don't have to worry about + resizing it and the [] operator adds new items automagically) */ + std::map< size_t,std::vector > region_volumes; PrintObjectConfig config; t_layer_height_ranges layer_height_ranges; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 5fbca0c6d..1d33fbc00 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -10,8 +10,6 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding _model_object(model_object), typed_slices(false) { - region_volumes.resize(this->_print->regions.size()); - // Compute the translation to be applied to our meshes so that we work with smaller coordinates { // Translate meshes so that our toolpath generation algorithms work with smaller @@ -125,10 +123,6 @@ PrintObject::bounding_box(BoundingBox* bb) const void PrintObject::add_region_volume(int region_id, int volume_id) { - if (region_id >= region_volumes.size()) { - region_volumes.resize(region_id + 1); - } - region_volumes[region_id].push_back(volume_id); } @@ -369,7 +363,7 @@ PrintObject::bridge_over_infill() diff(internal_solid, to_bridge, ¬_to_bridge, true); #ifdef SLIC3R_DEBUG - printf "Bridging %d internal areas at layer %d\n", scalar(@$to_bridge), $layer_id; + printf("Bridging %zu internal areas at layer %d\n", to_bridge.size(), layer->id()); #endif // build the new collection of fill_surfaces From ffff597bfec04fd84508673918d154d69f708db4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 02:36:10 +0100 Subject: [PATCH 095/107] Bugfix: the Bed Shape dialog didn't retain rectangle origin correctly. #2427 --- lib/Slic3r/GUI/BedShapeDialog.pm | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/GUI/BedShapeDialog.pm b/lib/Slic3r/GUI/BedShapeDialog.pm index 4169d38e6..a055dad24 100644 --- a/lib/Slic3r/GUI/BedShapeDialog.pm +++ b/lib/Slic3r/GUI/BedShapeDialog.pm @@ -82,7 +82,6 @@ sub new { tooltip => 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.', default => [0,0], )); - $optgroup->on_change->($_) for qw(rect_size rect_origin); # set defaults } { my $optgroup = $self->_init_shape_options_page('Circular'); @@ -94,7 +93,6 @@ sub new { sidetext => 'mm', default => 200, )); - $optgroup->on_change->($_) for qw(diameter); # set defaults } { my $optgroup = $self->_init_shape_options_page('Custom'); @@ -162,6 +160,7 @@ sub _set_shape { my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR]; $optgroup->set_value('rect_size', [ $x_max-$x_min, $y_max-$y_min ]); $optgroup->set_value('rect_origin', $origin); + $self->_update_shape; return; } } @@ -172,16 +171,18 @@ sub _set_shape { my $center = $polygon->bounding_box->center; my @vertex_distances = map $center->distance_to($_), @$polygon; my $avg_dist = sum(@vertex_distances)/@vertex_distances; - if (!defined first { abs($_ - $avg_dist) > scaled_epsilon } @vertex_distances) { + if (!defined first { abs($_ - $avg_dist) > 10*scaled_epsilon } @vertex_distances) { # all vertices are equidistant to center $self->{shape_options_book}->SetSelection(SHAPE_CIRCULAR); my $optgroup = $self->{optgroups}[SHAPE_CIRCULAR]; $optgroup->set_value('diameter', sprintf("%.0f", unscale($avg_dist*2))); + $self->_update_shape; return; } } $self->{shape_options_book}->SetSelection(SHAPE_CUSTOM); + $self->_update_shape; } sub _update_shape { @@ -189,13 +190,14 @@ sub _update_shape { my $page_idx = $self->{shape_options_book}->GetSelection; if ($page_idx == SHAPE_RECTANGULAR) { - return if grep !defined($self->{"_$_"}), qw(rect_size rect_origin); # not loaded yet - my ($x, $y) = @{$self->{_rect_size}}; + my $rect_size = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_size'); + my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin'); + my ($x, $y) = @$rect_size; return if !$x || !$y; # empty strings my ($x0, $y0) = (0,0); my ($x1, $y1) = ($x,$y); { - my ($dx, $dy) = @{$self->{_rect_origin}}; + my ($dx, $dy) = @$rect_origin; return if $dx eq '' || $dy eq ''; # empty strings $x0 -= $dx; $x1 -= $dx; @@ -209,9 +211,9 @@ sub _update_shape { [$x0,$y1], ]; } elsif ($page_idx == SHAPE_CIRCULAR) { - return if grep !defined($self->{"_$_"}), qw(diameter); # not loaded yet - return if !$self->{_diameter}; - my $r = $self->{_diameter}/2; + my $diameter = $self->{optgroups}[SHAPE_CIRCULAR]->get_value('diameter'); + return if !$diameter; + my $r = $diameter/2; my $twopi = 2*PI; my $edges = 60; my $polygon = Slic3r::Polygon->new_scale( @@ -366,7 +368,7 @@ sub _init_shape_options_page { label_width => 100, on_change => sub { my ($opt_id) = @_; - $self->{"_$opt_id"} = $optgroup->get_value($opt_id); + #$self->{"_$opt_id"} = $optgroup->get_value($opt_id); $self->_update_shape; }, ); From 70f454c69375ab983ef5cb7d3b6a64ccbf61be09 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 11:06:42 +0100 Subject: [PATCH 096/107] Fixed regression in inwards move --- lib/Slic3r/GCode.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 6f6ba2dab..63cecb333 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -214,10 +214,10 @@ sub extrude_loop { # create the destination point along the first segment and rotate it # we make sure we don't exceed the segment length because we don't know # the rotation of the second segment so we might cross the object boundary - my $first_segment = Slic3r::Line->new(@$last_path_polyline[0,1]); + my $first_segment = Slic3r::Line->new(@{$paths[0]->polyline}[0,1]); my $distance = min(scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)), $first_segment->length); my $point = $first_segment->point_at($distance); - $point->rotate($angle, $last_path_polyline->first_point); + $point->rotate($angle, $first_segment->a); # generate the travel move $gcode .= $self->travel_to($point, $paths[-1]->role, "move inwards before travel"); From 5a0f4eac8d696c5cb4ff4b3db34e695d831b0613 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 11:37:54 +0100 Subject: [PATCH 097/107] One more retraction optimization --- lib/Slic3r/GCode.pm | 9 ++++++--- xs/src/libslic3r/Layer.cpp | 11 +++++++++++ xs/src/libslic3r/Layer.hpp | 1 + xs/xsp/Layer.xsp | 4 ++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 63cecb333..7adec0529 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -341,14 +341,17 @@ sub travel_to { # Skip retraction at all in the following cases: # - travel length is shorter than the configured threshold # - user has enabled "Only retract when crossing perimeters" and the travel move is - # contained in a single island of the current layer *and* a single island in the - # upper layer (so that ooze will not be visible) + # contained in a single internal fill_surface (this includes the bottom layer when + # bottom_solid_layers == 0) or in a single internal slice (this would exclude such + # bottom layer but preserve perimeter-to-infill moves in all the other layers) # - the path that will be extruded after this travel move is a support material # extrusion and the travel move is contained in a single support material island if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id) || ($self->config->only_retract_when_crossing_perimeters && $self->config->fill_density > 0 - && defined($self->layer) && $self->layer->any_internal_region_fill_surface_contains_line($travel)) + && defined($self->layer) + && ($self->layer->any_internal_region_slice_contains_line($travel) + || $self->layer->any_internal_region_fill_surface_contains_line($travel))) || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel)) ) { # Just perform a straight travel move without any retraction. diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 8f8334d1f..47bef8101 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -120,6 +120,17 @@ Layer::make_slices() } } +template +bool +Layer::any_internal_region_slice_contains(const T &item) const +{ + FOREACH_LAYERREGION(this, layerm) { + if ((*layerm)->slices.any_internal_contains(item)) return true; + } + return false; +} +template bool Layer::any_internal_region_slice_contains(const Line &item) const; + template bool Layer::any_internal_region_fill_surface_contains(const T &item) const diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 63e4819c8..6a4e905c3 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -93,6 +93,7 @@ class Layer { LayerRegion* add_region(PrintRegion* print_region); void make_slices(); + template bool any_internal_region_slice_contains(const T &item) const; template bool any_internal_region_fill_surface_contains(const T &item) const; protected: diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index b2996dc03..cc38b0847 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -69,6 +69,8 @@ %code%{ RETVAL = (int)(intptr_t)THIS; %}; void make_slices(); + bool any_internal_region_slice_contains_line(Line* line) + %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; bool any_internal_region_fill_surface_contains_line(Line* line) %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %}; bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline) @@ -119,6 +121,8 @@ Ref slices() %code%{ RETVAL = &THIS->slices; %}; + bool any_internal_region_slice_contains_line(Line* line) + %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %}; bool any_internal_region_fill_surface_contains_line(Line* line) %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %}; bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline) From 70601eeb51e7bc68f59251e94c9162d72b29af99 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 17:35:31 +0100 Subject: [PATCH 098/107] Tell what options were changed when prompting user for saving a modified preset. Also, check whether the preset was actually modified by performing a proper idempotent diff. #2165 --- lib/Slic3r/GUI/Tab.pm | 195 +++++++++++++++++-------------- xs/src/libslic3r/PrintConfig.cpp | 3 +- xs/xsp/Config.xsp | 2 + 3 files changed, 112 insertions(+), 88 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index b591f6f94..436d9232b 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -8,7 +8,9 @@ use List::Util qw(first); use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :window wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_TREE_SEL_CHANGED); -use base 'Wx::Panel'; +use base qw(Wx::Panel Class::Accessor); + +__PACKAGE__->mk_accessors(qw(current_preset)); sub new { my $class = shift; @@ -86,18 +88,17 @@ sub new { EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); EVT_BUTTON($self, $self->{btn_delete_preset}, sub { - my $i = $self->{presets_choice}->GetSelection; + my $i = $self->current_preset; return if $i == 0; # this shouldn't happen but let's trap it anyway my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; return unless $res == wxID_YES; - if (-e $self->{presets}[$i]{file}) { - unlink $self->{presets}[$i]{file}; + if (-e $self->{presets}[$i]->file) { + unlink $self->{presets}[$i]->file; } splice @{$self->{presets}}, $i, 1; - $self->set_dirty(0); $self->{presets_choice}->Delete($i); - $self->{presets_choice}->SetSelection(0); - $self->on_select_preset; + $self->current_preset(undef); + $self->select_preset(0); $self->_on_presets_changed; }); @@ -111,14 +112,14 @@ sub new { return $self; } -sub current_preset { +sub get_current_preset { my $self = shift; - return $self->{presets}[ $self->{presets_choice}->GetSelection ]; + return $self->get_preset($self->current_preset); } sub get_preset { - my $self = shift; - return $self->{presets}[ $_[0] ]; + my ($self, $i) = @_; + return $self->{presets}[$i]; } sub save_preset { @@ -130,23 +131,22 @@ sub save_preset { $self->{treectrl}->SetFocus; if (!defined $name) { - my $preset = $self->current_preset; - my $default_name = $preset->{default} ? 'Untitled' : basename($preset->{name}); + my $preset = $self->get_current_preset; + my $default_name = $preset->default ? 'Untitled' : $preset->name; $default_name =~ s/\.ini$//i; my $dlg = Slic3r::GUI::SavePresetWindow->new($self, title => lc($self->title), default => $default_name, - values => [ map { my $name = $_->{name}; $name =~ s/\.ini$//i; $name } @{$self->{presets}} ], + values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ], ); return unless $dlg->ShowModal == wxID_OK; $name = $dlg->get_name; } $self->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->name, $name); - $self->set_dirty(0); $self->load_presets; - $self->select_preset(first { basename($self->{presets}[$_]{file}) eq $name . ".ini" } 1 .. $#{$self->{presets}}); + $self->select_preset_by_name($name); $self->_on_presets_changed; } @@ -185,7 +185,7 @@ sub config { $_[0]->{config}->clone } sub select_default_preset { my $self = shift; - $self->{presets_choice}->SetSelection(0); + $self->select_preset(0); } sub select_preset { @@ -194,21 +194,30 @@ sub select_preset { $self->on_select_preset; } +sub select_preset_by_name { + my ($self, $name) = @_; + $self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}); +} + sub on_select_preset { my $self = shift; - if ($self->{dirty}) { - my $name = $self->{dirty} == 0 ? 'Default preset' : "Preset \"$self->{presets}[$self->{dirty}]{name}\""; - my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes. Discard changes and continue anyway?", + if ($self->is_dirty) { + my $old_preset = $self->get_current_preset; + my $name = $old_preset->default ? 'Default preset' : "Preset \"" . $old_preset->name . "\""; + my $changes = join "\n", + map { "- " . ($Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } + @{$self->dirty_options}; + my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDiscard changes and continue anyway?", 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if ($confirm->ShowModal == wxID_NO) { - $self->{presets_choice}->SetSelection($self->{dirty}); + $self->{presets_choice}->SetSelection($self->current_preset); return; } - $self->set_dirty(0); } - my $preset = $self->current_preset; + $self->current_preset($self->{presets_choice}->GetSelection); + my $preset = $self->get_current_preset; my $preset_config = $self->get_preset_config($preset); eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); @@ -216,53 +225,32 @@ sub on_select_preset { $self->{config}->set($opt_key, $preset_config->get($opt_key)) if $preset_config->has($opt_key); } - ($preset->{default} || $preset->{external}) + ($preset->default || $preset->external) ? $self->{btn_delete_preset}->Disable : $self->{btn_delete_preset}->Enable; $self->_update; $self->on_preset_loaded; $self->reload_config; - - # use CallAfter because some field triggers schedule on_change calls using CallAfter, - # and we don't want them to be called after this set_dirty(0) as they would mark the - # preset dirty again - wxTheApp->CallAfter(sub { - $self->set_dirty(0); - }); - $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->{file} ? basename($preset->{file}) : ''; + $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : ''; }; if ($@) { $@ = "I was unable to load the selected config file: $@"; Slic3r::GUI::catch_error($self); $self->select_default_preset; } + + # use CallAfter because some field triggers schedule on_change calls using CallAfter, + # and we don't want them to be called after this update_dirty() as they would mark the + # preset dirty again + # (not sure this is true anymore now that update_dirty is idempotent) + wxTheApp->CallAfter(sub { + $self->update_dirty; + }); wxTheApp->save_settings; } -sub get_preset_config { - my $self = shift; - my ($preset) = @_; - - if ($preset->{default}) { - return Slic3r::Config->new_from_defaults(@{$self->{config}->get_keys}); - } else { - if (!-e $preset->{file}) { - Slic3r::GUI::show_error($self, "The selected preset does not exist anymore ($preset->{file})."); - return; - } - - # apply preset values on top of defaults - my $external_config = Slic3r::Config->load($preset->{file}); - my $config = Slic3r::Config->new; - $config->set($_, $external_config->get($_)) - for grep $external_config->has($_), @{$self->{config}->get_keys}; - - return $config; - } -} - sub init_config_options { my ($self, @opt_keys) = @_; $self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys)); @@ -305,53 +293,52 @@ sub update_tree { } } -sub set_dirty { +sub update_dirty { my $self = shift; - my ($dirty) = @_; - - return if $dirty and $self->is_dirty; - return if (not $dirty) and (not $self->is_dirty); - my $selection = $self->{presets_choice}->GetSelection; - my $i = $self->{dirty} // $selection; #/ - my $text = $self->{presets_choice}->GetString($i); - - if ($dirty) { - $self->{dirty} = $i; - if ($text !~ / \(modified\)$/) { - $self->{presets_choice}->SetString($i, "$text (modified)"); - $self->{presets_choice}->SetSelection($selection); # http://trac.wxwidgets.org/ticket/13769 + foreach my $i (0..$#{$self->{presets}}) { + my $preset = $self->get_preset($i); + if ($i == $self->current_preset && $self->is_dirty) { + $self->{presets_choice}->SetString($i, $preset->name . " (modified)"); + } else { + $self->{presets_choice}->SetString($i, $preset->name); } - } else { - $self->{dirty} = undef; - $text =~ s/ \(modified\)$//; - $self->{presets_choice}->SetString($i, $text); - $self->{presets_choice}->SetSelection($selection); # http://trac.wxwidgets.org/ticket/13769 } + $self->{presets_choice}->SetSelection($self->current_preset); # http://trac.wxwidgets.org/ticket/13769 $self->_on_presets_changed; } sub is_dirty { my $self = shift; - return (defined $self->{dirty}); + return @{$self->dirty_options} > 0; +} + +sub dirty_options { + my $self = shift; + + return [] if !defined $self->current_preset; # happens during initialization + return $self->get_preset_config($self->get_current_preset)->diff($self->{config}); } sub load_presets { my $self = shift; - $self->{presets} = [{ - default => 1, - name => '- default -', - }]; + $self->{presets} = [ + Slic3r::GUI::Tab::Preset->new( + default => 1, + name => '- default -', + ), + ]; my %presets = wxTheApp->presets($self->name); foreach my $preset_name (sort keys %presets) { - push @{$self->{presets}}, { + push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( name => $preset_name, file => $presets{$preset_name}, - }; + ); } + $self->current_preset(undef); $self->{presets_choice}->Clear; $self->{presets_choice}->Append($_->{name}) for @{$self->{presets}}; { @@ -370,11 +357,11 @@ sub load_config_file { my $i = first { $self->{presets}[$_]{file} eq $file && $self->{presets}[$_]{external} } 1..$#{$self->{presets}}; if (!$i) { my $preset_name = basename($file); # keep the .ini suffix - push @{$self->{presets}}, { + push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( file => $file, name => $preset_name, external => 1, - }; + ); $self->{presets_choice}->Append($preset_name); $i = $#{$self->{presets}}; } @@ -389,11 +376,16 @@ sub load_config { foreach my $opt_key (@{$self->{config}->diff($config)}) { $self->{config}->set($opt_key, $config->get($opt_key)); - $self->set_dirty(1); + $self->update_dirty; } $self->reload_config; } +sub get_preset_config { + my ($self, $preset) = @_; + return $preset->config($self->{config}->get_keys); +} + sub get_field { my ($self, $opt_key, $opt_index) = @_; @@ -945,7 +937,7 @@ sub build { if ($dlg->ShowModal == wxID_OK) { my $value = $dlg->GetValue; $self->{config}->set('bed_shape', $value); - $self->set_dirty(1); + $self->update_dirty; $self->_on_value_change('bed_shape', $value); } }); @@ -989,7 +981,7 @@ sub build { $optgroup->on_change(sub { my ($opt_id) = @_; if ($opt_id eq 'extruders_count') { - $self->set_dirty(1); + $self->update_dirty; $self->_extruders_count_changed($optgroup->get_value('extruders_count')); } }); @@ -1210,7 +1202,7 @@ sub new_optgroup { config => $self->GetParent->{config}, label_width => $params{label_width} // 200, on_change => sub { - $self->GetParent->set_dirty(1); + $self->GetParent->update_dirty; $self->GetParent->_on_value_change(@_); }, ); @@ -1257,7 +1249,7 @@ sub new { my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, "Save preset", wxDefaultPosition, wxDefaultSize); - my @values = grep $_ ne '- default -', @{$params{values}}; + my @values = @{$params{values}}; my $text = Wx::StaticText->new($self, -1, "Save " . lc($params{title}) . " as:", wxDefaultPosition, wxDefaultSize); $self->{combo} = Wx::ComboBox->new($self, -1, $params{default}, wxDefaultPosition, wxDefaultSize, \@values, @@ -1297,4 +1289,33 @@ sub get_name { return $self->{chosen_name}; } +package Slic3r::GUI::Tab::Preset; +use Moo; + +has 'default' => (is => 'ro', default => sub { 0 }); +has 'external' => (is => 'ro', default => sub { 0 }); +has 'name' => (is => 'rw', required => 1); +has 'file' => (is => 'rw'); + +sub config { + my ($self, $keys) = @_; + + if ($self->default) { + return Slic3r::Config->new_from_defaults(@$keys); + } else { + if (!-e $self->file) { + Slic3r::GUI::show_error($self, "The selected preset does not exist anymore (" . $self->file . ")."); + return; + } + + # apply preset values on top of defaults + my $external_config = Slic3r::Config->load($self->file); + my $config = Slic3r::Config->new; + $config->set($_, $external_config->get($_)) + for grep $external_config->has($_), @$keys; + + return $config; + } +} + 1; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 7fcef82fd..4565ba5af 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -552,9 +552,10 @@ PrintConfigDef::build_def() { Options["perimeter_speed"].min = 0; Options["perimeters"].type = coInt; - Options["perimeters"].label = "Perimeters (minimum)"; + Options["perimeters"].label = "Perimeters"; Options["perimeters"].category = "Layers and Perimeters"; Options["perimeters"].tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled."; + Options["perimeters"].sidetext = "(minimum)"; Options["perimeters"].cli = "perimeters=i"; Options["perimeters"].aliases.push_back("perimeter_offsets"); Options["perimeters"].min = 0; diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index d62a3a688..4ec282edd 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -23,6 +23,8 @@ %code{% THIS->apply(*other, true); %}; std::vector diff(DynamicPrintConfig* other) %code{% RETVAL = THIS->diff(*other); %}; + bool equals(DynamicPrintConfig* other) + %code{% RETVAL = THIS->equals(*other); %}; void apply_static(FullPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() From cc36aff66a10548db2313a493ad7d4107b043336 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 18:18:41 +0100 Subject: [PATCH 099/107] Fix MainFrame after recent changes in preset handling --- lib/Slic3r/GUI/MainFrame.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 2d92f2c52..31b9ff0a3 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -435,7 +435,7 @@ sub extra_variables { my %extra_variables = (); if ($self->{mode} eq 'expert') { - $extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->current_preset->{name} + $extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->get_current_preset->name for qw(print filament printer); } return { %extra_variables }; From 6c2a28166afd23ed9a163ddcdbd316da93c8c695 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 18:50:02 +0100 Subject: [PATCH 100/107] Rearrange plater's layout slightly in order to have larger preset selectors --- lib/Slic3r/GUI/Plater.pm | 42 ++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index eb82be789..0ee13ee38 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -162,7 +162,8 @@ sub new { } } - $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, [250,-1], wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS); + $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, + wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); $self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145); $self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45); $self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); @@ -181,8 +182,8 @@ sub new { # right pane buttons $self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); - $self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); + #$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); + #$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); if ($Slic3r::GUI::have_button_icons) { my %icons = qw( @@ -212,7 +213,7 @@ sub new { $self->export_gcode; Slic3r::thread_cleanup(); }); - EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl); + #EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl); if ($self->{htoolbar}) { EVT_TOOL($self, TB_ADD, sub { $self->add; }); @@ -290,27 +291,24 @@ sub new { { my $presets; if ($self->GetFrame->{mode} eq 'expert') { - $presets = Wx::BoxSizer->new(wxVERTICAL); + $presets = Wx::FlexGridSizer->new(3, 2, 1, 2); + $presets->AddGrowableCol(1, 1); + $presets->SetFlexibleDirection(wxHORIZONTAL); my %group_labels = ( print => 'Print settings', filament => 'Filament', printer => 'Printer', ); $self->{preset_choosers} = {}; - $self->{preset_choosers_sizers} = {}; for my $group (qw(print filament printer)) { my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); - my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [140, -1], []); + my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []); $choice->SetFont($Slic3r::GUI::small_font); $self->{preset_choosers}{$group} = [$choice]; EVT_CHOICE($choice, $choice, sub { $self->_on_select_preset($group, @_) }); - - $self->{preset_choosers_sizers}{$group} = Wx::BoxSizer->new(wxVERTICAL); - $self->{preset_choosers_sizers}{$group}->Add($choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); - $presets->Add($text, 0, wxALIGN_LEFT | wxRIGHT, 4); - $presets->Add($self->{preset_choosers_sizers}{$group}, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, 8); + $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 8); } } @@ -354,22 +352,20 @@ sub new { } } - my $right_buttons_sizer = Wx::BoxSizer->new(wxVERTICAL); - $right_buttons_sizer->Add($presets, 0, wxEXPAND, 0) if defined $presets; - $right_buttons_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxTOP, 8); - $right_buttons_sizer->Add($self->{btn_export_stl}, 0, wxEXPAND | wxTOP, 2); - - my $right_top_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $right_top_sizer->Add($self->{list}, 1, wxEXPAND | wxLEFT, 5); - $right_top_sizer->Add($right_buttons_sizer, 0, wxEXPAND | wxALL, 10); + my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $buttons_sizer->AddStretchSpacer(1); + $buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0); + $buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); - $right_sizer->Add($right_top_sizer, 1, wxEXPAND | wxBOTTOM, 10); - $right_sizer->Add($object_info_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 5); + $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; + $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); + $right_sizer->Add($self->{list}, 1, wxEXPAND, 5); + $right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); - $hsizer->Add($right_sizer, 0, wxEXPAND | wxBOTTOM, 0); + $hsizer->Add($right_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 3); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; From dbbc6e7e5547320313dd6a68fabab5b27e4fefdb Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 18:52:27 +0100 Subject: [PATCH 101/107] Update plater preset selectors when dismissing unsaved changes --- lib/Slic3r/GUI/Tab.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 436d9232b..ad32fc96a 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -245,6 +245,7 @@ sub on_select_preset { # preset dirty again # (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter(sub { + $self->_on_presets_changed; $self->update_dirty; }); From 617fbaa9bd8902b1bc4c22a6f52815ee80666be6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 19:14:18 +0100 Subject: [PATCH 102/107] Restore expansion of filament choosers --- lib/Slic3r/GUI/Plater.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 0ee13ee38..445e5784c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -291,7 +291,7 @@ sub new { { my $presets; if ($self->GetFrame->{mode} eq 'expert') { - $presets = Wx::FlexGridSizer->new(3, 2, 1, 2); + $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2); $presets->AddGrowableCol(1, 1); $presets->SetFlexibleDirection(wxHORIZONTAL); my %group_labels = ( @@ -1183,13 +1183,15 @@ sub on_extruders_change { my @presets = $choices->[0]->GetStrings; push @$choices, Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [@presets]); $choices->[-1]->SetFont($Slic3r::GUI::small_font); - $self->{preset_choosers_sizers}{filament}->Add($choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); + $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); + $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); EVT_CHOICE($choices->[-1], $choices->[-1], sub { $self->_on_select_preset('filament', @_) }); my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; $choices->[-1]->SetSelection($i || 0); } while (@$choices > $num_extruders) { - $self->{preset_choosers_sizers}{filament}->Remove(-1); + $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label + $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice $choices->[-1]->Destroy; pop @$choices; } From 4c3fa999f58810a195b369420ccb60408badbde6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 19:35:51 +0100 Subject: [PATCH 103/107] Only draw the slices in toolpath preview for the current selected layer --- lib/Slic3r/GUI/Plater/2DToolpaths.pm | 53 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 9aec479d0..a0721acd0 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -242,38 +242,39 @@ sub Render { foreach my $layer (@{$self->layers}) { my $object = $layer->object; + # only draw the slice for the current layer + next unless abs($layer->print_z - $self->z) < epsilon; + # draw slice contour - { - glLineWidth(1); - foreach my $copy (@{ $object->_shifted_copies }) { - glPushMatrix(); - glTranslatef(@$copy, 0); + glLineWidth(1); + foreach my $copy (@{ $object->_shifted_copies }) { + glPushMatrix(); + glTranslatef(@$copy, 0); + + foreach my $slice (@{$layer->slices}) { + glColor3f(0.95, 0.95, 0.95); - foreach my $slice (@{$layer->slices}) { - glColor3f(0.95, 0.95, 0.95); - - if ($tess) { - gluTessBeginPolygon($tess); - foreach my $polygon (@$slice) { - gluTessBeginContour($tess); - gluTessVertex_p($tess, @$_, 0) for @$polygon; - gluTessEndContour($tess); - } - gluTessEndPolygon($tess); - } - - glColor3f(0.9, 0.9, 0.9); + if ($tess) { + gluTessBeginPolygon($tess); foreach my $polygon (@$slice) { - foreach my $line (@{$polygon->lines}) { - glBegin(GL_LINES); - glVertex2f(@{$line->a}); - glVertex2f(@{$line->b}); - glEnd(); - } + gluTessBeginContour($tess); + gluTessVertex_p($tess, @$_, 0) for @$polygon; + gluTessEndContour($tess); + } + gluTessEndPolygon($tess); + } + + glColor3f(0.9, 0.9, 0.9); + foreach my $polygon (@$slice) { + foreach my $line (@{$polygon->lines}) { + glBegin(GL_LINES); + glVertex2f(@{$line->a}); + glVertex2f(@{$line->b}); + glEnd(); } } - glPopMatrix(); } + glPopMatrix(); } } From ce395dfba8224d983119ab5b6674473050ab77be Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 19:42:24 +0100 Subject: [PATCH 104/107] :lipstick: --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 445e5784c..42b875036 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -307,7 +307,7 @@ sub new { $choice->SetFont($Slic3r::GUI::small_font); $self->{preset_choosers}{$group} = [$choice]; EVT_CHOICE($choice, $choice, sub { $self->_on_select_preset($group, @_) }); - $presets->Add($text, 0, wxALIGN_LEFT | wxRIGHT, 4); + $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 8); } } From 5d9ff677c085940a14a451132e3e014f328c2cee Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 19:51:53 +0100 Subject: [PATCH 105/107] Workaround wxMSW not catching mouse wheel events if panel has no focus. #2424 --- lib/Slic3r/GUI/PreviewCanvas.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 7ff0075ac..436854d36 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -118,7 +118,10 @@ sub mouse_event { my ($self, $e) = @_; my $pos = Slic3r::Pointf->new($e->GetPositionXY); - if ($e->LeftDClick) { + if ($e->Entering && &Wx::wxMSW) { + # wxMSW needs focus in order to catch mouse wheel events + $self->SetFocus; + } elsif ($e->LeftDClick) { $self->on_double_click->() if $self->on_double_click; } elsif ($e->LeftDown || $e->RightDown) { From 6ac82f1a20e8c8958cf24939bab0c1b1e7a29772 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 20:04:42 +0100 Subject: [PATCH 106/107] Fix cutting of objects rotated around X or Y --- lib/Slic3r/GUI/PreviewCanvas.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 436854d36..ada211705 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -407,7 +407,7 @@ sub SetCuttingPlane { my @verts = (); foreach my $volume (@{$self->volumes}) { foreach my $volume (@{$self->volumes}) { - my $expolygons = $volume->mesh->slice([ $z + $volume->origin->z ])->[0]; + my $expolygons = $volume->mesh->slice([ $z - $volume->origin->z ])->[0]; $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1); foreach my $line (map @{$_->lines}, map @$_, @$expolygons) { From 5dc635b0b191fe17af6beb2c4b5acf030c944032 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Dec 2014 20:08:47 +0100 Subject: [PATCH 107/107] Pan with middle mouse button too. #2444 --- lib/Slic3r/GUI/PreviewCanvas.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index ada211705..d9e418345 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -189,7 +189,7 @@ sub mouse_event { $self->Refresh; } $self->_drag_start_pos($pos); - } elsif ($e->RightIsDown) { + } elsif ($e->MiddleIsDown || $e->RightIsDown) { # if dragging over blank area with right button, translate if (defined $self->_drag_start_xy) {