diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 94f0b5658f..1e94e0f482 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -28,11 +28,6 @@ BEGIN { use FindBin; -# Let the XS module know where the GUI resources reside. -set_resources_dir(decode_path($FindBin::Bin) . (($^O eq 'darwin') ? '/../Resources' : '/resources')); -set_var_dir(resources_dir() . "/icons"); -set_local_dir(resources_dir() . "/localization/"); - use Moo 1.003001; use Slic3r::XS; # import all symbols (constants etc.) before they get parsed @@ -40,9 +35,7 @@ use Slic3r::Config; use Slic3r::ExPolygon; use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; -use Slic3r::Flow; use Slic3r::GCode::Reader; -use Slic3r::Geometry::Clipper; use Slic3r::Layer; use Slic3r::Line; use Slic3r::Model; @@ -61,82 +54,4 @@ use constant SCALING_FACTOR => 0.000001; $Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0; set_logging_level($Slic3r::loglevel); -# Let the palceholder parser evaluate one expression to initialize its local static macro_processor -# class instance in a thread safe manner. -Slic3r::GCode::PlaceholderParser->new->evaluate_boolean_expression('1==1'); - -# Open a file by converting $filename to local file system locales. -sub open { - my ($fh, $mode, $filename) = @_; - return CORE::open $$fh, $mode, encode_path($filename); -} - -sub tags { - my ($format) = @_; - $format //= ''; - my %tags; - # End of line - $tags{eol} = ($format eq 'html') ? '
' : "\n"; - # Heading - $tags{h2start} = ($format eq 'html') ? '' : ''; - $tags{h2end} = ($format eq 'html') ? '' : ''; - # Bold font - $tags{bstart} = ($format eq 'html') ? '' : ''; - $tags{bend} = ($format eq 'html') ? '' : ''; - # Verbatim - $tags{vstart} = ($format eq 'html') ? '
'  : '';
-    $tags{vend}    = ($format eq 'html') ? '
' : ''; - return %tags; -} - -sub slic3r_info -{ - my (%params) = @_; - my %tag = Slic3r::tags($params{format}); - my $out = ''; - $out .= "$tag{bstart}$Slic3r::FORK_NAME$tag{bend}$tag{eol}"; - $out .= "$tag{bstart}Version: $tag{bend}$Slic3r::VERSION$tag{eol}"; - $out .= "$tag{bstart}Build: $tag{bend}$Slic3r::BUILD$tag{eol}"; - return $out; -} - -sub copyright_info -{ - my (%params) = @_; - my %tag = Slic3r::tags($params{format}); - my $out = - 'Copyright © 2016 Vojtech Bubnik, Prusa Research.
' . - 'Copyright © 2011-2016 Alessandro Ranellucci.
' . - 'Slic3r is licensed under the ' . - 'GNU Affero General Public License, version 3.' . - '


' . - 'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake and numerous others. ' . - 'Manual by Gary Hodgson. Inspired by the RepRap community.
' . - 'Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. '; - return $out; -} - -sub system_info -{ - my (%params) = @_; - my %tag = Slic3r::tags($params{format}); - - my $out = ''; - $out .= "$tag{bstart}Operating System: $tag{bend}$Config{osname}$tag{eol}"; - $out .= "$tag{bstart}System Architecture: $tag{bend}$Config{archname}$tag{eol}"; - if ($^O eq 'MSWin32') { - $out .= "$tag{bstart}Windows Version: $tag{bend}" . `ver` . $tag{eol}; - } else { - # Hopefully some kind of unix / linux. - $out .= "$tag{bstart}System Version: $tag{bend}" . `uname -a` . $tag{eol}; - } - $out .= $tag{vstart} . Config::myconfig . $tag{vend}; - $out .= " $tag{bstart}\@INC:$tag{bend}$tag{eol}$tag{vstart}"; - foreach my $i (@INC) { - $out .= " $i\n"; - } - $out .= "$tag{vend}"; - return $out; -} - 1; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index a9c822b96a..dadf76a0ab 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -23,54 +23,10 @@ our $Options = print_config_def(); } } -# From command line parameters, used by slic3r.pl -sub new_from_cli { - my $class = shift; - my %args = @_; - - # Delete hash keys with undefined value. - delete $args{$_} for grep !defined $args{$_}, keys %args; - - # Replace the start_gcode, end_gcode ... hash values - # with the content of the files they reference. - for (qw(start end layer toolchange)) { - my $opt_key = "${_}_gcode"; - if ($args{$opt_key}) { - if (-e $args{$opt_key}) { - Slic3r::open(\my $fh, "<", $args{$opt_key}) - or die "Failed to open $args{$opt_key}\n"; - binmode $fh, ':utf8'; - $args{$opt_key} = do { local $/; <$fh> }; - close $fh; - } - } - } - - my $self = $class->new; - foreach my $opt_key (keys %args) { - my $opt_def = $Options->{$opt_key}; - - # we use set_deserialize() for bool options since GetOpt::Long doesn't handle - # arrays of boolean values - if ($opt_key =~ /^(?:bed_shape|duplicate_grid|extruder_offset)$/ || $opt_def->{type} eq 'bool') { - $self->set_deserialize($opt_key, $args{$opt_key}); - } elsif (my $shortcut = $opt_def->{shortcut}) { - $self->set($_, $args{$opt_key}) for @$shortcut; - } else { - $self->set($opt_key, $args{$opt_key}); - } - } - - return $self; -} - package Slic3r::Config::Static; use parent 'Slic3r::Config'; sub Slic3r::Config::GCode::new { Slic3r::Config::Static::new_GCodeConfig } sub Slic3r::Config::Print::new { Slic3r::Config::Static::new_PrintConfig } -sub Slic3r::Config::PrintObject::new { Slic3r::Config::Static::new_PrintObjectConfig } -sub Slic3r::Config::PrintRegion::new { Slic3r::Config::Static::new_PrintRegionConfig } -sub Slic3r::Config::Full::new { Slic3r::Config::Static::new_FullPrintConfig } 1; diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 6adb650c21..1b55849ebf 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -4,36 +4,9 @@ use warnings; # an ExPolygon is a polygon with holes -use List::Util qw(first); -use Slic3r::Geometry::Clipper qw(union_ex diff_pl); - -sub offset { - my $self = shift; - return Slic3r::Geometry::Clipper::offset(\@$self, @_); -} - -sub offset_ex { - my $self = shift; - return Slic3r::Geometry::Clipper::offset_ex(\@$self, @_); -} - -sub noncollapsing_offset_ex { - my $self = shift; - my ($distance, @params) = @_; - - return $self->offset_ex($distance + 1, @params); -} - sub bounding_box { my $self = shift; return $self->contour->bounding_box; } -package Slic3r::ExPolygon::Collection; - -sub size { - my $self = shift; - return [ Slic3r::Geometry::size_2D([ map @$_, map @$_, @$self ]) ]; -} - 1; diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm deleted file mode 100644 index fed894e970..0000000000 --- a/lib/Slic3r/Flow.pm +++ /dev/null @@ -1,13 +0,0 @@ -package Slic3r::Flow; -use strict; -use warnings; - -use parent qw(Exporter); - -our @EXPORT_OK = qw(FLOW_ROLE_EXTERNAL_PERIMETER FLOW_ROLE_PERIMETER FLOW_ROLE_INFILL - FLOW_ROLE_SOLID_INFILL - FLOW_ROLE_TOP_SOLID_INFILL FLOW_ROLE_SUPPORT_MATERIAL - FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE); -our %EXPORT_TAGS = (roles => \@EXPORT_OK); - -1; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 286a73e2de..2e4f5a0972 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -9,26 +9,15 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw( PI epsilon - angle3points - collinear - dot - line_intersection - normalize - point_in_segment - polyline_lines - polygon_is_convex - polygon_segment_having_point scale unscale scaled_epsilon - size_2D X Y Z convex_hull chained_path_from deg2rad rad2deg - rad2deg_dir ); use constant PI => 4 * atan2(1, 1); @@ -45,171 +34,6 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } -# used by geometry.t, polygon_segment_having_point -sub point_in_segment { - my ($point, $line) = @_; - - my ($x, $y) = @$point; - my $line_p = $line->pp; - my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X]; - my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y]; - - # check whether the point is in the segment bounding box - return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon) - && $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon); - - # if line is vertical, check whether point's X is the same as the line - if ($line_p->[A][X] == $line_p->[B][X]) { - return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0; - } - - # calculate the Y in line at X of the point - my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y]) - * ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]); - return abs($y3 - $y) < epsilon ? 1 : 0; -} - -# used by geometry.t -sub polyline_lines { - my ($polyline) = @_; - my @points = @$polyline; - return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1; -} - -# given a $polygon, return the (first) segment having $point -# used by geometry.t -sub polygon_segment_having_point { - my ($polygon, $point) = @_; - - foreach my $line (@{ $polygon->lines }) { - return $line if point_in_segment($point, $line); - } - return undef; -} - -# polygon must be simple (non complex) and ccw -sub polygon_is_convex { - my ($points) = @_; - for (my $i = 0; $i <= $#$points; $i++) { - my $angle = angle3points($points->[$i-1], $points->[$i-2], $points->[$i]); - return 0 if $angle < PI; - } - return 1; -} - -sub normalize { - my ($line) = @_; - - my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) ) - or return [0, 0, 0]; # to avoid illegal division by zero - return [ map $_ / $len, @$line ]; -} - -# 2D dot product -# used by 3DScene.pm -sub dot { - my ($u, $v) = @_; - return $u->[X] * $v->[X] + $u->[Y] * $v->[Y]; -} - -sub line_intersection { - my ($line1, $line2, $require_crossing) = @_; - $require_crossing ||= 0; - - my $intersection = _line_intersection(map @$_, @$line1, @$line2); - return (ref $intersection && $intersection->[1] == $require_crossing) - ? $intersection->[0] - : undef; -} - -# Used by test cases. -sub collinear { - my ($line1, $line2, $require_overlapping) = @_; - my $intersection = _line_intersection(map @$_, @$line1, @$line2); - return 0 unless !ref($intersection) - && ($intersection eq 'parallel collinear' - || ($intersection eq 'parallel vertical' && abs($line1->[A][X] - $line2->[A][X]) < epsilon)); - - if ($require_overlapping) { - my @box_a = bounding_box([ $line1->[0], $line1->[1] ]); - my @box_b = bounding_box([ $line2->[0], $line2->[1] ]); - return 0 unless bounding_box_intersect( 2, @box_a, @box_b ); - } - - return 1; -} - -sub _line_intersection { - my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) = @_; - - my ($x, $y); # The as-yet-undetermined intersection point. - - my $dy10 = $y1 - $y0; # dyPQ, dxPQ are the coordinate differences - my $dx10 = $x1 - $x0; # between the points P and Q. - my $dy32 = $y3 - $y2; - my $dx32 = $x3 - $x2; - - my $dy10z = abs( $dy10 ) < epsilon; # Is the difference $dy10 "zero"? - my $dx10z = abs( $dx10 ) < epsilon; - my $dy32z = abs( $dy32 ) < epsilon; - my $dx32z = abs( $dx32 ) < epsilon; - - my $dyx10; # The slopes. - my $dyx32; - - $dyx10 = $dy10 / $dx10 unless $dx10z; - $dyx32 = $dy32 / $dx32 unless $dx32z; - - # Now we know all differences and the slopes; - # we can detect horizontal/vertical special cases. - # E.g., slope = 0 means a horizontal line. - - unless ( defined $dyx10 or defined $dyx32 ) { - return "parallel vertical"; - } - elsif ( $dy10z and not $dy32z ) { # First line horizontal. - $y = $y0; - $x = $x2 + ( $y - $y2 ) * $dx32 / $dy32; - } - elsif ( not $dy10z and $dy32z ) { # Second line horizontal. - $y = $y2; - $x = $x0 + ( $y - $y0 ) * $dx10 / $dy10; - } - elsif ( $dx10z and not $dx32z ) { # First line vertical. - $x = $x0; - $y = $y2 + $dyx32 * ( $x - $x2 ); - } - elsif ( not $dx10z and $dx32z ) { # Second line vertical. - $x = $x2; - $y = $y0 + $dyx10 * ( $x - $x0 ); - } - elsif ( abs( $dyx10 - $dyx32 ) < epsilon ) { - # The slopes are suspiciously close to each other. - # Either we have parallel collinear or just parallel lines. - - # The bounding box checks have already weeded the cases - # "parallel horizontal" and "parallel vertical" away. - - my $ya = $y0 - $dyx10 * $x0; - my $yb = $y2 - $dyx32 * $x2; - - return "parallel collinear" if abs( $ya - $yb ) < epsilon; - return "parallel"; - } - else { - # None of the special cases matched. - # We have a "honest" line intersection. - - $x = ($y2 - $y0 + $dyx10*$x0 - $dyx32*$x2)/($dyx10 - $dyx32); - $y = $y0 + $dyx10 * ($x - $x0); - } - - my $h10 = $dx10 ? ($x - $x0) / $dx10 : ($dy10 ? ($y - $y0) / $dy10 : 1); - my $h32 = $dx32 ? ($x - $x2) / $dx32 : ($dy32 ? ($y - $y2) / $dy32 : 1); - - return [Slic3r::Point->new($x, $y), $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1]; -} - # 2D sub bounding_box { my ($points) = @_; @@ -227,45 +51,4 @@ sub bounding_box { return @bb[X1,Y1,X2,Y2]; } -# used by ExPolygon::size -sub size_2D { - my @bounding_box = bounding_box(@_); - return ( - ($bounding_box[X2] - $bounding_box[X1]), - ($bounding_box[Y2] - $bounding_box[Y1]), - ); -} - -# Used by sub collinear, which is used by test cases. -# bounding_box_intersect($d, @a, @b) -# Return true if the given bounding boxes @a and @b intersect -# in $d dimensions. Used by sub collinear. -sub bounding_box_intersect { - my ( $d, @bb ) = @_; # Number of dimensions and box coordinates. - my @aa = splice( @bb, 0, 2 * $d ); # The first box. - # (@bb is the second one.) - - # Must intersect in all dimensions. - for ( my $i_min = 0; $i_min < $d; $i_min++ ) { - my $i_max = $i_min + $d; # The index for the maximum. - return 0 if ( $aa[ $i_max ] + epsilon ) < $bb[ $i_min ]; - return 0 if ( $bb[ $i_max ] + epsilon ) < $aa[ $i_min ]; - } - - return 1; -} - -# Used by test cases. -# this assumes a CCW rotation from $p2 to $p3 around $p1 -sub angle3points { - my ($p1, $p2, $p3) = @_; - # p1 is the center - - my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y]) - - atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]); - - # we only want to return only positive angles - return $angle <= 0 ? $angle + 2*PI() : $angle; -} - 1; diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm deleted file mode 100644 index b7a7da7727..0000000000 --- a/lib/Slic3r/Geometry/Clipper.pm +++ /dev/null @@ -1,14 +0,0 @@ -package Slic3r::Geometry::Clipper; -use strict; -use warnings; - -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT_OK = qw( - offset - offset_ex offset2_ex - diff_ex diff union_ex intersection_ex - JT_ROUND JT_MITER JT_SQUARE - intersection intersection_pl diff_pl union); - -1; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 4bff9b61fb..7f7b589d0d 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -31,7 +31,4 @@ sub regions { return [ map $self->get_region($_), 0..($self->region_count-1) ]; } -package Slic3r::Layer::Support; -our @ISA = qw(Slic3r::Layer); - 1; diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index bf53520d2d..16b609c95e 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -5,15 +5,4 @@ use warnings; # a line is a two-points line use parent 'Slic3r::Polyline'; -sub intersection { - my $self = shift; - my ($line, $require_crossing) = @_; - return Slic3r::Geometry::line_intersection($self, $line, $require_crossing); -} - -sub grow { - my $self = shift; - return Slic3r::Polyline->new(@$self)->grow(@_); -} - 1; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index ec31f6c8a1..d44e9878dd 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -133,11 +133,4 @@ sub add_instance { } } -sub mesh_stats { - my $self = shift; - - # TODO: sum values from all volumes - return $self->volumes->[0]->mesh->stats; -} - 1; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index 16222141a8..d5445047d9 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -5,9 +5,4 @@ use warnings; # a polygon is a closed polyline. use parent 'Slic3r::Polyline'; -sub grow { - my $self = shift; - return $self->split_at_first_point->grow(@_); -} - -1; \ No newline at end of file +1; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 0385f88b88..b4e53245ae 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -4,11 +4,6 @@ use strict; use warnings; use List::Util qw(min max sum first); -use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(scale epsilon); -use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex - offset offset_ex offset2_ex JT_MITER); -use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; sub layers { @@ -16,9 +11,4 @@ sub layers { return [ map $self->get_layer($_), 0..($self->layer_count - 1) ]; } -sub support_layers { - my $self = shift; - return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ]; -} - 1; diff --git a/lib/Slic3r/Print/State.pm b/lib/Slic3r/Print/State.pm deleted file mode 100644 index 17c614f1be..0000000000 --- a/lib/Slic3r/Print/State.pm +++ /dev/null @@ -1,12 +0,0 @@ -# Wraps C++ enums Slic3r::PrintStep and Slic3r::PrintObjectStep -package Slic3r::Print::State; -use strict; -use warnings; - -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT_OK = qw(STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL - STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM STEP_WIPE_TOWER); -our %EXPORT_TAGS = (steps => \@EXPORT_OK); - -1; diff --git a/resources/profiles/TriLAB.idx b/resources/profiles/TriLAB.idx index 1b12439f73..75819e135a 100644 --- a/resources/profiles/TriLAB.idx +++ b/resources/profiles/TriLAB.idx @@ -1,4 +1,5 @@ min_slic3r_version = 2.4.1-rc1 +1.0.1 Fix missing AzteQ Industrial ABS material for 0.6, 0.8 nozzle, enabled elefant foot compensation 1.0.0 Added AzteQ Industrial profiles for 0.8 nozzle, updated spool weight and filament cost, some minor setting improvements min_slic3r_version = 2.3.2-alpha0 0.0.9 Added AzteQ Industrial materials PC/ABS (Fillamentum), PC-Max (Polymaker), Nylon FX256 (Fillamentum), Added DeltiQ 2 materials Nylon PA12 (Fiberlogy), Nylon CF15 Carbon (Fillamentum), PEBA 90A - FlexFill (Fillamentum), MoldLay (Wax-Alike), disabled retract only when crossing perimeters, some minor setting improvements diff --git a/resources/profiles/TriLAB.ini b/resources/profiles/TriLAB.ini index a5b194eca3..b5c31d1832 100644 --- a/resources/profiles/TriLAB.ini +++ b/resources/profiles/TriLAB.ini @@ -6,7 +6,7 @@ name = TriLAB # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.0.0 +config_version = 1.0.1 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/TriLAB/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -118,7 +118,7 @@ complete_objects = 0 default_acceleration = 2000 dont_support_bridges = 0 draft_shield = 0 -elefant_foot_compensation = 0.0 +elefant_foot_compensation = 0.1 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 @@ -338,6 +338,7 @@ support_material_extrusion_width = 0.55 support_material_interface_spacing = 0.6 support_material_xy_spacing = 0.9 top_infill_extrusion_width = 0.6 +elefant_foot_compensation = 0.2 [print:DeltiQ 0.30mm Strong @0.6 nozzle] inherits = DeltiQ 0.30mm Normal @0.6 nozzle @@ -356,7 +357,7 @@ bottom_solid_min_thickness = 1.2 bridge_flow_ratio = 0.90 bridge_speed = 20 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and nozzle_diameter[0]==0.8 -elefant_foot_compensation = 0.0 +elefant_foot_compensation = 0.2 external_perimeter_extrusion_width = 0.80 external_perimeter_speed = 30 extrusion_width = 0.80 @@ -415,7 +416,7 @@ complete_objects = 0 default_acceleration = 2000 dont_support_bridges = 0 draft_shield = 0 -elefant_foot_compensation = 0 +elefant_foot_compensation = 0.1 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 @@ -558,6 +559,7 @@ support_material_extrusion_width = 0.55 support_material_interface_spacing = 0.6 support_material_xy_spacing = 0.9 top_infill_extrusion_width = 0.6 +elefant_foot_compensation = 0.2 [print:AzteQ Industrial 0.30mm Strong @0.6 nozzle] inherits = AzteQ Industrial 0.30mm Normal @0.6 nozzle @@ -579,7 +581,7 @@ bottom_solid_min_thickness = 0.7 bridge_flow_ratio = 0.95 bridge_speed = 30 dont_support_bridges = 0 -elefant_foot_compensation = 0 +elefant_foot_compensation = 0.2 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.8 external_perimeter_speed = 30 @@ -1413,9 +1415,11 @@ filament_spool_weight = 229 [filament:AzteQ Industrial - ABS - ExtraFill (Fillamentum) @0.6 nozzle] inherits = AzteQ Industrial - ABS - ExtraFill (Fillamentum) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_MODEL_AQI.*/ and nozzle_diameter[0]==0.6 [filament:AzteQ Industrial - ABS - ExtraFill (Fillamentum) @0.8 nozzle] inherits = AzteQ Industrial - ABS - ExtraFill (Fillamentum) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_MODEL_AQI.*/ and nozzle_diameter[0]==0.8 extrusion_multiplier = 0.95 [filament:AzteQ Industrial - PC/ABS - (Fillamentum)] diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 4483d60102..c79d088435 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -837,6 +837,9 @@ extern "C" { "leak:libnvidia-tls.so\n" // For NVidia driver. "leak:terminator_CreateDevice\n" // For Intel Vulkan drivers. "leak:swrast_dri.so\n" // For Mesa 3D software driver. + "leak:amdgpu_dri.so\n" // For AMD driver. + "leak:libdrm_amdgpu.so\n" // For AMD driver. + "leak:libdbus-1.so\n" // For D-Bus library. Unsure if it is a leak or not. ; } } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 8a6de26eaa..589be0e0d4 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -44,8 +44,6 @@ set(SLIC3R_SOURCES enum_bitmask.hpp ExPolygon.cpp ExPolygon.hpp - ExPolygonCollection.cpp - ExPolygonCollection.hpp Extruder.cpp Extruder.hpp ExtrusionEntity.cpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 25454d5001..5c4b5ac17b 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -240,7 +240,7 @@ TResult clipper_union( // Perform union of input polygons using the positive rule, convert to ExPolygons. //FIXME is there any benefit of not doing the boolean / using pftEvenOdd? -ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union) +inline ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union) { return PolyTreeToExPolygons(clipper_union(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd)); } @@ -438,7 +438,7 @@ Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, { return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) //FIXME one may spare one Clipper Union call. - { return ClipperPaths_to_Slic3rExPolygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); } + { return ClipperPaths_to_Slic3rExPolygons(expolygon_offset(expolygon, delta, joinType, miterLimit), /* do union */ false); } Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) @@ -713,6 +713,8 @@ Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::SinglePathProvider(clip.points)); } Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::PolygonsProvider(clip)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygons &clip) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonsProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index d7027e0ec8..484229f723 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -1,17 +1,27 @@ #ifndef slic3r_ClipperUtils_hpp_ #define slic3r_ClipperUtils_hpp_ +//#define SLIC3R_USE_CLIPPER2 + #include "libslic3r.h" -#include "clipper.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Surface.hpp" +#ifdef SLIC3R_USE_CLIPPER2 + +#include + +#else /* SLIC3R_USE_CLIPPER2 */ + +#include "clipper.hpp" // import these wherever we're included using Slic3r::ClipperLib::jtMiter; using Slic3r::ClipperLib::jtRound; using Slic3r::ClipperLib::jtSquare; +#endif /* SLIC3R_USE_CLIPPER2 */ + namespace Slic3r { static constexpr const float ClipperSafetyOffset = 10.f; @@ -298,9 +308,6 @@ namespace ClipperUtils { }; } -// Perform union of input polygons using the non-zero rule, convert to ExPolygons. -ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false); - // offset Polygons // Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better. Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); @@ -429,6 +436,7 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip); +Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 4b8dfa2343..2cfb0740ca 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -402,6 +402,42 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s return out; } +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (int v : values) { + if (i ++ > 0) + out += ", "; + out += std::to_string(v); + } + return out; +} + +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (float v : values) { + if (i ++ > 0) + out += ", "; + out += float_to_string_decimal_point(double(v)); + } + return out; +} + +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (float v : values) { + if (i ++ > 0) + out += ", "; + out += float_to_string_decimal_point(v); + } + return out; +} + void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) { // loop through options and apply them diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index a087dd8540..b8c046ceba 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "libslic3r.h" #include "clonable_ptr.hpp" @@ -1949,6 +1950,11 @@ public: throw BadOptionTypeException("Conversion to a wrong type"); return static_cast(opt); } + + template T* opt(const t_config_option_key &opt_key, bool create = false) + { return dynamic_cast(this->optptr(opt_key, create)); } + template const T* opt(const t_config_option_key &opt_key) const + { return dynamic_cast(this->optptr(opt_key)); } // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), @@ -1989,15 +1995,28 @@ public: struct SetDeserializeItem { SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::string_view opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const char *opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + std::string opt_key; std::string opt_value; bool append = false; + + private: + static std::string format(std::initializer_list values); + static std::string format(std::initializer_list values); + static std::string format(std::initializer_list values); }; // May throw BadOptionTypeException() if the operation fails. void set_deserialize(std::initializer_list items, ConfigSubstitutionContext& substitutions); @@ -2006,7 +2025,31 @@ public: double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; - void setenv_() const; + + std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option(opt_key, create)->value; } + const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } + std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_string(opt_key, idx); } + + double& opt_float(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } + const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } + double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + + int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } + int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } + int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + + // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. + // Thus the virtual method getInt() is used to retrieve the enum value. + template + ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } + + bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } + bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } + + void setenv_() const; ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule); @@ -2015,10 +2058,10 @@ public: ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule); - void save(const std::string &file) const; + void save(const std::string &file) const; // Set all the nullable values to nils. - void null_nullables(); + void null_nullables(); static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions); @@ -2127,10 +2170,6 @@ public: // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. const ConfigDef* def() const override { return nullptr; } - template T* opt(const t_config_option_key &opt_key, bool create = false) - { return dynamic_cast(this->option(opt_key, create)); } - template const T* opt(const t_config_option_key &opt_key) const - { return dynamic_cast(this->option(opt_key)); } // Overrides ConfigResolver::optptr(). const ConfigOption* optptr(const t_config_option_key &opt_key) const override; // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. @@ -2161,29 +2200,6 @@ public: // Returns options being equal in the two configs, ignoring options not present in both configs. t_config_option_keys equal(const DynamicConfig &other) const; - std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option(opt_key, create)->value; } - const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } - std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_string(opt_key, idx); } - - double& opt_float(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } - const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } - double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } - - int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } - int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } - int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } - - // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. - // Thus the virtual method getInt() is used to retrieve the enum value. - template - ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } - - bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } - bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } - // Command line processing bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr); diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 1385a51d8d..4985b788e4 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -136,11 +136,6 @@ void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution) create_from_m_contours(resolution); } -void EdgeGrid::Grid::create(const ExPolygonCollection &expolygons, coord_t resolution) -{ - create(expolygons.expolygons, resolution); -} - // m_contours has been initialized. Now fill in the edge grid. void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) { diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 3c99291498..4be2bdd07c 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -7,7 +7,6 @@ #include "Point.hpp" #include "BoundingBox.hpp" #include "ExPolygon.hpp" -#include "ExPolygonCollection.hpp" namespace Slic3r { namespace EdgeGrid { @@ -112,7 +111,6 @@ public: void create(const std::vector &polygons, coord_t resolution) { this->create(polygons, resolution, false); } void create(const ExPolygon &expoly, coord_t resolution); void create(const ExPolygons &expolygons, coord_t resolution); - void create(const ExPolygonCollection &expolygons, coord_t resolution); const std::vector& contours() const { return m_contours; } @@ -123,7 +121,6 @@ public: bool intersect(const Polygons &polygons) { for (size_t i = 0; i < polygons.size(); ++ i) if (intersect(polygons[i])) return true; return false; } bool intersect(const ExPolygon &expoly) { if (intersect(expoly.contour)) return true; for (size_t i = 0; i < expoly.holes.size(); ++ i) if (intersect(expoly.holes[i])) return true; return false; } bool intersect(const ExPolygons &expolygons) { for (size_t i = 0; i < expolygons.size(); ++ i) if (intersect(expolygons[i])) return true; return false; } - bool intersect(const ExPolygonCollection &expolygons) { return intersect(expolygons.expolygons); } // Test, whether a point is inside a contour. bool inside(const Point &pt); @@ -391,7 +388,7 @@ protected: // Referencing the source contours. // This format allows one to work with any Slic3r fixed point contour format - // (Polygon, ExPolygon, ExPolygonCollection etc). + // (Polygon, ExPolygon, ExPolygons etc). std::vector m_contours; // Referencing a contour and a line segment of m_contours. diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 6dae879510..cec59c419b 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -9,7 +9,7 @@ namespace Slic3r { class ExPolygon; -typedef std::vector ExPolygons; +using ExPolygons = std::vector; class ExPolygon { @@ -67,6 +67,8 @@ public: void simplify(double tolerance, ExPolygons* expolygons) const; void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const; void medial_axis(double max_width, double min_width, Polylines* polylines) const; + Polylines medial_axis(double max_width, double min_width) const + { Polylines out; this->medial_axis(max_width, min_width, &out); return out; } Lines lines() const; // Number of contours (outer contour with holes). diff --git a/src/libslic3r/ExPolygonCollection.cpp b/src/libslic3r/ExPolygonCollection.cpp deleted file mode 100644 index a0de8f6de6..0000000000 --- a/src/libslic3r/ExPolygonCollection.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "ExPolygonCollection.hpp" -#include "Geometry/ConvexHull.hpp" -#include "BoundingBox.hpp" - -namespace Slic3r { - -ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon) -{ - this->expolygons.push_back(expolygon); -} - -ExPolygonCollection::operator Points() const -{ - Points points; - Polygons pp = (Polygons)*this; - for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) { - for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point) - points.push_back(*point); - } - return points; -} - -ExPolygonCollection::operator Polygons() const -{ - Polygons polygons; - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { - polygons.push_back(it->contour); - for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) { - polygons.push_back(*ith); - } - } - return polygons; -} - -ExPolygonCollection::operator ExPolygons&() -{ - return this->expolygons; -} - -void -ExPolygonCollection::scale(double factor) -{ - for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { - (*it).scale(factor); - } -} - -void -ExPolygonCollection::translate(double x, double y) -{ - for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { - (*it).translate(x, y); - } -} - -void -ExPolygonCollection::rotate(double angle, const Point ¢er) -{ - for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { - (*it).rotate(angle, center); - } -} - -template -bool ExPolygonCollection::contains(const T &item) const -{ - for (const ExPolygon &poly : this->expolygons) - if (poly.contains(item)) - return true; - return false; -} -template bool ExPolygonCollection::contains(const Point &item) const; -template bool ExPolygonCollection::contains(const Line &item) const; -template bool ExPolygonCollection::contains(const Polyline &item) const; - -bool -ExPolygonCollection::contains_b(const Point &point) const -{ - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { - if (it->contains_b(point)) return true; - } - return false; -} - -void -ExPolygonCollection::simplify(double tolerance) -{ - ExPolygons expp; - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { - it->simplify(tolerance, &expp); - } - this->expolygons = expp; -} - -Polygon -ExPolygonCollection::convex_hull() const -{ - Points pp; - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) - pp.insert(pp.end(), it->contour.points.begin(), it->contour.points.end()); - return Slic3r::Geometry::convex_hull(pp); -} - -Lines -ExPolygonCollection::lines() const -{ - Lines lines; - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { - Lines ex_lines = it->lines(); - lines.insert(lines.end(), ex_lines.begin(), ex_lines.end()); - } - return lines; -} - -Polygons -ExPolygonCollection::contours() const -{ - Polygons contours; - contours.reserve(this->expolygons.size()); - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) - contours.push_back(it->contour); - return contours; -} - -void -ExPolygonCollection::append(const ExPolygons &expp) -{ - this->expolygons.insert(this->expolygons.end(), expp.begin(), expp.end()); -} - -BoundingBox get_extents(const ExPolygonCollection &expolygon) -{ - return get_extents(expolygon.expolygons); -} - -} diff --git a/src/libslic3r/ExPolygonCollection.hpp b/src/libslic3r/ExPolygonCollection.hpp deleted file mode 100644 index 35e1eef4eb..0000000000 --- a/src/libslic3r/ExPolygonCollection.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef slic3r_ExPolygonCollection_hpp_ -#define slic3r_ExPolygonCollection_hpp_ - -#include "libslic3r.h" -#include "ExPolygon.hpp" -#include "Line.hpp" -#include "Polyline.hpp" - -namespace Slic3r { - -class ExPolygonCollection; -typedef std::vector ExPolygonCollections; - -class ExPolygonCollection -{ -public: - ExPolygons expolygons; - - ExPolygonCollection() {} - explicit ExPolygonCollection(const ExPolygon &expolygon); - explicit ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {} - explicit operator Points() const; - explicit operator Polygons() const; - explicit operator ExPolygons&(); - void scale(double factor); - void translate(double x, double y); - void rotate(double angle, const Point ¢er); - template bool contains(const T &item) const; - bool contains_b(const Point &point) const; - void simplify(double tolerance); - Polygon convex_hull() const; - Lines lines() const; - Polygons contours() const; - void append(const ExPolygons &expolygons); -}; - -extern BoundingBox get_extents(const ExPolygonCollection &expolygon); - -} - -#endif diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 7b2506a221..0c11653165 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -1,6 +1,6 @@ #include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" -#include "ExPolygonCollection.hpp" +#include "ExPolygon.hpp" #include "ClipperUtils.hpp" #include "Extruder.hpp" #include "Flow.hpp" @@ -12,14 +12,14 @@ namespace Slic3r { -void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const +void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection.expolygons), retval); + this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval); } -void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const +void ExtrusionPath::subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection.expolygons), retval); + this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection), retval); } void ExtrusionPath::clip_end(double distance) diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 5e6a51d20a..52a8a563ce 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -11,7 +11,8 @@ namespace Slic3r { -class ExPolygonCollection; +class ExPolygon; +using ExPolygons = std::vector; class ExtrusionEntityCollection; class Extruder; @@ -144,12 +145,12 @@ public: size_t size() const { return this->polyline.size(); } bool empty() const { return this->polyline.empty(); } bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); } - // Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection. + // Produce a list of extrusion paths into retval by clipping this path by ExPolygons. // Currently not used. - void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; - // Produce a list of extrusion paths into retval by removing parts of this path by ExPolygonCollection. + void intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const; + // Produce a list of extrusion paths into retval by removing parts of this path by ExPolygons. // Currently not used. - void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; + void subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const; void clip_end(double distance); void simplify(double tolerance); double length() const override; @@ -324,10 +325,10 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, E polylines.clear(); } -inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); - for (Polyline &polyline : polylines) + for (const Polyline &polyline : polylines) if (polyline.is_valid()) { ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); dst.push_back(extrusion_path); diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 6a4a675cbe..0bdd1c7e88 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -63,7 +63,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object Polygons infill_area_here; for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) for (const Surface& surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal) + if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_area_here, infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); //Remove the part of the infill area that is already supported by the walls. @@ -92,7 +92,7 @@ void Generator::generateTrees(const PrintObject &print_object) for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal) + if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_outlines[layer_id], infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); // For various operations its beneficial to quickly locate nearby features on the polygon: @@ -116,8 +116,12 @@ void Generator::generateTrees(const PrintObject &print_object) if (layer_id == 0) return; - const Polygons& below_outlines = infill_outlines[layer_id - 1]; - outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON)); + const Polygons &below_outlines = infill_outlines[layer_id - 1]; + BoundingBox below_outlines_bbox = get_extents(below_outlines).inflated(SCALED_EPSILON); + if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined) + below_outlines_bbox.merge(outlines_locator_bbox); + + outlines_locator.set_bbox(below_outlines_bbox); outlines_locator.create(below_outlines, locator_cell_size); std::vector& lower_trees = m_lightning_layers[layer_id - 1].tree_roots; diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index 1e1127a792..c996b9b7b4 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -6,6 +6,7 @@ #include "DistanceField.hpp" #include "TreeNode.hpp" +#include "../../ClipperUtils.hpp" #include "../../Geometry.hpp" #include "Utils.hpp" @@ -271,6 +272,7 @@ void Layer::reconnectRoots } } +#if 0 /*! * Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance. * Given a \p distance more than zero, the point will end up inside, and conversely outside. @@ -398,6 +400,7 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan } return static_cast(-1); } +#endif // Returns 'added someting'. Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const @@ -405,31 +408,11 @@ Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t if (tree_roots.empty()) return {}; - Polygons result_lines; - for (const auto& tree : tree_roots) { - // If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon. - // (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.) - constexpr coord_t epsilon = 5; - Point should_be_inside = tree->getLocation(); - moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon); - if (inside(limit_to_outline, should_be_inside)) - tree->convertToPolylines(result_lines, line_width); - } + Polylines result_lines; + for (const auto &tree : tree_roots) + tree->convertToPolylines(result_lines, line_width); - // TODO: allow for polylines! - Polylines split_lines; - for (Polygon &line : result_lines) { - if (line.size() <= 1) - continue; - Point last = line[0]; - for (size_t point_idx = 1; point_idx < line.size(); point_idx++) { - Point here = line[point_idx]; - split_lines.push_back({ last, here }); - last = here; - } - } - - return split_lines; + return intersection_pl(result_lines, limit_to_outline); } } // namespace Slic3r::Lightning diff --git a/src/libslic3r/Fill/Lightning/TreeNode.cpp b/src/libslic3r/Fill/Lightning/TreeNode.cpp index d1820410ee..822550fc49 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.cpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.cpp @@ -343,16 +343,16 @@ coord_t Node::prune(const coord_t& pruning_distance) return max_distance_pruned; } -void Node::convertToPolylines(Polygons& output, const coord_t line_width) const +void Node::convertToPolylines(Polylines &output, const coord_t line_width) const { - Polygons result; + Polylines result; result.emplace_back(); convertToPolylines(0, result); removeJunctionOverlap(result, line_width); append(output, std::move(result)); } -void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const +void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const { if (m_children.empty()) { output[long_line_idx].points.push_back(m_p); @@ -372,12 +372,12 @@ void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const } } -void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const +void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_width) const { const coord_t reduction = line_width / 2; // TODO make configurable? size_t res_line_idx = 0; while (res_line_idx < result_lines.size()) { - Polygon &polyline = result_lines[res_line_idx]; + Polyline &polyline = result_lines[res_line_idx]; if (polyline.size() <= 1) { polyline = std::move(result_lines.back()); result_lines.pop_back(); @@ -387,7 +387,7 @@ void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_widt coord_t to_be_reduced = reduction; Point a = polyline.back(); for (int point_idx = int(polyline.size()) - 2; point_idx >= 0; point_idx--) { - const Point b = polyline[point_idx]; + const Point b = polyline.points[point_idx]; const Point ab = b - a; const auto ab_len = coord_t(ab.cast().norm()); if (ab_len >= to_be_reduced) { diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index 55ef35e0a8..fdb80d2e6f 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -239,7 +239,7 @@ public: * * \param output all branches in this tree connected into polylines */ - void convertToPolylines(Polygons& output, coord_t line_width) const; + void convertToPolylines(Polylines &output, coord_t line_width) const; /*! If this was ever a direct child of the root, it'll have a previous grounding location. * @@ -258,9 +258,9 @@ protected: * \param long_line a reference to a polyline in \p output which to continue building on in the recursion * \param output all branches in this tree connected into polylines */ - void convertToPolylines(size_t long_line_idx, Polygons& output) const; + void convertToPolylines(size_t long_line_idx, Polylines &output) const; - void removeJunctionOverlap(Polygons& polylines, coord_t line_width) const; + void removeJunctionOverlap(Polylines &polylines, coord_t line_width) const; bool m_is_root; Point m_p; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1a6ee4b807..aef83f21f4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3063,7 +3063,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) if (role == erSupportMaterial) { const SupportLayer* support_layer = dynamic_cast(m_layer); //FIXME support_layer->support_islands.contains should use some search structure! - if (support_layer != NULL && support_layer->support_islands.contains(travel)) + if (support_layer != NULL && ! intersection_pl(travel, support_layer->support_islands).empty()) // skip retraction if this is a travel move inside a support material island //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material // at the end of the extrusion path! diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 1fe0405184..91a81c7f31 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -26,6 +26,8 @@ public: void reset(const Vec3d &position); void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); + std::string process_layer(const std::string &gcode, size_t layer_id, bool flush) + { return this->process_layer(std::string(gcode), layer_id, flush); } private: CoolingBuffer& operator=(const CoolingBuffer&) = delete; diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index c8a9b790fc..44b0b89f60 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -215,9 +215,9 @@ bool GCodeReader::parse_file_raw(const std::string &filename, raw_line_callback_ [](size_t){}); } -bool GCodeReader::GCodeLine::has(char axis) const +const char* GCodeReader::axis_pos(const char *raw_str, char axis) { - const char *c = m_raw.c_str(); + const char *c = raw_str; // Skip the whitespaces. c = skip_whitespaces(c); // Skip the command. @@ -230,40 +230,48 @@ bool GCodeReader::GCodeLine::has(char axis) const break; // Check the name of the axis. if (*c == axis) - return true; + return c; // Skip the rest of the word. c = skip_word(c); } - return false; + return nullptr; +} + +bool GCodeReader::GCodeLine::has(char axis) const +{ + const char *c = axis_pos(m_raw.c_str(), axis); + return c != nullptr; } bool GCodeReader::GCodeLine::has_value(char axis, float &value) const { assert(is_decimal_separator_point()); - const char *c = m_raw.c_str(); - // Skip the whitespaces. - c = skip_whitespaces(c); - // Skip the command. - c = skip_word(c); - // Up to the end of line or comment. - while (! is_end_of_gcode_line(*c)) { - // Skip whitespaces. - c = skip_whitespaces(c); - if (is_end_of_gcode_line(*c)) - break; - // Check the name of the axis. - if (*c == axis) { - // Try to parse the numeric value. - char *pend = nullptr; - double v = strtod(++ c, &pend); - if (pend != nullptr && is_end_of_word(*pend)) { - // The axis value has been parsed correctly. - value = float(v); - return true; - } - } - // Skip the rest of the word. - c = skip_word(c); + const char *c = axis_pos(m_raw.c_str(), axis); + if (c == nullptr) + return false; + // Try to parse the numeric value. + char *pend = nullptr; + double v = strtod(++ c, &pend); + if (pend != nullptr && is_end_of_word(*pend)) { + // The axis value has been parsed correctly. + value = float(v); + return true; + } + return false; +} + +bool GCodeReader::GCodeLine::has_value(char axis, int &value) const +{ + const char *c = axis_pos(m_raw.c_str(), axis); + if (c == nullptr) + return false; + // Try to parse the numeric value. + char *pend = nullptr; + long v = strtol(++ c, &pend, 10); + if (pend != nullptr && is_end_of_word(*pend)) { + // The axis value has been parsed correctly. + value = int(v); + return true; } return false; } diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 0ab268139f..25ba6ee0ba 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -30,11 +30,14 @@ public: float value(Axis axis) const { return m_axis[axis]; } bool has(char axis) const; bool has_value(char axis, float &value) const; + bool has_value(char axis, int &value) const; float new_X(const GCodeReader &reader) const { return this->has(X) ? this->x() : reader.x(); } float new_Y(const GCodeReader &reader) const { return this->has(Y) ? this->y() : reader.y(); } float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); } float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); } float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); } + Point new_XY_scaled(const GCodeReader &reader) const + { return Point::new_scale(this->new_X(reader), this->new_Y(reader)); } float dist_X(const GCodeReader &reader) const { return this->has(X) ? (this->x() - reader.x()) : 0; } float dist_Y(const GCodeReader &reader) const { return this->has(Y) ? (this->y() - reader.y()) : 0; } float dist_Z(const GCodeReader &reader) const { return this->has(Z) ? (this->z() - reader.z()) : 0; } @@ -134,6 +137,8 @@ public: float e() const { return m_position[E]; } float& f() { return m_position[F]; } float f() const { return m_position[F]; } + Point xy_scaled() const { return Point::new_scale(this->x(), this->y()); } + // Returns 0 for gcfNoExtrusion. char extrusion_axis() const { return m_extrusion_axis; } @@ -162,6 +167,7 @@ private: ; // silence -Wempty-body return c; } + static const char* axis_pos(const char *raw_str, char axis); GCodeConfig m_config; char m_extrusion_axis; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 38f5f5fdb4..58c90d9bc8 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -50,13 +50,6 @@ bool contains(const std::vector &vector, const Point &point) } template bool contains(const ExPolygons &vector, const Point &point); -double rad2deg_dir(double angle) -{ - angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0); - if (angle < 0) angle += PI; - return rad2deg(angle); -} - void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) { Polygons pp; diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 82ffbd8d19..2ca4ef8844 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -291,7 +291,6 @@ bool directions_parallel(double angle1, double angle2, double max_diff = 0); bool directions_perpendicular(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } -double rad2deg_dir(double angle); template constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); } template T angle_to_0_2PI(T angle) { diff --git a/src/libslic3r/Geometry/ConvexHull.cpp b/src/libslic3r/Geometry/ConvexHull.cpp index 2e92535f2d..9601069b5e 100644 --- a/src/libslic3r/Geometry/ConvexHull.cpp +++ b/src/libslic3r/Geometry/ConvexHull.cpp @@ -104,6 +104,17 @@ Polygon convex_hull(const Polygons &polygons) return convex_hull(std::move(pp)); } +Polygon convex_hull(const ExPolygons &expolygons) +{ + Points pp; + size_t sz = 0; + for (const auto &expoly : expolygons) + sz += expoly.contour.size(); + pp.reserve(sz); + for (const auto &expoly : expolygons) + pp.insert(pp.end(), expoly.contour.points.begin(), expoly.contour.points.end()); + return convex_hull(pp); +} namespace rotcalip { diff --git a/src/libslic3r/Geometry/ConvexHull.hpp b/src/libslic3r/Geometry/ConvexHull.hpp index 03f00af6ae..94f4e4cf21 100644 --- a/src/libslic3r/Geometry/ConvexHull.hpp +++ b/src/libslic3r/Geometry/ConvexHull.hpp @@ -1,14 +1,21 @@ #ifndef slic3r_Geometry_ConvexHull_hpp_ #define slic3r_Geometry_ConvexHull_hpp_ +#include + #include "../Polygon.hpp" namespace Slic3r { + +class ExPolygon; +using ExPolygons = std::vector; + namespace Geometry { Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); +Polygon convex_hull(const ExPolygons &expolygons); // Returns true if the intersection of the two convex polygons A and B // is not an empty set. diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 0fe4952f4a..2e3affec7b 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -5,10 +5,11 @@ #include "Flow.hpp" #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" -#include "ExPolygonCollection.hpp" namespace Slic3r { +class ExPolygon; +using ExPolygons = std::vector; class Layer; using LayerPtrs = std::vector; class LayerRegion; @@ -191,7 +192,7 @@ class SupportLayer : public Layer public: // Polygons covered by the supports: base, interface and contact areas. // Used to suppress retraction if moving for a support extrusion over these support_islands. - ExPolygonCollection support_islands; + ExPolygons support_islands; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 1255d5473d..6994ef4251 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -2,7 +2,6 @@ #include "Polyline.hpp" #include "Exception.hpp" #include "ExPolygon.hpp" -#include "ExPolygonCollection.hpp" #include "Line.hpp" #include "Polygon.hpp" #include @@ -196,7 +195,7 @@ BoundingBox get_extents(const Polylines &polylines) const Point& leftmost_point(const Polylines &polylines) { if (polylines.empty()) - throw Slic3r::InvalidArgument("leftmost_point() called on empty PolylineCollection"); + throw Slic3r::InvalidArgument("leftmost_point() called on empty Polylines"); Polylines::const_iterator it = polylines.begin(); const Point *p = &it->leftmost_point(); for (++ it; it != polylines.end(); ++it) { diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 256dca28cc..e0379e8697 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -80,15 +80,6 @@ public: inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; } inline bool operator!=(const Polyline &lhs, const Polyline &rhs) { return lhs.points != rhs.points; } -// Don't use this class in production code, it is used exclusively by the Perl binding for unit tests! -#ifdef PERL_UCHAR_MIN -class PolylineCollection -{ -public: - Polylines polylines; -}; -#endif /* PERL_UCHAR_MIN */ - extern BoundingBox get_extents(const Polyline &polyline); extern BoundingBox get_extents(const Polylines &polylines); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 950816fa91..ab12bc8ce2 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -197,7 +197,6 @@ double min_object_distance(const ConfigBase &cfg); // The dynamic configuration is also used to store user modifications of the print global parameters, // so the modified configuration values may be diffed against the active configuration // to invalidate the proper slicing resp. g-code generation processing steps. -// This object is mapped to Perl as Slic3r::Config. class DynamicPrintConfig : public DynamicConfig { public: @@ -211,6 +210,16 @@ public: DynamicPrintConfig& operator=(DynamicPrintConfig &&rhs) noexcept { DynamicConfig::operator=(std::move(rhs)); return *this; } static DynamicPrintConfig full_print_config(); + static DynamicPrintConfig full_print_config_with(const t_config_option_key &opt_key, const std::string &str, bool append = false) { + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict(opt_key, str, append); + return config; + } + static DynamicPrintConfig full_print_config_with(std::initializer_list items) { + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict(items); + return config; + } static DynamicPrintConfig* new_from_defaults_keys(const std::vector &keys); // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. @@ -454,7 +463,6 @@ protected: \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ), \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ)) -// This object is mapped to Perl as Slic3r::Config::PrintObject. PRINT_CONFIG_CLASS_DEFINE( PrintObjectConfig, @@ -518,7 +526,6 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, wipe_into_objects)) ) -// This object is mapped to Perl as Slic3r::Config::PrintRegion. PRINT_CONFIG_CLASS_DEFINE( PrintRegionConfig, @@ -609,7 +616,6 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, machine_min_extruding_rate)) ) -// This object is mapped to Perl as Slic3r::Config::GCode. PRINT_CONFIG_CLASS_DEFINE( GCodeConfig, @@ -695,7 +701,6 @@ static inline std::string get_extrusion_axis(const GCodeConfig &cfg) (cfg.gcode_flavor.value == gcfNoExtrusion) ? "" : cfg.extrusion_axis.value; } -// This object is mapped to Perl as Slic3r::Config::Print. PRINT_CONFIG_CLASS_DERIVED_DEFINE( PrintConfig, (MachineEnvelopeConfig, GCodeConfig), @@ -774,7 +779,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, z_offset)) ) -// This object is mapped to Perl as Slic3r::Config::Full. PRINT_CONFIG_CLASS_DERIVED_DEFINE0( FullPrintConfig, (PrintObjectConfig, PrintRegionConfig, PrintConfig) diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index c32da04319..4c1af03eb4 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -1,17 +1,14 @@ -//#include "igl/random_points_on_mesh.h" -//#include "igl/AABB.h" - #include #include "SupportPointGenerator.hpp" #include "Concurrency.hpp" +#include "Geometry/ConvexHull.hpp" #include "Model.hpp" #include "ExPolygon.hpp" #include "SVG.hpp" #include "Point.hpp" #include "ClipperUtils.hpp" #include "Tesselate.hpp" -#include "ExPolygonCollection.hpp" #include "MinAreaBoundingBox.hpp" #include "libslic3r.h" @@ -550,7 +547,7 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure // auto bb = get_extents(islands); if (flags & icfIsNew) { - auto chull = ExPolygonCollection{islands}.convex_hull(); + auto chull = Geometry::convex_hull(islands); auto rotbox = MinAreaBoundigBox{chull, MinAreaBoundigBox::pcConvex}; Vec2d bbdim = {unscaled(rotbox.width()), unscaled(rotbox.height())}; diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 195fc9e172..52cb177bb8 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -4283,7 +4283,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); } if (! polys.empty()) - expolygons_append(support_layer.support_islands.expolygons, union_ex(polys)); + expolygons_append(support_layer.support_islands, union_ex(polys)); } // for each support_layer_id }); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 01dcd05adb..b6c9f4cad6 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -18,7 +18,6 @@ namespace Slic3r { extern void set_logging_level(unsigned int level); extern unsigned get_logging_level(); -extern void trace(unsigned int level, const char *message); // Format memory allocated, separate thousands by comma. extern std::string format_memsize_MB(size_t n); // Return string to be added to the boost::log output to inform about the current process memory allocation. @@ -68,13 +67,6 @@ std::string debug_out_path(const char *name, ...); // This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded. typedef std::string local_encoded_string; -// Convert an UTF-8 encoded string into local coding. -// On Windows, the UTF-8 string is converted to a local 8-bit code page. -// On OSX and Linux, this function does no conversion and returns a copy of the source string. -extern local_encoded_string encode_path(const char *src); -extern std::string decode_path(const char *src); -extern std::string normalize_utf8_nfc(const char *src); - // Returns next utf8 sequence length. =number of bytes in string, that creates together one utf-8 character. // Starting at pos. ASCII characters returns 1. Works also if pos is in the middle of the sequence. extern size_t get_utf8_sequence_length(const std::string& text, size_t pos = 0); @@ -115,19 +107,6 @@ extern bool is_gallery_file(const boost::filesystem::directory_entry& path, char extern bool is_gallery_file(const std::string& path, char const* type); extern bool is_shapes_dir(const std::string& dir); -// File path / name / extension splitting utilities, working with UTF-8, -// to be published to Perl. -namespace PerlUtils { - // Get a file name including the extension. - extern std::string path_to_filename(const char *src); - // Get a file name without the extension. - extern std::string path_to_stem(const char *src); - // Get just the extension. - extern std::string path_to_extension(const char *src); - // Get a directory without the trailing slash. - extern std::string path_to_parent_path(const char *src); -}; - std::string string_printf(const char *format, ...); // Standard "generated by Slic3r version xxx timestamp xxx" header string, diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index ee7ffa6718..4f7b84d10c 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -125,14 +125,6 @@ static struct RunOnInit { } } g_RunOnInit; -void trace(unsigned int level, const char *message) -{ - boost::log::trivial::severity_level severity = level_to_boost(level); - - BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\ - (::boost::log::keywords::severity = severity)) << message; -} - void disable_multi_threading() { // Disable parallelization so the Shiny profiler works @@ -820,49 +812,6 @@ bool is_shapes_dir(const std::string& dir) namespace Slic3r { -// Encode an UTF-8 string to the local code page. -std::string encode_path(const char *src) -{ -#ifdef WIN32 - // Convert the source utf8 encoded string to a wide string. - std::wstring wstr_src = boost::nowide::widen(src); - if (wstr_src.length() == 0) - return std::string(); - // Convert a wide string to a local code page. - int size_needed = ::WideCharToMultiByte(0, 0, wstr_src.data(), (int)wstr_src.size(), nullptr, 0, nullptr, nullptr); - std::string str_dst(size_needed, 0); - ::WideCharToMultiByte(0, 0, wstr_src.data(), (int)wstr_src.size(), str_dst.data(), size_needed, nullptr, nullptr); - return str_dst; -#else /* WIN32 */ - return src; -#endif /* WIN32 */ -} - -// Encode an 8-bit string from a local code page to UTF-8. -// Multibyte to utf8 -std::string decode_path(const char *src) -{ -#ifdef WIN32 - int len = int(strlen(src)); - if (len == 0) - return std::string(); - // Convert the string encoded using the local code page to a wide string. - int size_needed = ::MultiByteToWideChar(0, 0, src, len, nullptr, 0); - std::wstring wstr_dst(size_needed, 0); - ::MultiByteToWideChar(0, 0, src, len, wstr_dst.data(), size_needed); - // Convert a wide string to utf8. - return boost::nowide::narrow(wstr_dst.c_str()); -#else /* WIN32 */ - return src; -#endif /* WIN32 */ -} - -std::string normalize_utf8_nfc(const char *src) -{ - static std::locale locale_utf8(boost::locale::generator().generate("")); - return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); -} - size_t get_utf8_sequence_length(const std::string& text, size_t pos) { assert(pos < text.size()); @@ -933,18 +882,6 @@ size_t get_utf8_sequence_length(const char *seq, size_t size) return length; } -namespace PerlUtils { - // Get a file name including the extension. - std::string path_to_filename(const char *src) { return boost::filesystem::path(src).filename().string(); } - // Get a file name without the extension. - std::string path_to_stem(const char *src) { return boost::filesystem::path(src).stem().string(); } - // Get just the extension. - std::string path_to_extension(const char *src) { return boost::filesystem::path(src).extension().string(); } - // Get a directory without the trailing slash. - std::string path_to_parent_path(const char *src) { return boost::filesystem::path(src).parent_path().string(); } -}; - - std::string string_printf(const char *format, ...) { va_list args1; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 4ff0898820..86768dfc56 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1281,17 +1281,6 @@ PageMode::PageMode(ConfigWizard *parent) radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); - append(radio_simple); - append(radio_advanced); - append(radio_expert); - - append_text("\n" + _L("The size of the object can be specified in inches")); - check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); - append(check_inch); -} - -void PageMode::on_activate() -{ std::string mode { "simple" }; wxGetApp().app_config->get("", "view_mode", mode); @@ -1299,7 +1288,16 @@ void PageMode::on_activate() else if (mode == "expert") { radio_expert->SetValue(true); } else { radio_simple->SetValue(true); } + append(radio_simple); + append(radio_advanced); + append(radio_expert); + + append_text("\n" + _L("The size of the object can be specified in inches")); + check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); + append(check_inch); + + on_activate(); } void PageMode::serialize_mode(AppConfig *app_config) const @@ -1310,11 +1308,6 @@ void PageMode::serialize_mode(AppConfig *app_config) const if (radio_advanced->GetValue()) { mode = "advanced"; } if (radio_expert->GetValue()) { mode = "expert"; } - // If "Mode" page wasn't selected (no one radiobutton is checked), - // we shouldn't to update a view_mode value in app_config - if (mode.empty()) - return; - app_config->set("view_mode", mode); app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index c822a2be81..4de8381ffe 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -426,8 +426,6 @@ struct PageMode: ConfigWizardPage PageMode(ConfigWizard *parent); void serialize_mode(AppConfig *app_config) const; - - virtual void on_activate(); }; struct PageVendors: ConfigWizardPage diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 993070b453..652410589f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3941,9 +3941,8 @@ void GCodeViewer::render_legend(float& legend_height) PartialTimes items; std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; - int extruders_count = wxGetApp().extruders_edited_cnt(); - std::vector last_color(extruders_count); - for (int i = 0; i < extruders_count; ++i) { + std::vector last_color(m_extruders_count); + for (size_t i = 0; i < m_extruders_count; ++i) { last_color[i] = m_tool_colors[i]; } int last_extruder_id = 1; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f272237e3b..9846ebd0c8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3465,7 +3465,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // during the scene manipulation. #if ENABLE_NEW_RECTANGLE_SELECTION - if (m_picking_enabled && !any_gizmo_active && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) { + if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) { #else if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) { #endif // ENABLE_NEW_RECTANGLE_SELECTION diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 570877716f..cde7a05a22 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -706,6 +706,10 @@ void MainFrame::init_tabpanel() // before the MainFrame is fully set up. tab->OnActivate(); m_last_selected_tab = m_tabpanel->GetSelection(); +#ifdef _MSW_DARK_MODE + if (wxGetApp().tabs_as_menu()) + tab->SetFocus(); +#endif } else select_tab(size_t(0)); // select Plater @@ -2018,8 +2022,13 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) m_plater->SetFocus(); Layout(); } - else + else { select(false); +#ifdef _MSW_DARK_MODE + if (wxGetApp().tabs_as_menu() && tab == 0) + m_plater->SetFocus(); +#endif + } // When we run application in ESettingsLayout::New or ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning // and as a result Tab::update_changed_tree_ui() function couldn't update m_is_nonsys_values values, diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index a861f478f3..257fe2532c 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -338,10 +338,12 @@ void OptionsGroup::activate_line(Line& line) wxBOTTOM | wxTOP | (option.opt.full_width ? int(wxEXPAND) : int(wxALIGN_CENTER_VERTICAL)), (wxOSX || !staticbox) ? 0 : 2); if (is_sizer_field(field)) sizer->Add(field->getSizer(), 1, (option.opt.full_width ? int(wxEXPAND) : int(wxALIGN_CENTER_VERTICAL)), 0); - } + } else + delete sizer; return; } + bool sizer_is_used = false; bool is_multioption_line = option_set.size() > 1; for (auto opt : option_set) { ConfigOptionDef option = opt.opt; @@ -356,8 +358,9 @@ void OptionsGroup::activate_line(Line& line) wxSize(sublabel_width != -1 ? sublabel_width * wxGetApp().em_unit() : -1, -1), wxALIGN_RIGHT); label->SetBackgroundStyle(wxBG_STYLE_PAINT); label->SetFont(wxGetApp().normal_font()); - sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); - } + sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_is_used = true; + } // add field const Option& opt_ref = opt; @@ -412,6 +415,9 @@ void OptionsGroup::activate_line(Line& line) if (!custom_ctrl) sizer->Add(line.extra_widget_sizer, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification } + + if (custom_ctrl && !sizer_is_used) + delete sizer; } // create all controls for the option group from the m_lines diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a8b55c05e6..be91d298d7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3862,6 +3862,9 @@ void Plater::priv::reload_from_disk() old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); sla::reproject_points_and_holes(old_model_object); + + // Fix warning icon in object list + wxGetApp().obj_list()->update_item_error_icon(obj_idx, vol_idx); } } #else diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 75c5c116fd..8ad387367a 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -9,6 +9,7 @@ #include "Notebook.hpp" #include "ButtonsDescription.hpp" #include "OG_CustomCtrl.hpp" +#include "GLCanvas3D.hpp" namespace Slic3r { @@ -54,6 +55,14 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent) : m_highlighter.set_timer_owner(this, 0); } +static void update_color(wxColourPickerCtrl* color_pckr, const wxColour& color) +{ + if (color_pckr->GetColour() != color) { + color_pckr->SetColour(color); + wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED)); + } +} + void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::string()*/, const std::string& tab_name/*= std::string()*/) { int selected_tab = 0; @@ -66,6 +75,14 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin if (!highlight_opt_key.empty()) init_highlighter(highlight_opt_key); + // cache input values for custom toolbar size + m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); + m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; + + // update colors for color pickers + update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); + update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + this->ShowModal(); } @@ -173,6 +190,10 @@ void PreferencesDialog::build() // Add "General" tab m_optgroup_general = create_options_tab(L("General"), tabs); m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project") m_values[opt_key] = boost::any_cast(value) ? "none" : "discard"; else if (opt_key == "default_action_on_dirty_project") @@ -335,6 +356,10 @@ void PreferencesDialog::build() // Add "Camera" tab m_optgroup_camera = create_options_tab(L("Camera"), tabs); m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key);it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -358,25 +383,42 @@ void PreferencesDialog::build() // Add "GUI" tab m_optgroup_gui = create_options_tab(L("GUI"), tabs); m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "suppress_hyperlinks") - m_values[opt_key] = boost::any_cast(value) ? "1" : ""; - else if (opt_key == "notify_release") { + if (opt_key == "notify_release") { int val_int = boost::any_cast(value); for (const auto& item : s_keys_map_NotifyReleaseMode) { if (item.second == val_int) { m_values[opt_key] = item.first; - break; + return; } } - } else - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - + } if (opt_key == "use_custom_toolbar_size") { m_icon_size_sizer->ShowItems(boost::any_cast(value)); - m_optgroup_gui->parent()->Layout(); - tabs->Layout(); - this->layout(); + refresh_og(m_optgroup_gui); + get_app_config()->set("use_custom_toolbar_size", boost::any_cast(value) ? "1" : "0"); + get_app_config()->save(); + wxGetApp().plater()->get_current_canvas3D()->render(); + return; } + if (opt_key == "tabs_as_menu") { + bool disable_new_layout = boost::any_cast(value); + m_rb_new_settings_layout_mode->Show(!disable_new_layout); + if (disable_new_layout && m_rb_new_settings_layout_mode->GetValue()) { + m_rb_new_settings_layout_mode->SetValue(false); + m_rb_old_settings_layout_mode->SetValue(true); + } + refresh_og(m_optgroup_gui); + } + + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } + + if (opt_key == "suppress_hyperlinks") + m_values[opt_key] = boost::any_cast(value) ? "1" : ""; + else + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; append_bool_option(m_optgroup_gui, "seq_top_layer_only", @@ -464,6 +506,10 @@ void PreferencesDialog::build() // Add "Render" tab m_optgroup_render = create_options_tab(L("Render"), tabs); m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -479,6 +525,10 @@ void PreferencesDialog::build() // Add "Dark Mode" tab m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs); m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -509,6 +559,7 @@ void PreferencesDialog::build() auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK); + this->Bind(wxEVT_BUTTON, &PreferencesDialog::revert, this, wxID_CANCEL); for (int id : {wxID_OK, wxID_CANCEL}) wxGetApp().UpdateDarkUI(static_cast(FindWindowById(id, this))); @@ -592,19 +643,6 @@ void PreferencesDialog::accept(wxEvent&) } } - for (const std::string& key : { "default_action_on_close_application", - "default_action_on_select_preset", - "default_action_on_new_project" }) { - auto it = m_values.find(key); - if (it != m_values.end() && it->second != "none" && app_config->get(key) != "none") - m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected - } - { - auto it = m_values.find("default_action_on_dirty_project"); - if (it != m_values.end() && !it->second.empty() && !app_config->get("default_action_on_dirty_project").empty()) - m_values.erase(it); // we shouldn't change value, if this parameter was selected, and then deselected - } - #if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application if (m_values.find("always_dark_color_mode") != m_values.end()) wxGetApp().force_sys_colors_update(); @@ -629,11 +667,84 @@ void PreferencesDialog::accept(wxEvent&) wxGetApp().force_menu_update(); #endif //_MSW_DARK_MODE #endif // _WIN32 - if (m_settings_layout_changed) - ;// application will be recreated after Preference dialog will be destroyed - else - // Nothify the UI to update itself from the ini file. - wxGetApp().update_ui_from_settings(); + + wxGetApp().update_ui_from_settings(); + clear_cache(); +} + +void PreferencesDialog::revert(wxEvent&) +{ + auto app_config = get_app_config(); + + bool save_app_config = false; + if (m_custom_toolbar_size != atoi(app_config->get("custom_toolbar_size").c_str())) { + app_config->set("custom_toolbar_size", (boost::format("%d") % m_custom_toolbar_size).str()); + m_icon_size_slider->SetValue(m_custom_toolbar_size); + save_app_config |= true; + } + if (m_use_custom_toolbar_size != (get_app_config()->get("use_custom_toolbar_size") == "1")) { + app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0"); + save_app_config |= true; + + m_optgroup_gui->set_value("use_custom_toolbar_size", m_use_custom_toolbar_size); + m_icon_size_sizer->ShowItems(m_use_custom_toolbar_size); + refresh_og(m_optgroup_gui); + } + if (save_app_config) + app_config->save(); + + + for (auto value : m_values) { + bool reverted = false; + const std::string& key = value.first; + + if (key == "default_action_on_dirty_project") { + m_optgroup_general->set_value(key, app_config->get(key).empty()); + continue; + } + if (key == "default_action_on_close_application" || key == "default_action_on_select_preset" || key == "default_action_on_new_project") { + m_optgroup_general->set_value(key, app_config->get(key) == "none"); + continue; + } + if (key == "notify_release") { + m_optgroup_gui->set_value(key, s_keys_map_NotifyReleaseMode.at(app_config->get(key))); + continue; + } + if (key == "old_settings_layout_mode") { + m_rb_old_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + if (key == "new_settings_layout_mode") { + m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + if (key == "dlg_settings_layout_mode") { + m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + + for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui +#ifdef _WIN32 + , m_optgroup_dark_mode +#endif // _WIN32 +#if ENABLE_ENVIRONMENT_MAP + , m_optgroup_render +#endif // ENABLE_ENVIRONMENT_MAP + }) { + if (reverted = opt_group->set_value(key, app_config->get(key) == "1")) + break; + } + if (!reverted) + int i=0; + if (key == "tabs_as_menu") { + m_rb_new_settings_layout_mode->Show(app_config->get(key) != "1"); + refresh_og(m_optgroup_gui); + continue; + } + } + + clear_cache(); + EndModal(wxID_CANCEL); } void PreferencesDialog::msw_rescale() @@ -669,6 +780,19 @@ void PreferencesDialog::layout() Refresh(); } +void PreferencesDialog::clear_cache() +{ + m_values.clear(); + m_custom_toolbar_size = -1; +} + +void PreferencesDialog::refresh_og(std::shared_ptr og) +{ + og->parent()->Layout(); + tabs->Layout(); + this->layout(); +} + void PreferencesDialog::create_icon_size_slider() { const auto app_config = get_app_config(); @@ -695,14 +819,14 @@ void PreferencesDialog::create_icon_size_slider() if (!isOSX) style |= wxSL_LABELS | wxSL_AUTOTICKS; - auto slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, + m_icon_size_slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, wxDefaultPosition, wxDefaultSize, style); - slider->SetTickFreq(10); - slider->SetPageSize(10); - slider->SetToolTip(_L("Select toolbar icon size in respect to the default one.")); + m_icon_size_slider->SetTickFreq(10); + m_icon_size_slider->SetPageSize(10); + m_icon_size_slider->SetToolTip(_L("Select toolbar icon size in respect to the default one.")); - m_icon_size_sizer->Add(slider, 1, wxEXPAND); + m_icon_size_sizer->Add(m_icon_size_slider, 1, wxEXPAND); wxStaticText* val_label{ nullptr }; if (isOSX) { @@ -710,15 +834,18 @@ void PreferencesDialog::create_icon_size_slider() m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em); } - slider->Bind(wxEVT_SLIDER, ([this, slider, val_label](wxCommandEvent e) { - auto val = slider->GetValue(); - m_values["custom_toolbar_size"] = (boost::format("%d") % val).str(); + m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label, app_config](wxCommandEvent e) { + auto val = m_icon_size_slider->GetValue(); + + app_config->set("custom_toolbar_size", (boost::format("%d") % val).str()); + app_config->save(); + wxGetApp().plater()->get_current_canvas3D()->render(); if (val_label) val_label->SetLabelText(wxString::Format("%d", val)); - }), slider->GetId()); + }), m_icon_size_slider->GetId()); - for (wxWindow* win : std::vector{ slider, label, val_label }) { + for (wxWindow* win : std::vector{ m_icon_size_slider, label, val_label }) { if (!win) continue; win->SetFont(wxGetApp().normal_font()); @@ -731,26 +858,6 @@ void PreferencesDialog::create_icon_size_slider() void PreferencesDialog::create_settings_mode_widget() { -#ifdef _MSW_DARK_MODE - bool disable_new_layout = wxGetApp().tabs_as_menu(); -#endif - std::vector choices = { _L("Old regular layout with the tab bar"), - _L("New layout, access via settings button in the top menu"), - _L("Settings in non-modal window") }; - - auto app_config = get_app_config(); - int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : - app_config->get("new_settings_layout_mode") == "1" ? 1 : - app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0; - -#ifdef _MSW_DARK_MODE - if (disable_new_layout) { - choices = { _L("Old regular layout with the tab bar"), - _L("Settings in non-modal window") }; - selection = app_config->get("dlg_settings_layout_mode") == "1" ? 1 : 0; - } -#endif - wxWindow* parent = m_optgroup_gui->parent(); wxGetApp().UpdateDarkUI(parent); @@ -762,32 +869,35 @@ void PreferencesDialog::create_settings_mode_widget() wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); - int id = 0; - for (const wxString& label : choices) { - wxRadioButton* btn = new wxRadioButton(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id==0 ? wxRB_GROUP : 0); - stb_sizer->Add(btn); - btn->SetValue(id == selection); - - int dlg_id = 2; -#ifdef _MSW_DARK_MODE - if (disable_new_layout) - dlg_id = 1; -#endif - - btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id -#ifdef _MSW_DARK_MODE - , disable_new_layout -#endif - ](wxCommandEvent& ) { - m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; -#ifdef _MSW_DARK_MODE - if (!disable_new_layout) -#endif - m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; - m_values["dlg_settings_layout_mode"] = (id == dlg_id) ? "1" : "0"; + auto app_config = get_app_config(); + std::vector choices = { _L("Old regular layout with the tab bar"), + _L("New layout, access via settings button in the top menu"), + _L("Settings in non-modal window") }; + int id = -1; + auto add_radio = [this, parent, stb_sizer, choices](wxRadioButton** rb, int id, bool select) { + *rb = new wxRadioButton(parent, wxID_ANY, choices[id], wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0); + stb_sizer->Add(*rb); + (*rb)->SetValue(select); + (*rb)->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { + m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; + m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; + m_values["dlg_settings_layout_mode"] = (id == 2) ? "1" : "0"; }); - id++; + }; + + add_radio(&m_rb_old_settings_layout_mode, ++id, app_config->get("old_settings_layout_mode") == "1"); + add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get("new_settings_layout_mode") == "1"); + add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get("dlg_settings_layout_mode") == "1"); + +#ifdef _MSW_DARK_MODE + if (app_config->get("tabs_as_menu") == "1") { + m_rb_new_settings_layout_mode->Hide(); + if (m_rb_new_settings_layout_mode->GetValue()) { + m_rb_new_settings_layout_mode->SetValue(false); + m_rb_old_settings_layout_mode->SetValue(true); + } } +#endif std::string opt_key = "settings_layout_mode"; m_blinkers[opt_key] = new BlinkingBitmap(parent); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index f8b1d1237a..4a82cee003 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -12,6 +12,8 @@ class wxColourPickerCtrl; class wxBookCtrlBase; +class wxSlider; +class wxRadioButton; namespace Slic3r { @@ -39,6 +41,11 @@ class PreferencesDialog : public DPIDialog std::shared_ptr m_optgroup_render; #endif // ENABLE_ENVIRONMENT_MAP wxSizer* m_icon_size_sizer; + wxSlider* m_icon_size_slider {nullptr}; + wxRadioButton* m_rb_old_settings_layout_mode {nullptr}; + wxRadioButton* m_rb_new_settings_layout_mode {nullptr}; + wxRadioButton* m_rb_dlg_settings_layout_mode {nullptr}; + wxColourPickerCtrl* m_sys_colour {nullptr}; wxColourPickerCtrl* m_mod_colour {nullptr}; wxBookCtrlBase* tabs {nullptr}; @@ -48,6 +55,9 @@ class PreferencesDialog : public DPIDialog bool m_seq_top_layer_only_changed{ false }; bool m_recreate_GUI{false}; + int m_custom_toolbar_size{-1}; + bool m_use_custom_toolbar_size{false}; + public: explicit PreferencesDialog(wxWindow* paren); ~PreferencesDialog() = default; @@ -58,6 +68,7 @@ public: void build(); void update_ctrls_alignment(); void accept(wxEvent&); + void revert(wxEvent&); void show(const std::string& highlight_option = std::string(), const std::string& tab_name = std::string()); protected: @@ -65,6 +76,8 @@ protected: void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); } void on_sys_color_changed() override; void layout(); + void clear_cache(); + void refresh_og(std::shared_ptr og); void create_icon_size_slider(); void create_settings_mode_widget(); void create_settings_text_color_widget(); diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index b53fe5c474..a09d158821 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -490,8 +490,11 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector& vector) auto* app_config = wxGetApp().app_config; auto hasget = [app_config](const std::string& name, std::vector& vector)->bool { if (app_config->has(name)) { - vector.push_back(std::stoi(app_config->get(name))); - return true; + std::string val = app_config->get(name); + if (!val.empty() || val[0]!='\0') { + vector.push_back(std::stoi(val)); + return true; + } } return false; }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 86394819e7..96151d8c7f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3481,6 +3481,28 @@ void Tab::activate_selected_page(std::function throw_if_canceled) toggle_options(); } +#ifdef WIN32 +// Override the wxCheckForInterrupt to process inperruptions just from key or mouse +// and to avoid an unwanted early call of CallAfter() +static bool CheckForInterrupt(wxWindow* wnd) +{ + wxCHECK(wnd, false); + + MSG msg; + while (::PeekMessage(&msg, ((HWND)((wnd)->GetHWND())), WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + while (::PeekMessage(&msg, ((HWND)((wnd)->GetHWND())), WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + return true; +} +#endif //WIN32 + bool Tab::tree_sel_change_delayed() { // There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/PrusaSlicer/issues/898 and https://github.com/prusa3d/PrusaSlicer/issues/952. @@ -3517,7 +3539,7 @@ bool Tab::tree_sel_change_delayed() auto throw_if_canceled = std::function([this](){ #ifdef WIN32 - wxCheckForInterrupt(m_treectrl); + CheckForInterrupt(m_treectrl); if (m_page_switch_planned) throw UIBuildCanceled(); #else // WIN32 diff --git a/t/angles.t b/t/angles.t deleted file mode 100644 index 9dc690dea7..0000000000 --- a/t/angles.t +++ /dev/null @@ -1,93 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 34; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Geometry qw(rad2deg_dir angle3points PI); - -#========================================================== - -{ - is line_atan([ [0, 0], [10, 0] ]), (0), 'E atan2'; - is line_atan([ [10, 0], [0, 0] ]), (PI), 'W atan2'; - is line_atan([ [0, 0], [0, 10] ]), (PI/2), 'N atan2'; - is line_atan([ [0, 10], [0, 0] ]), -(PI/2), 'S atan2'; - - is line_atan([ [10, 10], [0, 0] ]), -(PI*3/4), 'SW atan2'; - is line_atan([ [0, 0], [10, 10] ]), (PI*1/4), 'NE atan2'; - is line_atan([ [0, 10], [10, 0] ]), -(PI*1/4), 'SE atan2'; - is line_atan([ [10, 0], [0, 10] ]), (PI*3/4), 'NW atan2'; -} - -#========================================================== - -{ - is line_orientation([ [0, 0], [10, 0] ]), (0), 'E orientation'; - is line_orientation([ [0, 0], [0, 10] ]), (PI/2), 'N orientation'; - is line_orientation([ [10, 0], [0, 0] ]), (PI), 'W orientation'; - is line_orientation([ [0, 10], [0, 0] ]), (PI*3/2), 'S orientation'; - - is line_orientation([ [0, 0], [10, 10] ]), (PI*1/4), 'NE orientation'; - is line_orientation([ [10, 0], [0, 10] ]), (PI*3/4), 'NW orientation'; - is line_orientation([ [10, 10], [0, 0] ]), (PI*5/4), 'SW orientation'; - is line_orientation([ [0, 10], [10, 0] ]), (PI*7/4), 'SE orientation'; -} - -#========================================================== - -{ - is line_direction([ [0, 0], [10, 0] ]), (0), 'E direction'; - is line_direction([ [10, 0], [0, 0] ]), (0), 'W direction'; - is line_direction([ [0, 0], [0, 10] ]), (PI/2), 'N direction'; - is line_direction([ [0, 10], [0, 0] ]), (PI/2), 'S direction'; - - is line_direction([ [10, 10], [0, 0] ]), (PI*1/4), 'SW direction'; - is line_direction([ [0, 0], [10, 10] ]), (PI*1/4), 'NE direction'; - is line_direction([ [0, 10], [10, 0] ]), (PI*3/4), 'SE direction'; - is line_direction([ [10, 0], [0, 10] ]), (PI*3/4), 'NW direction'; -} - -#========================================================== - -{ - is rad2deg_dir(0), 90, 'E (degrees)'; - is rad2deg_dir(PI), 270, 'W (degrees)'; - is rad2deg_dir(PI/2), 0, 'N (degrees)'; - is rad2deg_dir(-(PI/2)), 180, 'S (degrees)'; - is rad2deg_dir(PI*1/4), 45, 'NE (degrees)'; - is rad2deg_dir(PI*3/4), 135, 'NW (degrees)'; - is rad2deg_dir(PI/6), 60, '30°'; - is rad2deg_dir(PI/6*2), 30, '60°'; -} - -#========================================================== - -{ - is angle3points([0,0], [10,0], [0,10]), PI/2, 'CW angle3points'; - is angle3points([0,0], [0,10], [10,0]), PI/2*3, 'CCW angle3points'; -} - -#========================================================== - -sub line_atan { - my ($l) = @_; - return Slic3r::Line->new(@$l)->atan2_; -} - -sub line_orientation { - my ($l) = @_; - return Slic3r::Line->new(@$l)->orientation; -} - -sub line_direction { - my ($l) = @_; - return Slic3r::Line->new(@$l)->direction; -} \ No newline at end of file diff --git a/t/avoid_crossing_perimeters.t b/t/avoid_crossing_perimeters.t deleted file mode 100644 index 86c3e91cb9..0000000000 --- a/t/avoid_crossing_perimeters.t +++ /dev/null @@ -1,22 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-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__ diff --git a/t/bridges.t b/t/bridges.t deleted file mode 100644 index ca55862b62..0000000000 --- a/t/bridges.t +++ /dev/null @@ -1,137 +0,0 @@ -use Test::More tests => 16; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first sum); -use Slic3r; -use Slic3r::Geometry qw(scale epsilon deg2rad rad2deg); -use Slic3r::Test; - -{ - my $test = sub { - my ($bridge_size, $rotate, $expected_angle, $tolerance) = @_; - - my ($x, $y) = @$bridge_size; - my $lower = Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([-2,-2], [$x+2,-2], [$x+2,$y+2], [-2,$y+2]), - Slic3r::Polygon->new_scale([0,0], [0,$y], [$x,$y], [$x,0]), - ); - $lower->translate(scale 20, scale 20); # avoid negative coordinates for easier SVG preview - $lower->rotate(deg2rad($rotate), [$x/2,$y/2]); - my $bridge = $lower->[1]->clone; - $bridge->reverse; - $bridge = Slic3r::ExPolygon->new($bridge); - - ok check_angle([$lower], $bridge, $expected_angle, $tolerance), 'correct bridge angle for O-shaped overhang'; - }; - - $test->([20,10], 0, 90); - $test->([10,20], 0, 0); - $test->([20,10], 45, 135, 20); - $test->([20,10], 135, 45, 20); -} - -{ - my $bridge = Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [20,0], [20,10], [0,10]), - ); - my $lower = [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([-2,0], [0,0], [0,10], [-2,10]), - ), - ]; - $_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview - - $lower->[1] = $lower->[0]->clone; - $lower->[1]->translate(scale 22, 0); - - ok check_angle($lower, $bridge, 0), 'correct bridge angle for two-sided bridge'; -} - -{ - my $bridge = Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [20,0], [10,10], [0,10]), - ); - my $lower = [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [0,10], [10,10], [10,12], [-2,12], [-2,-2], [22,-2], [22,0]), - ), - ]; - $_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview - - ok check_angle($lower, $bridge, 135), 'correct bridge angle for C-shaped overhang'; -} - -{ - my $bridge = Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([10,10],[20,10],[20,20], [10,20]), - ); - my $lower = [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([10,10],[10,20],[20,20],[30,30],[0,30],[0,0]), - ), - ]; - $_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview - - ok check_angle($lower, $bridge, 45, undef, $bridge->area/2), 'correct bridge angle for square overhang with L-shaped anchors'; -} - -sub check_angle { - my ($lower, $bridge, $expected, $tolerance, $expected_coverage) = @_; - - if (ref($lower) eq 'ARRAY') { - $lower = Slic3r::ExPolygon::Collection->new(@$lower); - } - - $expected_coverage //= -1; - $expected_coverage = $bridge->area if $expected_coverage == -1; - - my $bd = Slic3r::BridgeDetector->new($bridge, $lower, scale 0.5); - - $tolerance //= rad2deg($bd->resolution) + epsilon; - $bd->detect_angle; - my $result = $bd->angle; - my $coverage = $bd->coverage; - is sum(map $_->area, @$coverage), $expected_coverage, 'correct coverage area'; - - # our epsilon is equal to the steps used by the bridge detection algorithm - ###use XXX; YYY [ rad2deg($result), $expected ]; - # returned value must be non-negative, check for that too - my $delta=rad2deg($result) - $expected; - $delta-=180 if $delta>=180 - epsilon; - return defined $result && $result>=0 && abs($delta) < $tolerance; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('top_solid_layers', 0); # to prevent bridging on sparse infill - $config->set('bridge_speed', 99); - - my $print = Slic3r::Test::init_print('bridge', config => $config); - - my %extrusions = (); # angle => length - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && ($args->{F} // $self->F)/60 == $config->bridge_speed) { - my $line = Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - my $angle = $line->direction; - $extrusions{$angle} //= 0; - $extrusions{$angle} += $line->length; - } - }); - ok !!%extrusions, "bridge is generated"; - my ($main_angle) = sort { $extrusions{$b} <=> $extrusions{$a} } keys %extrusions; - is $main_angle, 0, "bridge has the expected direction"; -} - -__END__ diff --git a/t/clean_polylines.t b/t/clean_polylines.t deleted file mode 100644 index 50c6f5bbdb..0000000000 --- a/t/clean_polylines.t +++ /dev/null @@ -1,83 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 6; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; - -{ - my $polyline = Slic3r::Polyline->new( - [0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[0,0], - ); - $polyline->simplify(1); - is_deeply $polyline->pp, [ [0, 0], [2, 0], [2, 2], [0, 2], [0, 0] ], 'Douglas-Peucker'; -} - -{ - my $polyline = Slic3r::Polyline->new( - [0,0], [50,50], [100,0], [125,-25], [150,50], - ); - $polyline->simplify(25); - is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker'; -} - -{ - my $gear = Slic3r::Polygon->new_scale( - [144.9694,317.1543], [145.4181,301.5633], [146.3466,296.921], [131.8436,294.1643], [131.7467,294.1464], - [121.7238,291.5082], [117.1631,290.2776], [107.9198,308.2068], [100.1735,304.5101], [104.9896,290.3672], - [106.6511,286.2133], [93.453,279.2327], [81.0065,271.4171], [67.7886,286.5055], [60.7927,280.1127], - [69.3928,268.2566], [72.7271,264.9224], [61.8152,253.9959], [52.2273,242.8494], [47.5799,245.7224], - [34.6577,252.6559], [30.3369,245.2236], [42.1712,236.3251], [46.1122,233.9605], [43.2099,228.4876], - [35.0862,211.5672], [33.1441,207.0856], [13.3923,212.1895], [10.6572,203.3273], [6.0707,204.8561], - [7.2775,204.4259], [29.6713,196.3631], [25.9815,172.1277], [25.4589,167.2745], [19.8337,167.0129], - [5.0625,166.3346], [5.0625,156.9425], [5.3701,156.9282], [21.8636,156.1628], [25.3713,156.4613], - [25.4243,155.9976], [29.3432,155.8157], [30.3838,149.3549], [26.3596,147.8137], [27.1085,141.2604], - [29.8466,126.8337], [24.5841,124.9201], [10.6664,119.8989], [13.4454,110.9264], [33.1886,116.0691], - [38.817,103.1819], [45.8311,89.8133], [30.4286,76.81], [35.7686,70.0812], [48.0879,77.6873], - [51.564,81.1635], [61.9006,69.1791], [72.3019,58.7916], [60.5509,42.5416], [68.3369,37.1532], - [77.9524,48.1338], [80.405,52.2215], [92.5632,44.5992], [93.0123,44.3223], [106.3561,37.2056], - [100.8631,17.4679], [108.759,14.3778], [107.3148,11.1283], [117.0002,32.8627], [140.9109,27.3974], - [145.7004,26.4994], [145.1346,6.1011], [154.502,5.4063], [156.9398,25.6501], [171.0557,26.2017], - [181.3139,27.323], [186.2377,27.8532], [191.6031,8.5474], [200.6724,11.2756], [197.2362,30.2334], - [220.0789,39.1906], [224.3261,41.031], [236.3506,24.4291], [243.6897,28.6723], [234.2956,46.7747], - [245.6562,55.1643], [257.2523,65.0901], [261.4374,61.5679], [273.1709,52.8031], [278.555,59.5164], - [268.4334,69.8001], [264.1615,72.3633], [268.2763,77.9442], [278.8488,93.5305], [281.4596,97.6332], - [286.4487,95.5191], [300.2821,90.5903], [303.4456,98.5849], [286.4523,107.7253], [293.7063,131.1779], - [294.9748,135.8787], [314.918,133.8172], [315.6941,143.2589], [300.9234,146.1746], [296.6419,147.0309], - [297.1839,161.7052], [296.6136,176.3942], [302.1147,177.4857], [316.603,180.3608], [317.1658,176.7341], - [315.215,189.6589], [315.1749,189.6548], [294.9411,187.5222], [291.13,201.7233], [286.2615,215.5916], - [291.1944,218.2545], [303.9158,225.1271], [299.2384,233.3694], [285.7165,227.6001], [281.7091,225.1956], - [273.8981,237.6457], [268.3486,245.2248], [267.4538,246.4414], [264.8496,250.0221], [268.6392,253.896], - [278.5017,265.2131], [272.721,271.4403], [257.2776,258.3579], [234.4345,276.5687], [242.6222,294.8315], - [234.9061,298.5798], [227.0321,286.2841], [225.2505,281.8301], [211.5387,287.8187], [202.3025,291.0935], - [197.307,292.831], [199.808,313.1906], [191.5298,315.0787], [187.3082,299.8172], [186.4201,295.3766], - [180.595,296.0487], [161.7854,297.4248], [156.8058,297.6214], [154.3395,317.8592], - ); - - my $num_points = scalar @$gear; - my $simplified = $gear->simplify(1000); - ok @$simplified == 1, 'gear simplified to a single polygon'; - ###note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]}); - ok @{$simplified->[0]} < $num_points, 'gear was further simplified using Douglas-Peucker'; -} - -{ - - my $hole_in_square = Slic3r::Polygon->new( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ); - my $simplified = $hole_in_square->simplify(2); - is scalar(@$simplified), 1, 'hole simplification returns one polygon'; - ok $simplified->[0]->is_counter_clockwise, 'hole simplification turns cw polygon into ccw polygon'; -} - diff --git a/t/collinear.t b/t/collinear.t deleted file mode 100644 index b28a3602c5..0000000000 --- a/t/collinear.t +++ /dev/null @@ -1,87 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 11; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Geometry qw(collinear); - -#========================================================== - -{ - my @lines = ( - Slic3r::Line->new([0,4], [4,2]), - Slic3r::Line->new([2,3], [8,0]), - Slic3r::Line->new([6,1], [8,0]), - ); - is collinear($lines[0], $lines[1]), 1, 'collinear'; - is collinear($lines[1], $lines[2]), 1, 'collinear'; - is collinear($lines[0], $lines[2]), 1, 'collinear'; -} - -#========================================================== - -{ - # horizontal - my @lines = ( - Slic3r::Line->new([0,1], [5,1]), - Slic3r::Line->new([2,1], [8,1]), - ); - is collinear($lines[0], $lines[1]), 1, 'collinear'; -} - -#========================================================== - -{ - # vertical - my @lines = ( - Slic3r::Line->new([1,0], [1,5]), - Slic3r::Line->new([1,2], [1,8]), - ); - is collinear($lines[0], $lines[1]), 1, 'collinear'; -} - -#========================================================== - -{ - # non overlapping - my @lines = ( - Slic3r::Line->new([0,1], [5,1]), - Slic3r::Line->new([7,1], [10,1]), - ); - is collinear($lines[0], $lines[1], 1), 0, 'non overlapping'; - is collinear($lines[0], $lines[1], 0), 1, 'overlapping'; -} - -#========================================================== - -{ - # with one common point - my @lines = ( - Slic3r::Line->new([0,4], [4,2]), - Slic3r::Line->new([4,2], [8,0]), - ); - is collinear($lines[0], $lines[1], 1), 1, 'one common point'; - is collinear($lines[0], $lines[1], 0), 1, 'one common point'; -} - -#========================================================== - -{ - # not collinear - my @lines = ( - Slic3r::Line->new([290000000,690525600], [285163380,684761540]), - Slic3r::Line->new([285163380,684761540], [193267599,575244400]), - ); - is collinear($lines[0], $lines[1], 0), 0, 'not collinear'; - is collinear($lines[0], $lines[1], 1), 0, 'not collinear'; -} - -#========================================================== diff --git a/t/combineinfill.t b/t/combineinfill.t index a19e817a14..ebb4304196 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -107,60 +107,4 @@ plan tests => 8; 'infill combination is idempotent'; } -# the following needs to be adapted to the new API -if (0) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('top_solid_layers', 0); - $config->set('infill_every_layers', 6); - $config->set('layer_height', 0.06); - $config->set('perimeters', 1); - - my $test = sub { - my ($shift) = @_; - - my $self = Slic3r::Test::init_print('20mm_cube', config => $config); - - $shift /= &Slic3r::SCALING_FACTOR; - my $scale = 4; # make room for fat infill lines with low layer height - - # Put a slope on the box's sides by shifting x and y coords by $tilt * (z / boxheight). - # The test here is to put such a slight slope on the walls that it should - # not trigger any extra fill on fill layers that should be empty when - # combine infill is enabled. - $_->[0] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; - $_->[1] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; - $_ = [$_->[0]*$scale, $_->[1]*$scale, $_->[2]] for @{$self->objects->[0]->meshes->[0]->vertices}; - - # copy of Print::export_gcode() up to the point - # after fill surfaces are combined - $_->slice for @{$self->objects}; - $_->make_perimeters for @{$self->objects}; - $_->detect_surfaces_type for @{$self->objects}; - $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; - $_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; - $_->discover_horizontal_shells for @{$self->objects}; - $_->combine_infill for @{$self->objects}; - - # Only layers with id % 6 == 0 should have fill. - my $spurious_infill = 0; - foreach my $layer (map @{$_->layers}, @{$self->objects}) { - ++$spurious_infill if ($layer->id % 6 && grep @{$_->fill_surfaces} > 0, @{$layer->regions}); - } - - $spurious_infill -= scalar(@{$self->objects->[0]->layers} - 1) % 6; - - fail "spurious fill surfaces found on layers that should have none (walls " . sprintf("%.4f", Slic3r::Geometry::rad2deg(atan2($shift, 20/&Slic3r::SCALING_FACTOR))) . " degrees off vertical)" - unless $spurious_infill == 0; - 1; - }; - - # Test with mm skew offsets for the top of the 20mm-high box - for my $shift (0, 0.0001, 1) { - ok $test->($shift), "no spurious fill surfaces with box walls " . sprintf("%.4f",Slic3r::Geometry::rad2deg(atan2($shift, 20))) . " degrees off of vertical"; - } -} - __END__ diff --git a/t/config.t b/t/config.t deleted file mode 100644 index f4a1867de6..0000000000 --- a/t/config.t +++ /dev/null @@ -1,20 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('perimeter_extrusion_width', '250%'); - ok $config->validate, 'percent extrusion width is validated'; -} - -__END__ diff --git a/t/cooling.t b/t/cooling.t deleted file mode 100644 index e46cfa2f77..0000000000 --- a/t/cooling.t +++ /dev/null @@ -1,214 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 14; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(none all); -use Slic3r; -use Slic3r::Test; - -my $gcodegen; -sub buffer { - my $config = shift; - if (defined($config)) { - $config = $config->clone(); - } else { - $config = Slic3r::Config->new; - } - my $config_override = shift; - foreach my $key (keys %{$config_override}) { - $config->set($key, ${$config_override}{$key}); - } - - my $print_config = Slic3r::Config::Print->new; - $print_config->apply_dynamic($config); - - $gcodegen = Slic3r::GCode->new; - $gcodegen->apply_print_config($print_config); - $gcodegen->set_layer_count(10); - - my $extruders_ref = shift; - $extruders_ref = [ 0 ] if !defined $extruders_ref; - $gcodegen->set_extruders($extruders_ref); - return Slic3r::GCode::CoolingBuffer->new($gcodegen); -} - -my $gcode1 = "G1 X100 E1 F3000\n"; -my $print_time1 = 100 / (3000 / 60); # 2 sec -my $gcode2 = $gcode1 . "G1 X0 E1 F3000\n"; -my $print_time2 = 2 * $print_time1; # 4 sec - -my $config = Slic3r::Config::new_from_defaults; -# Default cooling settings. -$config->set('bridge_fan_speed', [ 100 ]); -$config->set('cooling', [ 1 ]); -$config->set('fan_always_on', [ 0 ]); -$config->set('fan_below_layer_time', [ 60 ]); -$config->set('max_fan_speed', [ 100 ]); -$config->set('min_print_speed', [ 10 ]); -$config->set('slowdown_below_layer_time', [ 5 ]); -# Default print speeds. -$config->set('bridge_speed', 60); -$config->set('external_perimeter_speed', '50%'); -$config->set('first_layer_speed', 30); -$config->set('gap_fill_speed', 20); -$config->set('infill_speed', 80); -$config->set('perimeter_speed', 60); -$config->set('small_perimeter_speed', 15); -$config->set('solid_infill_speed', 20); -$config->set('top_solid_infill_speed', 15); -$config->set('max_print_speed', 80); -# Override for tests. -$config->set('disable_fan_first_layers', [ 0 ]); - -{ - my $gcode_src = "G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1"; - # Print time of $gcode. - my $print_time = 100 / (3000 / 60); - my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 0.999 ] }); - my $gcode = $buffer->process_layer($gcode_src, 0); - like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; -} - -{ - my $gcode_src = - "G1 X50 F2500\n" . - "G1 F3000;_EXTRUDE_SET_SPEED\n" . - "G1 X100 E1\n" . - ";_EXTRUDE_END\n" . - "G1 E4 F400", - # Print time of $gcode. - my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60); - my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 1.001 ] }); - my $gcode = $buffer->process_layer($gcode_src, 0); - unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold'; - like $gcode, qr/F2500/, 'speed is not altered for travel moves'; - like $gcode, qr/F400/, 'speed is not altered for extruder-only moves'; -} - -{ - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time1 * 0.88 ], - 'slowdown_below_layer_time' => [ $print_time1 * 0.99 ] - }); - my $gcode = $buffer->process_layer($gcode1, 0); - unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; -} - -{ - my $gcode .= buffer($config, { 'slowdown_below_layer_time', [ $print_time2 * 0.99 ] })->process_layer($gcode2, 0); - like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at the same Z'; -} - -{ - # use an elapsed time which is < the threshold but greater than it when summed twice - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time2 * 0.65], - 'slowdown_below_layer_time' => [ $print_time2 * 0.7 ] - }); - my $gcode = $buffer->process_layer($gcode2, 0) . - $buffer->process_layer($gcode2, 1); - unlike $gcode, qr/M106/, 'fan is not activated on all objects printing at different Z'; -} - -{ - # use an elapsed time which is < the threshold even when summed twice - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time2 + 1 ], - 'slowdown_below_layer_time' => [ $print_time2 + 2 ] - }); - my $gcode = $buffer->process_layer($gcode2, 0) . - $buffer->process_layer($gcode2, 1); - like $gcode, qr/M106/, 'fan is activated on all objects printing at different Z'; -} - -{ - my $buffer = buffer($config, { - 'cooling' => [ 1 , 0 ], - 'fan_below_layer_time' => [ $print_time2 + 1, $print_time2 + 1 ], - 'slowdown_below_layer_time' => [ $print_time2 + 2, $print_time2 + 2 ] - }, - [ 0, 1]); - my $gcode = $buffer->process_layer($gcode1 . "T1\nG1 X0 E1 F3000\n", 0); - like $gcode, qr/^M106/, 'fan is activated for the 1st tool'; - like $gcode, qr/.*M107/, 'fan is disabled for the 2nd tool'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('cooling', [ 1 ]); - $config->set('bridge_fan_speed', [ 100 ]); - $config->set('fan_below_layer_time', [ 0 ]); - $config->set('slowdown_below_layer_time', [ 0 ]); - $config->set('bridge_speed', 99); - $config->set('top_solid_layers', 1); # internal bridges use solid_infil speed - $config->set('bottom_solid_layers', 1); # internal bridges use solid_infil speed - - my $print = Slic3r::Test::init_print('overhang', config => $config); - my $fan = 0; - my $fan_with_incorrect_speeds = my $fan_with_incorrect_print_speeds = 0; - my $bridge_with_no_fan = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'M106') { - $fan = $args->{S}; - $fan_with_incorrect_speeds++ if $fan != 255; - } elsif ($cmd eq 'M107') { - $fan = 0; - } elsif ($info->{extruding} && $info->{dist_XY} > 0) { - $fan_with_incorrect_print_speeds++ - if ($fan > 0) && ($args->{F} // $self->F) != 60*$config->bridge_speed; - $bridge_with_no_fan++ - if !$fan && ($args->{F} // $self->F) == 60*$config->bridge_speed; - } - }); - ok !$fan_with_incorrect_speeds, 'bridge fan speed is applied correctly'; - ok !$fan_with_incorrect_print_speeds, 'bridge fan is only turned on for bridges'; - ok !$bridge_with_no_fan, 'bridge fan is turned on for all bridges'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('cooling', [ 1 ]); - $config->set('fan_below_layer_time', [ 0 ]); - $config->set('slowdown_below_layer_time', [ 10 ]); - $config->set('min_print_speed', [ 0 ]); - $config->set('start_gcode', ''); - $config->set('first_layer_speed', '100%'); - $config->set('external_perimeter_speed', 99); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my @layer_times = (0); # in seconds - my %layer_external = (); # z => 1 - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($info->{dist_Z}) { - push @layer_times, 0; - $layer_external{ $args->{Z} } = 0; - } - $layer_times[-1] += abs($info->{dist_XY} || $info->{dist_E} || $info->{dist_Z} || 0) / ($args->{F} // $self->F) * 60; - if ($args->{F} && $args->{F} == $config->external_perimeter_speed*60) { - $layer_external{ $self->Z }++; - } - } - }); - @layer_times = grep $_, @layer_times; - my $all_below = none { $_ < $config->slowdown_below_layer_time->[0] } @layer_times; - ok $all_below, 'slowdown_below_layer_time is honored'; - - # check that all layers have at least one unaltered external perimeter speed -# my $external = all { $_ > 0 } values %layer_external; -# ok $external, 'slowdown_below_layer_time does not alter external perimeters'; -} - -__END__ diff --git a/t/dynamic.t b/t/dynamic.t deleted file mode 100644 index 5d4d3ceb4e..0000000000 --- a/t/dynamic.t +++ /dev/null @@ -1,93 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan skip_all => 'variable-width paths are currently disabled'; -plan tests => 20; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Geometry qw(X Y scale epsilon); -use Slic3r::Surface ':types'; - -sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } - -{ - my $square = Slic3r::ExPolygon->new([ - scale_points [0,0], [10,0], [10,10], [0,10], - ]); - - my @offsets = @{$square->noncollapsing_offset_ex(- scale 5)}; - is scalar @offsets, 1, 'non-collapsing offset'; -} - -{ - local $Slic3r::Config = Slic3r::Config->new( - perimeters => 3, - ); - my $w = 0.7; - my $perimeter_flow = Slic3r::Flow->new( - nozzle_diameter => 0.5, - layer_height => 0.4, - width => $w, - ); - - my $print = Slic3r::Print->new; - my $region = Slic3r::Print::Region->new( - print => $print, - flows => { perimeter => $perimeter_flow }, - ); - push @{$print->regions}, $region; - my $object = Slic3r::Print::Object->new( - print => $print, - size => [1,1], - ); - my $make_layer = sub { - my ($width) = @_; - my $layer = Slic3r::Layer->new( - object => $object, - id => 1, - slices => [ - Slic3r::Surface->new( - surface_type => S_TYPE_INTERNAL, - expolygon => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,$width], [0,$width] ]), - ), - ], - thin_walls => [], - ); - my $layerm = $layer->region(0); - $layer->make_perimeters; - return $layerm; - }; - - my %widths = ( - 1 * $w => { perimeters => 1, gaps => 0 }, - 1.3 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 0.2 * $w)->spacing }, - 1.5 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 0.5 * $w)->spacing }, - 2 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->spacing }, - 2.5 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 1.5 * $w)->spacing }, - 3 * $w => { perimeters => 2, gaps => 0 }, - 4 * $w => { perimeters => 2, gaps => 1, gap_flow_spacing => $perimeter_flow->spacing }, - ); - - foreach my $width (sort keys %widths) { - my $layerm = $make_layer->($width); - is scalar @{$layerm->perimeters}, $widths{$width}{perimeters}, 'right number of perimeters'; - is scalar @{$layerm->thin_fills} ? 1 : 0, $widths{$width}{gaps}, - ($widths{$width}{gaps} ? 'gaps were filled' : 'no gaps detected'); # TODO: we should check the exact number of gaps, but we need a better medial axis algorithm - - my @gaps = map $_, @{$layerm->thin_fills}; - if (@gaps) { - ok +(!first { abs($_->flow_spacing - $widths{$width}{gap_flow_spacing}) > epsilon } @gaps), - 'flow spacing was dynamically adjusted'; - } - } -} - -__END__ diff --git a/t/fill.t b/t/fill.t deleted file mode 100644 index 88cc35801f..0000000000 --- a/t/fill.t +++ /dev/null @@ -1,318 +0,0 @@ -use Test::More; -use strict; -use warnings; - -#plan tests => 43; -# Test of a 100% coverage is off. -plan tests => 19; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first sum); -use Slic3r; -use Slic3r::Geometry qw(X Y scale unscale convex_hull); -use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex); -use Slic3r::Surface qw(:types); -use Slic3r::Test; - -sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } - -{ - my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]); - my $filler = Slic3r::Filler->new_from_type('rectilinear'); - $filler->set_bounding_box($expolygon->bounding_box); - $filler->set_angle(0); - my $surface = Slic3r::Surface->new( - surface_type => S_TYPE_TOP, - expolygon => $expolygon, - ); - my $flow = Slic3r::Flow->new( - width => 0.69, - height => 0.4, - nozzle_diameter => 0.50, - ); - $filler->set_spacing($flow->spacing); - foreach my $angle (0, 45) { - $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); - my $paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); - is scalar @$paths, 1, 'one continuous path'; - } -} - -SKIP: -{ - skip "The FillRectilinear2 does not fill the surface completely", 1; - - my $test = sub { - my ($expolygon, $flow_spacing, $angle, $density) = @_; - - my $filler = Slic3r::Filler->new_from_type('rectilinear'); - $filler->set_bounding_box($expolygon->bounding_box); - $filler->set_angle($angle // 0); - # Adjust line spacing to fill the region. - $filler->set_dont_adjust(0); - $filler->set_link_max_length(scale(1.2*$flow_spacing)); - my $surface = Slic3r::Surface->new( - surface_type => S_TYPE_BOTTOM, - expolygon => $expolygon, - ); - my $flow = Slic3r::Flow->new( - width => $flow_spacing, - height => 0.4, - nozzle_diameter => $flow_spacing, - ); - $filler->set_spacing($flow->spacing); - my $paths = $filler->fill_surface( - $surface, - layer_height => $flow->height, - density => $density // 1, - ); - - # check whether any part was left uncovered - 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 - my $uncovered_filtered = [ grep $_->area > (scale $flow_spacing)**2, @$uncovered ]; - - is scalar(@$uncovered_filtered), 0, 'solid surface is fully filled'; - - if (0 && @$uncovered_filtered) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("uncovered.svg", - no_arrows => 1, - expolygons => [ $expolygon ], - blue_expolygons => [ @$uncovered ], - red_expolygons => [ @$uncovered_filtered ], - polylines => [ @$paths ], - ); - exit; - } - }; - - my $expolygon = Slic3r::ExPolygon->new([ - [6883102, 9598327.01296997], - [6883102, 20327272.01297], - [3116896, 20327272.01297], - [3116896, 9598327.01296997], - ]); - $test->($expolygon, 0.55); - - for (1..20) { - $expolygon->scale(1.05); - $test->($expolygon, 0.55); - } - - $expolygon = Slic3r::ExPolygon->new( - [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] - ); - $test->($expolygon, 0.524341649025257); - - $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); - $test->($expolygon, 0.5, 45, 0.99); # non-solid infill -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([4,0], [10,0], [15,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - 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]), - ); - is_deeply - [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - 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]), - ); - is_deeply - [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); - $config->set('fill_pattern', $pattern); - $config->set('top_fill_pattern', $pattern); - $config->set('bottom_fill_pattern', $pattern); - $config->set('perimeters', 1); - $config->set('skirts', 0); - $config->set('fill_density', 20); - $config->set('layer_height', 0.05); - $config->set('perimeter_extruder', 1); - $config->set('infill_extruder', 2); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); - ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; - my $tool = undef; - my @perimeter_points = my @infill_points = (); - 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) { - if ($tool == $config->perimeter_extruder-1) { - push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } elsif ($tool == $config->infill_extruder-1) { - push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } - } - }); - my $convex_hull = convex_hull(\@perimeter_points); - ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); - $config->set('infill_only_where_needed', 1); - $config->set('bottom_solid_layers', 0); - $config->set('infill_extruder', 2); - $config->set('infill_extrusion_width', 0.5); - $config->set('wipe_into_infill', 0); - $config->set('fill_density', 40); - $config->set('cooling', [ 0 ]); # for preventing speeds from being altered - $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('pyramid', config => $config); - - my $tool = undef; - my @infill_extrusions = (); # array of polylines - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - push @infill_extrusions, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points - - my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]); - return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))}); - }; - - my $tolerance = 5; # mm^2 - - $config->set('solid_infill_below_area', 0); - ok $test->() < $tolerance, - 'no infill is generated when using infill_only_where_needed on a pyramid'; - - $config->set('solid_infill_below_area', 70); - ok abs($test->() - $config->solid_infill_below_area) < $tolerance, - 'infill is only generated under the forced solid shells'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('solid_infill_below_area', 20000000); - $config->set('solid_infill_every_layers', 2); - $config->set('perimeter_speed', 99); - $config->set('external_perimeter_speed', 99); - $config->set('cooling', [ 0 ]); - $config->set('first_layer_speed', '100%'); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my %layers_with_extrusion = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { - if (($args->{F} // $self->F) != $config->perimeter_speed*60) { - $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F); - } - } - }); - - ok !%layers_with_extrusion, - "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('fill_density', 0); - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.35,0.35,0.35,0.35]); - $config->set('infill_extruder', 2); - $config->set('solid_infill_extruder', 2); - $config->set('infill_extrusion_width', 0.52); - $config->set('solid_infill_extrusion_width', 0.52); - $config->set('first_layer_extrusion_width', 0); - - my $print = Slic3r::Test::init_print('A', config => $config); - my %infill = (); # Z => [ Line, Line ... ] - my $tool = undef; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - my $z = 1 * $self->Z; - $infill{$z} ||= []; - push @{$infill{$z}}, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - my $grow_d = scale($config->infill_extrusion_width)/2; - my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); - my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); - my $diff = diff($layer0_infill, $layer1_infill); - $diff = offset2_ex($diff, -$grow_d, +$grow_d); - $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; - is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; -} - -__END__ diff --git a/t/flow.t b/t/flow.t deleted file mode 100644 index 50c4916049..0000000000 --- a/t/flow.t +++ /dev/null @@ -1,83 +0,0 @@ -use Test::More tests => 6; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first sum); -use Slic3r; -use Slic3r::Geometry qw(scale PI); -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 1); - $config->set('brim_width', 2); - $config->set('perimeters', 3); - $config->set('fill_density', 0.4); - $config->set('bottom_solid_layers', 1); - $config->set('first_layer_extrusion_width', 2); - $config->set('first_layer_height', $config->layer_height); - $config->set('filament_diameter', [ 3.0 ]); - $config->set('nozzle_diameter', [ 0.5 ]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my @E_per_mm = (); - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($self->Z == $config->layer_height) { # only consider first layer - if ($info->{extruding} && $info->{dist_XY} > 0) { - push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; - } - } - }); - my $E_per_mm_avg = sum(@E_per_mm) / @E_per_mm; - # allow some tolerance because solid rectilinear infill might be adjusted/stretched - ok !(defined first { abs($_ - $E_per_mm_avg) > 0.015 } @E_per_mm), - 'first_layer_extrusion_width applies to everything on first layer'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('bridge_speed', 99); - $config->set('bridge_flow_ratio', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('overhang', config => $config); - my @E_per_mm = (); - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - if (($args->{F} // $self->F) == $config->bridge_speed*60) { - push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; - } - } - }); - my $expected_mm3_per_mm = ($config->nozzle_diameter->[0]**2) * PI/4 * $config->bridge_flow_ratio; - my $expected_E_per_mm = $expected_mm3_per_mm / ((($config->filament_diameter->[0]/2)**2)*PI); - ok !(defined first { abs($_ - $expected_E_per_mm) > 0.01 } @E_per_mm), - 'expected flow when using bridge_flow_ratio = ' . $config->bridge_flow_ratio; - }; - - $config->set('bridge_flow_ratio', 0.5); - $test->(); - $config->set('bridge_flow_ratio', 2); - $test->(); - $config->set('extrusion_width', 0.4); - $config->set('bridge_flow_ratio', 1); - $test->(); - $config->set('bridge_flow_ratio', 0.5); - $test->(); - $config->set('bridge_flow_ratio', 2); - $test->(); -} - -__END__ diff --git a/t/gaps.t b/t/gaps.t deleted file mode 100644 index f0c75c3531..0000000000 --- a/t/gaps.t +++ /dev/null @@ -1,60 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(PI scale unscale convex_hull); -use Slic3r::Surface ':types'; -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeter_speed', 66); - $config->set('external_perimeter_speed', 66); - $config->set('small_perimeter_speed', 66); - $config->set('gap_fill_speed', 99); - $config->set('perimeters', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - $config->set('perimeter_extrusion_width', 0.35); - $config->set('first_layer_extrusion_width', 0.35); - - my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); - my @perimeter_points = (); - my $last = ''; # perimeter | gap - my $gap_fills_outside_last_perimeters = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - my $F = $args->{F} // $self->F; - my $point = Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}); - if ($F == $config->perimeter_speed*60) { - if ($last eq 'gap') { - @perimeter_points = (); - } - push @perimeter_points, $point; - $last = 'perimeter'; - } elsif ($F == $config->gap_fill_speed*60) { - my $convex_hull = convex_hull(\@perimeter_points); - if (!$convex_hull->contains_point($point)) { - $gap_fills_outside_last_perimeters++; - } - - $last = 'gap'; - } - } - }); - is $gap_fills_outside_last_perimeters, 0, 'gap fills are printed before leaving islands'; -} - -__END__ diff --git a/t/gcode.t b/t/gcode.t index b95505e436..902c40b834 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 24; +use Test::More tests => 23; use strict; use warnings; @@ -13,13 +13,6 @@ use Slic3r; use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Test; -{ - my $gcodegen = Slic3r::GCode->new(); - $gcodegen->set_layer_count(1); - $gcodegen->set_origin(Slic3r::Pointf->new(10, 10)); - is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly'; -} - { my $config = Slic3r::Config::new_from_defaults; $config->set('wipe', [1]); diff --git a/t/geometry.t b/t/geometry.t index bb72b22e17..981621ee8d 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 30; +plan tests => 11; BEGIN { use FindBin; @@ -11,7 +11,7 @@ BEGIN { } use Slic3r; -use Slic3r::Geometry qw(PI polygon_is_convex +use Slic3r::Geometry qw(PI chained_path_from epsilon scale); { @@ -27,56 +27,6 @@ use Slic3r::Geometry qw(PI polygon_is_convex #========================================================== -my $line1 = [ [5, 15], [30, 15] ]; -my $line2 = [ [10, 20], [10, 10] ]; -is_deeply Slic3r::Geometry::line_intersection($line1, $line2, 1)->arrayref, [10, 15], 'line_intersection'; - -#========================================================== - -$line1 = [ [73.6310778185108/0.0000001, 371.74239268924/0.0000001], [73.6310778185108/0.0000001, 501.74239268924/0.0000001] ]; -$line2 = [ [75/0.0000001, 437.9853/0.0000001], [62.7484/0.0000001, 440.4223/0.0000001] ]; -isnt Slic3r::Geometry::line_intersection($line1, $line2, 1), undef, 'line_intersection'; - -#========================================================== - -{ - my $polygon = Slic3r::Polygon->new( - [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], - [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], - [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], - [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], - [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], - [285273900, 461246400], [254081000, 515273900], - ); - - # this points belongs to $polyline - # note: it's actually a vertex, while we should better check an intermediate point - my $point = Slic3r::Point->new(104577600, 327748400); - - local $Slic3r::Geometry::epsilon = 1E-5; - is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp, - [ [107014700, 340000000], [104577600, 327748400] ], - 'polygon_segment_having_point'; -} - -#========================================================== - -{ - my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); - my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); - is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; -} - -#========================================================== - -{ - my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); - my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); - is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; -} - -#========================================================== - my $polygons = [ Slic3r::Polygon->new( # contour, ccw [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], @@ -95,54 +45,6 @@ my $polygons = [ ), ]; -#========================================================== - -{ - my $p1 = [10, 10]; - my $p2 = [10, 20]; - my $p3 = [10, 30]; - my $p4 = [20, 20]; - my $p5 = [0, 20]; - - is Slic3r::Geometry::angle3points($p2, $p3, $p1), PI(), 'angle3points'; - is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points'; - is Slic3r::Geometry::angle3points($p2, $p3, $p4), PI()/2*3, 'angle3points'; - is Slic3r::Geometry::angle3points($p2, $p4, $p3), PI()/2, 'angle3points'; - is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2, 'angle3points'; - is Slic3r::Geometry::angle3points($p2, $p1, $p5), PI()/2*3, 'angle3points'; -} - -{ - my $p1 = [30, 30]; - my $p2 = [20, 20]; - my $p3 = [10, 10]; - my $p4 = [30, 10]; - - is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points'; - is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2*3, 'angle3points'; - is Slic3r::Geometry::angle3points($p2, $p1, $p1), 2*PI(), 'angle3points'; -} - -#========================================================== - -{ - my $cw_square = [ [0,0], [0,10], [10,10], [10,0] ]; - is polygon_is_convex($cw_square), 0, 'cw square is not convex'; - is polygon_is_convex([ reverse @$cw_square ]), 1, 'ccw square is convex'; - - my $convex1 = [ [0,0], [10,0], [10,10], [0,10], [0,6], [4,6], [4,4], [0,4] ]; - is polygon_is_convex($convex1), 0, 'concave polygon'; -} - -#========================================================== - -{ - my $polyline = Slic3r::Polyline->new([0, 0], [10, 0], [20, 0]); - is_deeply [ map $_->pp, @{$polyline->lines} ], [ - [ [0, 0], [10, 0] ], - [ [10, 0], [20, 0] ], - ], 'polyline_lines'; -} #========================================================== @@ -163,13 +65,6 @@ my $polygons = [ #========================================================== -{ - my $line = Slic3r::Line->new([10,10], [20,10]); - is $line->grow(5)->[0]->area, Slic3r::Polygon->new([10,5], [20,5], [20,15], [10,15])->area, 'grow line'; -} - -#========================================================== - { # if chained_path() works correctly, these points should be joined with no diagonal paths # (thus 26 units long) diff --git a/t/loops.t b/t/loops.t deleted file mode 100644 index e662469cad..0000000000 --- a/t/loops.t +++ /dev/null @@ -1,57 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan skip_all => 'temporarily disabled'; -plan tests => 4; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Test; - -{ - # We only need to slice at one height, so we'll build a non-manifold mesh - # that still produces complete loops at that height. Triangular walls are - # enough for this purpose. - # Basically we want to check what happens when three concentric loops happen - # to be at the same height, the two external ones being ccw and the other being - # a hole, thus cw. - my (@vertices, @facets) = (); - Slic3r::Test::add_facet($_, \@vertices, \@facets) for - # external surface below the slicing Z - [ [0,0,0], [20,0,10], [0,0,10] ], - [ [20,0,0], [20,20,10], [20,0,10] ], - [ [20,20,0], [0,20,10], [20,20,10] ], - [ [0,20,0], [0,0,10], [0,20,10] ], - - # external insetted surface above the slicing Z - [ [2,2,10], [18,2,10], [2,2,20] ], - [ [18,2,10], [18,18,10], [18,2,20] ], - [ [18,18,10], [2,18,10], [18,18,20] ], - [ [2,18,10], [2,2,10], [2,18,20] ], - - # insetted hole below the slicing Z - [ [15,5,0], [5,5,10], [15,5,10] ], - [ [15,15,0], [15,5,10], [15,15,10] ], - [ [5,15,0], [15,15,10], [5,15,10] ], - [ [5,5,0], [5,15,10], [5,5,10] ]; - - my $mesh = Slic3r::TriangleMesh->new; - $mesh->ReadFromPerl(\@vertices, \@facets); - $mesh->analyze; - my @lines = map $mesh->intersect_facet($_, 10), 0..$#facets; - my $loops = Slic3r::TriangleMesh::make_loops(\@lines); - is scalar(@$loops), 3, 'correct number of loops detected'; - is scalar(grep $_->is_counter_clockwise, @$loops), 2, 'correct number of ccw loops detected'; - - my @surfaces = Slic3r::Layer::Region::_merge_loops($loops, 0); - is scalar(@surfaces), 1, 'one surface detected'; - is scalar(@{$surfaces[0]->expolygon})-1, 1, 'surface has one hole'; -} - -__END__ diff --git a/t/multi.t b/t/multi.t deleted file mode 100644 index e74a7a1a8b..0000000000 --- a/t/multi.t +++ /dev/null @@ -1,221 +0,0 @@ -use Test::More tests => 13; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Geometry qw(scale convex_hull); -use Slic3r::Geometry::Clipper qw(offset); -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('raft_layers', 2); - $config->set('infill_extruder', 2); - $config->set('solid_infill_extruder', 3); - $config->set('support_material_extruder', 4); - $config->set('ooze_prevention', 1); - $config->set('extruder_offset', [ [0,0], [20,0], [0,20], [20,20] ]); - $config->set('temperature', [200, 180, 170, 160]); - $config->set('first_layer_temperature', [206, 186, 166, 156]); - $config->set('toolchange_gcode', 'T[next_extruder] ;toolchange'); # test that it doesn't crash when this is supplied - # Since July 2019, PrusaSlicer only emits automatic Tn command in case that the toolchange_gcode is empty - # The "T[next_extruder]" is therefore needed in this test. - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - my $tool = undef; - my @tool_temp = (0,0,0,0); - my @toolchange_points = (); - my @extrusion_points = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - # ignore initial toolchange - if (defined $tool) { - my $expected_temp = $self->Z == ($config->get_value('first_layer_height') + $config->z_offset) - ? $config->first_layer_temperature->[$tool] - : $config->temperature->[$tool]; - die 'standby temperature was not set before toolchange' - if $tool_temp[$tool] != $expected_temp + $config->standby_temperature_delta; - - push @toolchange_points, my $point = Slic3r::Point->new_scale($self->X, $self->Y); - } - $tool = $1; - } elsif ($cmd eq 'M104' || $cmd eq 'M109') { - my $t = $args->{T} // $tool; - if ($tool_temp[$t] == 0) { - fail 'initial temperature is not equal to first layer temperature + standby delta' - unless $args->{S} == $config->first_layer_temperature->[$t] + $config->standby_temperature_delta; - } - $tool_temp[$t] = $args->{S}; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); - $point->translate(map +scale($_), @{ $config->extruder_offset->[$tool] }); - } - }); - my $convex_hull = convex_hull(\@extrusion_points); - - my @t = (); - foreach my $point (@toolchange_points) { - foreach my $offset (@{$config->extruder_offset}) { - push @t, my $p = $point->clone; - $p->translate(map +scale($_), @$offset); - } - } - ok !(defined first { $convex_hull->contains_point($_) } @t), 'all nozzles are outside skirt at toolchange'; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "ooze_prevention_test.svg", - no_arrows => 1, - polygons => [$convex_hull], - red_points => \@t, - points => \@toolchange_points, - ); - } - - # offset the skirt by the maximum displacement between extruders plus a safety extra margin - my $delta = scale(20 * sqrt(2) + 1); - my $outer_convex_hull = offset([$convex_hull], +$delta)->[0]; - ok !(defined first { !$outer_convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen within expected area'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('support_material_extruder', 3); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'no errors when using non-consecutive extruders'; -} - -{ - my $config = Slic3r::Config->new; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('extruder', 2); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - like Slic3r::Test::gcode($print), qr/ T1/, 'extruder shortcut'; -} - -{ - my $config = Slic3r::Config->new; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('perimeter_extruder', 2); - $config->set('infill_extruder', 2); - $config->set('support_material_extruder', 2); - $config->set('support_material_interface_extruder', 2); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'no errors when using multiple skirts with a single, non-zero, extruder'; -} - -{ - my $model = stacked_cubes(); - my $lower_config = $model->get_material('lower')->config; - my $upper_config = $model->get_material('upper')->config; - - $lower_config->set('extruder', 1); - $lower_config->set('bottom_solid_layers', 0); - $lower_config->set('top_solid_layers', 1); - $upper_config->set('extruder', 2); - $upper_config->set('bottom_solid_layers', 1); - $upper_config->set('top_solid_layers', 0); - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('fill_density', 0); - $config->set('solid_infill_speed', 99); - $config->set('top_solid_infill_speed', 99); - $config->set('cooling', [ 0 ]); # for preventing speeds from being altered - $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print($model, config => $config); - my $tool = undef; - my %T0_shells = my %T1_shells = (); # Z => 1 - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if (($args->{F} // $self->F) == $config->solid_infill_speed*60) { - if ($tool == 0) { - $T0_shells{$self->Z} = 1; - } elsif ($tool == 1) { - $T1_shells{$self->Z} = 1; - } - } - } - }); - return [ sort keys %T0_shells ], [ sort keys %T1_shells ]; - }; - - { - my ($t0, $t1) = $test->(); - is scalar(@$t0), 0, 'no interface shells'; - is scalar(@$t1), 0, 'no interface shells'; - } - { - $config->set('interface_shells', 1); - my ($t0, $t1) = $test->(); - is scalar(@$t0), $lower_config->top_solid_layers, 'top interface shells'; - is scalar(@$t1), $upper_config->bottom_solid_layers, 'bottom interface shells'; - } -} - -{ - my $model = stacked_cubes(); - my $object = $model->objects->[0]; - - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('layer_height', 0.4); - $config->set('first_layer_height', $config->layer_height); - $config->set('skirts', 0); - my $print = Slic3r::Test::init_print($model, config => $config); - - is $object->volumes->[0]->config->extruder, 1, 'auto_assign_extruders() assigned correct extruder to first volume'; - is $object->volumes->[1]->config->extruder, 2, 'auto_assign_extruders() assigned correct extruder to second volume'; - - my $tool = undef; - my %T0 = my %T1 = (); # Z => 1 - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == 0) { - $T0{$self->Z} = 1; - } elsif ($tool == 1) { - $T1{$self->Z} = 1; - } - } - }); - - ok !(defined first { $_ > 20 } keys %T0), 'T0 is never used for upper object'; - ok !(defined first { $_ < 20 } keys %T1), 'T1 is never used for lower object'; -} - -sub stacked_cubes { - my $model = Slic3r::Model->new; - my $object = $model->add_object; - $object->add_volume(mesh => Slic3r::Test::mesh('20mm_cube'), material_id => 'lower'); - $object->add_volume(mesh => Slic3r::Test::mesh('20mm_cube', translate => [0,0,20]), material_id => 'upper'); - $object->add_instance(offset => Slic3r::Pointf->new(0,0)); - - return $model; -} - -__END__ diff --git a/t/perimeters.t b/t/perimeters.t deleted file mode 100644 index adc2a6cec7..0000000000 --- a/t/perimeters.t +++ /dev/null @@ -1,444 +0,0 @@ -use Test::More tests => 59; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r::ExtrusionLoop ':roles'; -use Slic3r::ExtrusionPath ':roles'; -use List::Util qw(first); -use Slic3r; -use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(PI scale unscale); -use Slic3r::Geometry::Clipper qw(union_ex diff union offset); -use Slic3r::Surface ':types'; -use Slic3r::Test; - -{ - my $flow = Slic3r::Flow->new( - width => 1, - height => 1, - nozzle_diameter => 1, - ); - - my $config = Slic3r::Config->new; - my $test = sub { - my ($expolygons, %expected) = @_; - - my $slices = Slic3r::Surface::Collection->new; - $slices->append(Slic3r::Surface->new( - surface_type => S_TYPE_INTERNAL, - expolygon => $_, - )) for @$expolygons; - - my ($region_config, $object_config, $print_config, $loops, $gap_fill, $fill_surfaces); - my $g = Slic3r::Layer::PerimeterGenerator->new( - # input: - $slices, - 1, # layer height - $flow, - ($region_config = Slic3r::Config::PrintRegion->new), - ($object_config = Slic3r::Config::PrintObject->new), - ($print_config = Slic3r::Config::Print->new), - - # output: - ($loops = Slic3r::ExtrusionPath::Collection->new), - ($gap_fill = Slic3r::ExtrusionPath::Collection->new), - ($fill_surfaces = Slic3r::Surface::Collection->new), - ); - $g->config->apply_dynamic($config); - $g->process; - - is scalar(@$loops), - scalar(@$expolygons), 'expected number of collections'; - ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @$loops), - 'everything is returned as collections'; - - my $flattened_loops = $loops->flatten; - my @loops = @$flattened_loops; - is scalar(@loops), - $expected{total}, 'expected number of loops'; - is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops), - $expected{external}, 'expected number of external loops'; - is_deeply [ map { ($_->role == EXTR_ROLE_EXTERNAL_PERIMETER) || 0 } map @$_, @loops ], - $expected{ext_order}, 'expected external order'; - is scalar(grep $_->loop_role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops), - $expected{cinternal}, 'expected number of internal contour loops'; - is scalar(grep $_->polygon->is_counter_clockwise, @loops), - $expected{ccw}, 'expected number of ccw loops'; - is_deeply [ map $_->polygon->is_counter_clockwise, @loops ], - $expected{ccw_order}, 'expected ccw/cw order'; - - if ($expected{nesting}) { - foreach my $nesting (@{ $expected{nesting} }) { - for my $i (1..$#$nesting) { - ok $loops[$nesting->[$i-1]]->polygon->contains_point($loops[$nesting->[$i]]->first_point), - 'expected nesting order'; - } - } - } - }; - - $config->set('perimeters', 3); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), - ), - ], - total => 3, - external => 1, - ext_order => [0,0,1], - cinternal => 1, - ccw => 3, - ccw_order => [1,1,1], - nesting => [ [2,1,0] ], - ); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), - Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]), - ), - ], - total => 6, - external => 2, - ext_order => [0,0,1,0,0,1], - cinternal => 1, - ccw => 3, - ccw_order => [0,0,0,1,1,1], - nesting => [ [5,4,3,0,1,2] ], - ); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]), - Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]), - ), - # nested: - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]), - Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]), - ), - ], - total => 4*3, - external => 4, - ext_order => [0,0,1,0,0,1,0,0,1,0,0,1], - cinternal => 2, - ccw => 2*3, - ccw_order => [0,0,0,1,1,1,0,0,0,1,1,1], - ); - - $config->set('perimeters', 2); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [50,0], [50,50], [0,50]), - Slic3r::Polygon->new_scale([7.5,7.5], [7.5,12.5], [12.5,12.5], [12.5,7.5]), - Slic3r::Polygon->new_scale([7.5,17.5], [7.5,22.5], [12.5,22.5], [12.5,17.5]), - Slic3r::Polygon->new_scale([7.5,27.5], [7.5,32.5], [12.5,32.5], [12.5,27.5]), - Slic3r::Polygon->new_scale([7.5,37.5], [7.5,42.5], [12.5,42.5], [12.5,37.5]), - Slic3r::Polygon->new_scale([17.5,7.5], [17.5,12.5], [22.5,12.5], [22.5,7.5]), - ), - ], - total => 12, - external => 6, - ext_order => [0,1,0,1,0,1,0,1,0,1,0,1], - cinternal => 1, - ccw => 2, - ccw_order => [0,0,0,0,0,0,0,0,0,0,1,1], - nesting => [ [0,1],[2,3],[4,5],[6,7],[8,9] ], - ); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('fill_density', 0); - $config->set('perimeters', 3); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - - { - my $print = Slic3r::Test::init_print('overhang', config => $config); - my $has_cw_loops = 0; - my $cur_loop; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - $cur_loop ||= [ [$self->X, $self->Y] ]; - push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; - } elsif ($cmd ne 'M73') { # skips remaining time lines (M73) - if ($cur_loop) { - $has_cw_loops = 1 if Slic3r::Polygon->new(@$cur_loop)->is_clockwise; - $cur_loop = undef; - } - } - }); - ok !$has_cw_loops, 'all perimeters extruded ccw'; - } - - foreach my $model (qw(cube_with_hole cube_with_concave_hole)) { - $config->set('external_perimeter_speed', 68); - my $print = Slic3r::Test::init_print( - $model, - config => $config, - duplicate => 2, # we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong inwards moves) - ); - my $has_cw_loops = my $has_outwards_move = my $starts_on_convex_point = 0; - my $cur_loop; - my %external_loops = (); # print_z => count of external loops - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - $cur_loop ||= [ [$self->X, $self->Y] ]; - push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; - } elsif ($cmd ne 'M73') { # skips remaining time lines (M73) - if ($cur_loop) { - $has_cw_loops = 1 if Slic3r::Polygon->new_scale(@$cur_loop)->is_clockwise; - if ($self->F == $config->external_perimeter_speed*60) { - my $move_dest = Slic3r::Point->new_scale(@$info{qw(new_X new_Y)}); - - # reset counter for second object - $external_loops{$self->Z} = 0 - if defined($external_loops{$self->Z}) && $external_loops{$self->Z} == 2; - - $external_loops{$self->Z}++; - my $is_contour = $external_loops{$self->Z} == 2; - my $is_hole = $external_loops{$self->Z} == 1; - - my $loop = Slic3r::Polygon->new_scale(@$cur_loop); - my $loop_contains_point = $loop->contains_point($move_dest); - $has_outwards_move = 1 - if (!$loop_contains_point && $is_contour) # contour should include destination - || ($loop_contains_point && $is_hole); # hole should not - - if ($model eq 'cube_with_concave_hole') { - # check that loop starts at a concave vertex - my $ccw_angle = $loop->[-2]->ccw($loop->first_point, $loop->[1]); - my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex - $starts_on_convex_point = 1 - if ($convex && $is_contour) || (!$convex && $is_hole); - } - } - $cur_loop = undef; - } - } - }); - ok !$has_cw_loops, 'all perimeters extruded ccw'; - ok !$has_outwards_move, 'move inwards after completing external loop'; - ok !$starts_on_convex_point, 'loops start on concave point if any'; - } - - { - $config->set('perimeters', 1); - $config->set('perimeter_speed', 77); - $config->set('external_perimeter_speed', 66); - $config->set('bridge_speed', 99); - $config->set('cooling', [ 1 ]); - $config->set('fan_below_layer_time', [ 0 ]); - $config->set('slowdown_below_layer_time', [ 0 ]); - $config->set('bridge_fan_speed', [ 100 ]); - $config->set('bridge_flow_ratio', 33); # arbitrary value - $config->set('overhangs', 1); - my $print = Slic3r::Test::init_print('overhang', config => $config); - my %layer_speeds = (); # print Z => [ speeds ] - my $fan_speed = 0; - my $bridge_mm_per_mm = ($config->nozzle_diameter->[0]**2) / ($config->filament_diameter->[0]**2) * $config->bridge_flow_ratio; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - $fan_speed = 0 if $cmd eq 'M107'; - $fan_speed = $args->{S} if $cmd eq 'M106'; - if ($info->{extruding} && $info->{dist_XY} > 0) { - $layer_speeds{$self->Z} ||= {}; - $layer_speeds{$self->Z}{my $feedrate = $args->{F} // $self->F} = 1; - - fail 'wrong speed found' - if $feedrate != $config->perimeter_speed*60 - && $feedrate != $config->external_perimeter_speed*60 - && $feedrate != $config->bridge_speed*60; - - if ($feedrate == $config->bridge_speed*60) { - fail 'printing overhang but fan is not enabled or running at wrong speed' - if $fan_speed != 255; - my $mm_per_mm = $info->{dist_E} / $info->{dist_XY}; - fail 'wrong bridge flow' if abs($mm_per_mm - $bridge_mm_per_mm) > 0.01; - } else { - fail 'fan is running when not supposed to' - if $fan_speed > 0; - } - } - }); - is scalar(grep { keys %$_ > 1 } values %layer_speeds), 1, - 'only overhang layer has more than one speed'; - } -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('layer_height', 0.4); - $config->set('first_layer_height', 0.35); - $config->set('extra_perimeters', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - $config->set('perimeter_speed', 99); - $config->set('external_perimeter_speed', 99); - $config->set('small_perimeter_speed', 99); - $config->set('thin_walls', 0); - - my $print = Slic3r::Test::init_print('ipadstand', config => $config); - my %perimeters = (); # z => number of loops - my $in_loop = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) == $config->perimeter_speed*60) { - $perimeters{$self->Z}++ if !$in_loop; - $in_loop = 1; - } elsif ($cmd ne 'M73') { # skips remaining time lines (M73) - $in_loop = 0; - } - }); - ok !(grep { $_ % $config->perimeters } values %perimeters), 'no superfluous extra perimeters'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.4]); - $config->set('perimeters', 2); - $config->set('perimeter_extrusion_width', 0.4); - $config->set('external_perimeter_extrusion_width', 0.4); - $config->set('infill_extrusion_width', 0.53); - $config->set('solid_infill_extrusion_width', 0.53); - - # we just need a pre-filled Print object - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - # override a layer's slices - my $expolygon = Slic3r::ExPolygon->new([[-71974463,-139999376],[-71731792,-139987456],[-71706544,-139985616],[-71682119,-139982639],[-71441248,-139946912],[-71417487,-139942895],[-71379384,-139933984],[-71141800,-139874480],[-71105247,-139862895],[-70873544,-139779984],[-70838592,-139765856],[-70614943,-139660064],[-70581783,-139643567],[-70368368,-139515680],[-70323751,-139487872],[-70122160,-139338352],[-70082399,-139306639],[-69894800,-139136624],[-69878679,-139121327],[-69707992,-138933008],[-69668575,-138887343],[-69518775,-138685359],[-69484336,-138631632],[-69356423,-138418207],[-69250040,-138193296],[-69220920,-138128976],[-69137992,-137897168],[-69126095,-137860255],[-69066568,-137622608],[-69057104,-137582511],[-69053079,-137558751],[-69017352,-137317872],[-69014392,-137293456],[-69012543,-137268207],[-68999369,-137000000],[-63999999,-137000000],[-63705947,-136985551],[-63654984,-136977984],[-63414731,-136942351],[-63364756,-136929840],[-63129151,-136870815],[-62851950,-136771631],[-62585807,-136645743],[-62377483,-136520895],[-62333291,-136494415],[-62291908,-136463728],[-62096819,-136319023],[-62058644,-136284432],[-61878676,-136121328],[-61680968,-135903184],[-61650275,-135861807],[-61505591,-135666719],[-61354239,-135414191],[-61332211,-135367615],[-61228359,-135148063],[-61129179,-134870847],[-61057639,-134585262],[-61014451,-134294047],[-61000000,-134000000],[-61000000,-107999999],[-61014451,-107705944],[-61057639,-107414736],[-61129179,-107129152],[-61228359,-106851953],[-61354239,-106585808],[-61505591,-106333288],[-61680967,-106096816],[-61878675,-105878680],[-62096820,-105680967],[-62138204,-105650279],[-62333292,-105505591],[-62585808,-105354239],[-62632384,-105332207],[-62851951,-105228360],[-62900463,-105211008],[-63129152,-105129183],[-63414731,-105057640],[-63705947,-105014448],[-63999999,-105000000],[-68999369,-105000000],[-69012543,-104731792],[-69014392,-104706544],[-69017352,-104682119],[-69053079,-104441248],[-69057104,-104417487],[-69066008,-104379383],[-69125528,-104141799],[-69137111,-104105248],[-69220007,-103873544],[-69234136,-103838591],[-69339920,-103614943],[-69356415,-103581784],[-69484328,-103368367],[-69512143,-103323752],[-69661647,-103122160],[-69693352,-103082399],[-69863383,-102894800],[-69878680,-102878679],[-70066999,-102707992],[-70112656,-102668576],[-70314648,-102518775],[-70368367,-102484336],[-70581783,-102356424],[-70806711,-102250040],[-70871040,-102220919],[-71102823,-102137992],[-71139752,-102126095],[-71377383,-102066568],[-71417487,-102057104],[-71441248,-102053079],[-71682119,-102017352],[-71706535,-102014392],[-71731784,-102012543],[-71974456,-102000624],[-71999999,-102000000],[-104000000,-102000000],[-104025536,-102000624],[-104268207,-102012543],[-104293455,-102014392],[-104317880,-102017352],[-104558751,-102053079],[-104582512,-102057104],[-104620616,-102066008],[-104858200,-102125528],[-104894751,-102137111],[-105126455,-102220007],[-105161408,-102234136],[-105385056,-102339920],[-105418215,-102356415],[-105631632,-102484328],[-105676247,-102512143],[-105877839,-102661647],[-105917600,-102693352],[-106105199,-102863383],[-106121320,-102878680],[-106292007,-103066999],[-106331424,-103112656],[-106481224,-103314648],[-106515663,-103368367],[-106643575,-103581783],[-106749959,-103806711],[-106779080,-103871040],[-106862007,-104102823],[-106873904,-104139752],[-106933431,-104377383],[-106942896,-104417487],[-106946920,-104441248],[-106982648,-104682119],[-106985607,-104706535],[-106987456,-104731784],[-107000630,-105000000],[-112000000,-105000000],[-112294056,-105014448],[-112585264,-105057640],[-112870848,-105129184],[-112919359,-105146535],[-113148048,-105228360],[-113194624,-105250392],[-113414191,-105354239],[-113666711,-105505591],[-113708095,-105536279],[-113903183,-105680967],[-114121320,-105878679],[-114319032,-106096816],[-114349720,-106138200],[-114494408,-106333288],[-114645760,-106585808],[-114667792,-106632384],[-114771640,-106851952],[-114788991,-106900463],[-114870815,-107129151],[-114942359,-107414735],[-114985551,-107705943],[-115000000,-107999999],[-115000000,-134000000],[-114985551,-134294048],[-114942359,-134585263],[-114870816,-134870847],[-114853464,-134919359],[-114771639,-135148064],[-114645759,-135414192],[-114494407,-135666720],[-114319031,-135903184],[-114121320,-136121327],[-114083144,-136155919],[-113903184,-136319023],[-113861799,-136349712],[-113666711,-136494416],[-113458383,-136619264],[-113414192,-136645743],[-113148049,-136771631],[-112870848,-136870815],[-112820872,-136883327],[-112585264,-136942351],[-112534303,-136949920],[-112294056,-136985551],[-112000000,-137000000],[-107000630,-137000000],[-106987456,-137268207],[-106985608,-137293440],[-106982647,-137317872],[-106946920,-137558751],[-106942896,-137582511],[-106933991,-137620624],[-106874471,-137858208],[-106862888,-137894751],[-106779992,-138126463],[-106765863,-138161424],[-106660080,-138385055],[-106643584,-138418223],[-106515671,-138631648],[-106487855,-138676256],[-106338352,-138877839],[-106306647,-138917600],[-106136616,-139105199],[-106121320,-139121328],[-105933000,-139291999],[-105887344,-139331407],[-105685351,-139481232],[-105631632,-139515663],[-105418216,-139643567],[-105193288,-139749951],[-105128959,-139779072],[-104897175,-139862016],[-104860247,-139873904],[-104622616,-139933423],[-104582511,-139942896],[-104558751,-139946912],[-104317880,-139982656],[-104293463,-139985616],[-104268216,-139987456],[-104025544,-139999376],[-104000000,-140000000],[-71999999,-140000000]],[[-105000000,-138000000],[-105000000,-104000000],[-71000000,-104000000],[-71000000,-138000000]],[[-69000000,-132000000],[-69000000,-110000000],[-64991180,-110000000],[-64991180,-132000000]],[[-111008824,-132000000],[-111008824,-110000000],[-107000000,-110000000],[-107000000,-132000000]]); - my $object = $print->print->objects->[0]; - $object->slice; - my $layer = $object->get_layer(1); - my $layerm = $layer->regions->[0]; - $layerm->slices->clear; - $layerm->slices->append(Slic3r::Surface->new(surface_type => S_TYPE_INTERNAL, expolygon => $expolygon)); - - # make perimeters - $layer->make_perimeters; - - # compute the covered area - my $pflow = $layerm->flow(FLOW_ROLE_PERIMETER); - my $iflow = $layerm->flow(FLOW_ROLE_INFILL); - my $covered_by_perimeters = union_ex([ - (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, map @$_, @{$layerm->perimeters}), - ]); - my $covered_by_infill = union_ex([ - (map $_->p, @{$layerm->fill_surfaces}), - (map @{$_->polyline->grow($iflow->scaled_width/2)}, @{$layerm->thin_fills}), - ]); - - # compute the non covered area - my $non_covered = diff( - [ map @{$_->expolygon}, @{$layerm->slices} ], - [ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ], - ); - - if (0) { - printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered); - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "gaps.svg", - expolygons => [ map $_->expolygon, @{$layerm->slices} ], - red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]), - green_expolygons => union_ex($non_covered), - no_arrows => 1, - polylines => [ - map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters}, - ], - ); - } - ok !(defined first { $_->area > ($iflow->scaled_width**2) } @$non_covered), 'no gap between perimeters and infill'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('layer_height', 0.4); - $config->set('bridge_speed', 99); - $config->set('fill_density', 0); # to prevent bridging over sparse infill - $config->set('overhangs', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - - my $test = sub { - my ($print) = @_; - my %z_with_bridges = (); # z => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - $z_with_bridges{$self->Z} = 1 if ($args->{F} // $self->F) == $config->bridge_speed*60; - } - }); - return scalar keys %z_with_bridges; - }; - ok $test->(Slic3r::Test::init_print('V', config => $config)) == 1, - 'no overhangs printed with bridge speed'; # except for the two internal solid layers above void - ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 2, - 'overhangs printed with bridge speed'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('seam_position', 'random'); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random'; -} - -{ - my $test = sub { - my ($model_name) = @_; - my $config = Slic3r::Config::new_from_defaults; - $config->set('seam_position', 'aligned'); - $config->set('skirts', 0); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('retract_layer_change', [0]); - - my $was_extruding = 0; - my @seam_points = (); - my $print = Slic3r::Test::init_print($model_name, config => $config); - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding}) { - if (!$was_extruding) { - push @seam_points, Slic3r::Point->new_scale($self->X, $self->Y); - } - $was_extruding = 1; - } elsif ($cmd ne 'M73') { # skips remaining time lines (M73) - $was_extruding = 0; - } - }); - my @dist = map unscale($_), map $seam_points[$_]->distance_to($seam_points[$_+1]), 0..($#seam_points-1); - ok !(defined first { $_ > 3 } @dist), 'seam is aligned'; - }; - $test->('20mm_cube'); - $test->('small_dorito'); -} - -__END__ diff --git a/t/polyclip.t b/t/polyclip.t deleted file mode 100644 index 0808c7be99..0000000000 --- a/t/polyclip.t +++ /dev/null @@ -1,121 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 18; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Geometry::Clipper qw(intersection_pl); - -#========================================================== - -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([5, 10], [20, 10])), 1, 'point in horizontal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(30, 10), Slic3r::Line->new([5, 10], [20, 10])), 0, 'point not in horizontal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([10, 5], [10, 20])), 1, 'point in vertical segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 30), Slic3r::Line->new([10, 5], [10, 20])), 0, 'point not in vertical segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(15, 15), Slic3r::Line->new([10, 10], [20, 20])), 1, 'point in diagonal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(20, 15), Slic3r::Line->new([10, 10], [20, 20])), 0, 'point not in diagonal segment'; - -#========================================================== - -my $square = Slic3r::Polygon->new( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -); - -#========================================================== - -{ - my $hole_in_square = [ # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ]; - my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - #is $expolygon->contains_point(Slic3r::Point->new(100, 100)), 1, 'corner point is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(100, 180)), 1, 'point on contour is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(140, 150)), 1, 'point on hole contour is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(140, 140)), 1, 'point on hole corner is recognized'; - { - my $intersection = intersection_pl([Slic3r::Polyline->new([150,180], [150,150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([150, 180], [150, 160])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([150,150], [150,120])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([150, 140], [150, 120])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([120,180], [180,180])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([120,180], [180,180])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([50, 150], [300, 150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([100, 150], [140, 150])->length, - 'line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([160, 150], [200, 150])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([300, 150], [50, 150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([200, 150], [160, 150])->length, - 'reverse line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([140, 150], [100, 150])->length, - 'reverse line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([100,180], [200,180])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([100,180], [200,180])->length, - 'tangent line is clipped to square with hole'; - } -} - -#========================================================== - -{ - my $large_circle = Slic3r::Polygon->new_scale( # ccw - [151.8639,288.1192], [133.2778,284.6011], [115.0091,279.6997], [98.2859,270.8606], [82.2734,260.7933], - [68.8974,247.4181], [56.5622,233.0777], [47.7228,216.3558], [40.1617,199.0172], [36.6431,180.4328], - [34.932,165.2312], [37.5567,165.1101], [41.0547,142.9903], [36.9056,141.4295], [40.199,124.1277], - [47.7776,106.7972], [56.6335,90.084], [68.9831,75.7557], [82.3712,62.3948], [98.395,52.3429], - [115.1281,43.5199], [133.4004,38.6374], [151.9884,35.1378], [170.8905,35.8571], [189.6847,37.991], - [207.5349,44.2488], [224.8662,51.8273], [240.0786,63.067], [254.407,75.4169], [265.6311,90.6406], - [275.6832,106.6636], [281.9225,124.52], [286.8064,142.795], [287.5061,161.696], [286.7874,180.5972], - [281.8856,198.8664], [275.6283,216.7169], [265.5604,232.7294], [254.3211,247.942], [239.9802,260.2776], - [224.757,271.5022], [207.4179,279.0635], [189.5605,285.3035], [170.7649,287.4188], - ); - ok $large_circle->is_counter_clockwise, "contour is counter-clockwise"; - - my $small_circle = Slic3r::Polygon->new_scale( # cw - [158.227,215.9007], [164.5136,215.9007], [175.15,214.5007], [184.5576,210.6044], [190.2268,207.8743], - [199.1462,201.0306], [209.0146,188.346], [213.5135,177.4829], [214.6979,168.4866], [216.1025,162.3325], - [214.6463,151.2703], [213.2471,145.1399], [209.0146,134.9203], [199.1462,122.2357], [189.8944,115.1366], - [181.2504,111.5567], [175.5684,108.8205], [164.5136,107.3655], [158.2269,107.3655], [147.5907,108.7656], - [138.183,112.6616], [132.5135,115.3919], [123.5943,122.2357], [113.7259,134.92], [109.2269,145.7834], - [108.0426,154.7799], [106.638,160.9339], [108.0941,171.9957], [109.4933,178.1264], [113.7259,188.3463], - [123.5943,201.0306], [132.8461,208.1296], [141.4901,211.7094], [147.172,214.4458], - ); - ok $small_circle->is_clockwise, "hole is clockwise"; - - my $expolygon = Slic3r::ExPolygon->new($large_circle, $small_circle); - my $line = Slic3r::Polyline->new_scale([152.742,288.086671142818], [152.742,34.166466971035]); - - my $intersection = intersection_pl([$line], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([152742000, 288086661], [152742000, 215178843])->length, - 'line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([152742000, 108087507], [152742000, 35166477])->length, - 'line is clipped to square with hole'; -} - -#========================================================== diff --git a/t/slice.t b/t/slice.t deleted file mode 100644 index 2f193aae3c..0000000000 --- a/t/slice.t +++ /dev/null @@ -1,152 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan skip_all => 'temporarily disabled'; -plan tests => 16; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -# temporarily disable compilation errors due to constant not being exported anymore -sub Slic3r::TriangleMesh::I_B {} -sub Slic3r::TriangleMesh::I_FACET_EDGE {} -sub Slic3r::TriangleMesh::FE_BOTTOM { -sub Slic3r::TriangleMesh::FE_TOP {}} - -use Slic3r; -use Slic3r::Geometry qw(X Y Z); - -my @lines; -my $z = 20; -my @points = ([3, 4], [8, 5], [1, 9]); # XY coordinates of the facet vertices - -# NOTE: -# the first point of the intersection lines is replaced by -1 because TriangleMesh.pm -# is saving memory and doesn't store point A anymore since it's not actually needed. - -# We disable this test because intersect_facet() now assumes we never feed a horizontal -# facet to it. -# is_deeply lines(20, 20, 20), [ -# [ -1, $points[1] ], # $points[0] -# [ -1, $points[2] ], # $points[1] -# [ -1, $points[0] ], # $points[2] -# ], 'horizontal'; - -is_deeply lines(22, 20, 20), [ [ -1, $points[2] ] ], 'lower edge on layer'; # $points[1] -is_deeply lines(20, 20, 22), [ [ -1, $points[1] ] ], 'lower edge on layer'; # $points[0] -is_deeply lines(20, 22, 20), [ [ -1, $points[0] ] ], 'lower edge on layer'; # $points[2] - -is_deeply lines(20, 20, 10), [ [ -1, $points[0] ] ], 'upper edge on layer'; # $points[1] -is_deeply lines(10, 20, 20), [ [ -1, $points[1] ] ], 'upper edge on layer'; # $points[2] -is_deeply lines(20, 10, 20), [ [ -1, $points[2] ] ], 'upper edge on layer'; # $points[0] - -is_deeply lines(20, 15, 10), [ ], 'upper vertex on layer'; -is_deeply lines(28, 20, 30), [ ], 'lower vertex on layer'; - -{ - my @z = (24, 10, 16); - is_deeply lines(@z), [ - [ - -1, # line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), - line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), - ] - ], 'two edges intersect'; -} - -{ - my @z = (16, 24, 10); - is_deeply lines(@z), [ - [ - -1, # line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), - line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), - ] - ], 'two edges intersect'; -} - -{ - my @z = (10, 16, 24); - is_deeply lines(@z), [ - [ - -1, # line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), - line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), - ] - ], 'two edges intersect'; -} - -{ - my @z = (24, 10, 20); - is_deeply lines(@z), [ - [ - -1, # line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), - $points[2], - ] - ], 'one vertex on plane and one edge intersects'; -} - -{ - my @z = (10, 20, 24); - is_deeply lines(@z), [ - [ - -1, # line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), - $points[1], - ] - ], 'one vertex on plane and one edge intersects'; -} - -{ - my @z = (20, 24, 10); - is_deeply lines(@z), [ - [ - -1, # line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), - $points[0], - ] - ], 'one vertex on plane and one edge intersects'; -} - -my @lower = intersect(22, 20, 20); -my @upper = intersect(20, 20, 10); -is $lower[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_BOTTOM, 'bottom edge on layer'; -is $upper[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_TOP, 'upper edge on layer'; - -my $mesh; - -sub intersect { - $mesh = Slic3r::TriangleMesh->new( - facets => [], - vertices => [], - ); - push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; - $mesh->analyze; - return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); -} - -sub vertices { - push @{$mesh->vertices}, map [ @{$points[$_]}, $_[$_] ], 0..2; - [ ($#{$mesh->vertices}-2) .. $#{$mesh->vertices} ] -} - -sub lines { - my @lines = intersect(@_); - #$_->a->[X] = sprintf('%.0f', $_->a->[X]) for @lines; - #$_->a->[Y] = sprintf('%.0f', $_->a->[Y]) for @lines; - $_->[Slic3r::TriangleMesh::I_B][X] = sprintf('%.0f', $_->[Slic3r::TriangleMesh::I_B][X]) for @lines; - $_->[Slic3r::TriangleMesh::I_B][Y] = sprintf('%.0f', $_->[Slic3r::TriangleMesh::I_B][Y]) for @lines; - return [ map [ -1, $_->[Slic3r::TriangleMesh::I_B] ], @lines ]; -} - -sub line_plane_intersection { - my ($line) = @_; - @$line = map $mesh->vertices->[$_], @$line; - - return [ - map sprintf('%.0f', $_), - map +($line->[1][$_] + ($line->[0][$_] - $line->[1][$_]) * ($z - $line->[1][Z]) / ($line->[0][Z] - $line->[1][Z])), - (X,Y) - ]; -} - -__END__ diff --git a/t/support.t b/t/support.t deleted file mode 100644 index 0283df22b1..0000000000 --- a/t/support.t +++ /dev/null @@ -1,272 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan skip_all => 'temporarily disabled'; -plan tests => 27; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(epsilon scale); -use Slic3r::Geometry::Clipper qw(diff); -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('support_material', 1); - my @contact_z = my @top_z = (); - - my $test = sub { - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $object_config = $print->print->objects->[0]->config; - my $flow = Slic3r::Flow->new_from_width( - width => $object_config->support_material_extrusion_width || $object_config->extrusion_width, - role => FLOW_ROLE_SUPPORT_MATERIAL, - nozzle_diameter => $print->config->nozzle_diameter->[$object_config->support_material_extruder-1] // $print->config->nozzle_diameter->[0], - layer_height => $object_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($print->print->objects->[0], \@contact_z, \@top_z, $config->layer_height); - my $expected_top_spacing = $support->contact_distance($config->layer_height, $config->nozzle_diameter->[0]); - - is $support_z->[0], $config->first_layer_height, - 'first layer height is honored'; - is scalar(grep { $support_z->[$_]-$support_z->[$_-1] <= 0 } 1..$#$support_z), 0, - 'no null or negative support layers'; - is scalar(grep { $support_z->[$_]-$support_z->[$_-1] > $config->nozzle_diameter->[0] + epsilon } 1..$#$support_z), 0, - 'no layers thicker than nozzle diameter'; - - my $wrong_top_spacing = 0; - foreach my $top_z (@top_z) { - # 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 (or the next one) is spaced with nozzle diameter - $wrong_top_spacing = 1 - 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'; - }; - - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.3); - @contact_z = (1.9); - @top_z = (1.1); - $test->(); - - $config->set('first_layer_height', 0.4); - $test->(); - - $config->set('layer_height', $config->nozzle_diameter->[0]); - $test->(); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('raft_layers', 3); - $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', 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'; - - my $tool = 0; - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($info->{extruding}) { - if ($self->Z <= ($config->raft_layers * $config->layer_height)) { - fail 'not extruding raft with support material extruder' - if $tool != ($config->support_material_extruder-1); - } else { - fail 'support material exceeds raft layers' - if $tool == $config->support_material_extruder-1; - # TODO: we should test that full support is generated when we use raft too - } - } - }); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('raft_layers', 3); - $config->set('support_material_pattern', 'honeycomb'); - $config->set('support_material_extrusion_width', 0.6); - $config->set('first_layer_extrusion_width', '100%'); - $config->set('bridge_speed', 99); - $config->set('cooling', [ 0 ]); # prevent speed alteration - $config->set('first_layer_speed', '100%'); # prevent speed alteration - $config->set('start_gcode', ''); # prevent any unexpected Z move - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - my $layer_id = -1; # so that first Z move sets this to 0 - my @raft = my @first_object_layer = (); - my %first_object_layer_speeds = (); # F => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - if ($layer_id <= $config->raft_layers) { - # this is a raft layer or the first object layer - my $line = Slic3r::Line->new_scale([ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ]); - my @path = @{$line->grow(scale($config->support_material_extrusion_width/2))}; - if ($layer_id < $config->raft_layers) { - # this is a raft layer - push @raft, @path; - } else { - push @first_object_layer, @path; - $first_object_layer_speeds{ $args->{F} // $self->F } = 1; - } - } - } elsif ($cmd eq 'G1' && $info->{dist_Z} > 0) { - $layer_id++; - } - }); - - ok !@{diff(\@first_object_layer, \@raft)}, - 'first object layer is completely supported by raft'; - is scalar(keys %first_object_layer_speeds), 1, - 'only one speed used in first object layer'; - ok +(keys %first_object_layer_speeds)[0] == $config->bridge_speed*60, - 'bridge speed used in first object layer'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('layer_height', 0.35); - $config->set('first_layer_height', 0.3); - $config->set('nozzle_diameter', [0.5]); - $config->set('support_material_extruder', 2); - $config->set('support_material_interface_extruder', 2); - - my $test = sub { - my ($raft_layers) = @_; - $config->set('raft_layers', $raft_layers); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my %raft_z = (); # z => 1 - my $tool = undef; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->support_material_extruder-1) { - $raft_z{$self->Z} = 1; - } - } - }); - - is scalar(keys %raft_z), $config->raft_layers, 'correct number of raft layers is generated'; - }; - - $test->(2); - $test->(70); - - $config->set('layer_height', 0.4); - $config->set('first_layer_height', 0.35); - $test->(3); - $test->(70); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('brim_width', 0); - $config->set('skirts', 0); - $config->set('support_material', 1); - $config->set('top_solid_layers', 0); # so that we don't have the internal bridge over infill - $config->set('bridge_speed', 99); - $config->set('cooling', [ 0 ]); - $config->set('first_layer_speed', '100%'); - - my $test = sub { - my $print = Slic3r::Test::init_print('overhang', config => $config); - - my $has_bridge_speed = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding}) { - if (($args->{F} // $self->F) == $config->bridge_speed*60) { - $has_bridge_speed = 1; - } - } - }); - return $has_bridge_speed; - }; - - $config->set('support_material_contact_distance', 0.2); - ok $test->(), 'bridge speed is used when support_material_contact_distance > 0'; - - $config->set('support_material_contact_distance', 0); - ok !$test->(), 'bridge speed is not used when support_material_contact_distance == 0'; - - $config->set('raft_layers', 5); - $config->set('support_material_contact_distance', 0.2); - ok $test->(), 'bridge speed is used when raft_layers > 0 and support_material_contact_distance > 0'; - - $config->set('support_material_contact_distance', 0); - ok !$test->(), 'bridge speed is not used when raft_layers > 0 and support_material_contact_distance == 0'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('start_gcode', ''); - $config->set('raft_layers', 8); - $config->set('nozzle_diameter', [0.4, 1]); - $config->set('layer_height', 0.1); - $config->set('first_layer_height', 0.8); - $config->set('support_material_extruder', 2); - $config->set('support_material_interface_extruder', 2); - $config->set('support_material_contact_distance', 0); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok my $gcode = Slic3r::Test::gcode($print), 'first_layer_height is validated with support material extruder nozzle diameter when using raft layers'; - - my $tool = undef; - my @z = (0); - my %layer_heights_by_tool = (); # tool => [ lh, lh... ] - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && exists $args->{Z} && $args->{Z} != $self->Z) { - push @z, $args->{Z}; - } elsif ($info->{extruding} && $info->{dist_XY} > 0) { - $layer_heights_by_tool{$tool} ||= []; - push @{ $layer_heights_by_tool{$tool} }, $z[-1] - $z[-2]; - } - }); - - ok !defined(first { $_ > $config->nozzle_diameter->[0] + epsilon } - @{ $layer_heights_by_tool{$config->perimeter_extruder-1} }), - 'no object layer is thicker than nozzle diameter'; - - ok !defined(first { abs($_ - $config->layer_height) < epsilon } - @{ $layer_heights_by_tool{$config->support_material_extruder-1} }), - 'no support material layer is as thin as object layers'; -} - -__END__ diff --git a/t/thin.t b/t/thin.t deleted file mode 100644 index 50e7abc950..0000000000 --- a/t/thin.t +++ /dev/null @@ -1,185 +0,0 @@ -use Test::More tests => 23; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use List::Util qw(first sum none); -use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon Y); -use Slic3r::Test; - -# Disable this until a more robust implementation is provided. It currently -# fails on Linux 32bit because some spurious extrudates are generated. -if (0) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.2); - $config->set('first_layer_height', $config->layer_height); - $config->set('extrusion_width', 0.5); - $config->set('first_layer_extrusion_width', '200%'); # check this one too - $config->set('skirts', 0); - $config->set('thin_walls', 1); - - my $print = Slic3r::Test::init_print('gt2_teeth', config => $config); - - my %extrusion_paths = (); # Z => count of continuous extrusions - my $extruding = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($info->{extruding} && $info->{dist_XY}) { - if (!$extruding) { - $extrusion_paths{$self->Z} //= 0; - $extrusion_paths{$self->Z}++; - } - $extruding = 1; - } else { - $extruding = 0; - } - } - }); - - ok !(first { $_ != 3 } values %extrusion_paths), - 'no superfluous thin walls are generated for toothed profile'; -} - -{ - my $square = Slic3r::Polygon->new_scale( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], - ); - my $hole_in_square = Slic3r::Polygon->new_scale( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ); - my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - my $res = $expolygon->medial_axis(scale 40, scale 0.5); - is scalar(@$res), 1, 'medial axis of a square shape is a single path'; - isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline'; - ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop'; - ok $res->[0]->length > $hole_in_square->length && $res->[0]->length < $square->length, - 'medial axis loop has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 200], - [100, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; - - $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 200], - [105, 200], # extra point in the short side - [100, 200], - )); - my $res2 = $expolygon->medial_axis(scale 1, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow rectangle with an extra vertex is still a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has still a reasonable length'; - ok !(grep { abs($_ - scale 150) < scaled_epsilon } map $_->[Y], map @$_, @$res2), "extra vertices don't influence medial axis"; -} - -{ - my $expolygon = Slic3r::ExPolygon->new( - Slic3r::Polygon->new([1185881,829367],[1421988,1578184],[1722442,2303558],[2084981,2999998],[2506843,3662186],[2984809,4285086],[3515250,4863959],[4094122,5394400],[4717018,5872368],[5379210,6294226],[6075653,6656769],[6801033,6957229],[7549842,7193328],[8316383,7363266],[9094809,7465751],[9879211,7500000],[10663611,7465750],[11442038,7363265],[12208580,7193327],[12957389,6957228],[13682769,6656768],[14379209,6294227],[15041405,5872366],[15664297,5394401],[16243171,4863960],[16758641,4301424],[17251579,3662185],[17673439,3000000],[18035980,2303556],[18336441,1578177],[18572539,829368],[18750748,0],[19758422,0],[19727293,236479],[19538467,1088188],[19276136,1920196],[18942292,2726179],[18539460,3499999],[18070731,4235755],[17539650,4927877],[16950279,5571067],[16307090,6160437],[15614974,6691519],[14879209,7160248],[14105392,7563079],[13299407,7896927],[12467399,8159255],[11615691,8348082],[10750769,8461952],[9879211,8500000],[9007652,8461952],[8142729,8348082],[7291022,8159255],[6459015,7896927],[5653029,7563079],[4879210,7160247],[4143447,6691519],[3451331,6160437],[2808141,5571066],[2218773,4927878],[1687689,4235755],[1218962,3499999],[827499,2748020],[482284,1920196],[219954,1088186],[31126,236479],[0,0],[1005754,0]), - ); - my $res = $expolygon->medial_axis(scale 1.324888, scale 0.25); - is scalar(@$res), 1, 'medial axis of a semicircumference is a single line'; - - # check whether turns are all CCW or all CW - my @lines = @{$res->[0]->lines}; - my @angles = map { $lines[$_-1]->ccw($lines[$_]->b) } 1..$#lines; - ok !!(none { $_ < 0 } @angles) || (none { $_ > 0 } @angles), - 'all medial axis segments of a semicircumference have the same orientation'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [112, 200], - [108, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 180], - [200, 180], - [200, 200], - [100, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a L shape is a single polyline'; - my $len = unscale($res->[0]->length) + 20; # 20 is the thickness of the expolygon, which is subtracted from the ends - ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [-203064906,-51459966],[-219312231,-51459966],[-219335477,-51459962],[-219376095,-51459962],[-219412047,-51459966], - [-219572094,-51459966],[-219624814,-51459962],[-219642183,-51459962],[-219656665,-51459966],[-220815482,-51459966], - [-220815482,-37738966],[-221117540,-37738966],[-221117540,-51762024],[-203064906,-51762024], - )); - my $polylines = $expolygon->medial_axis(819998, 102499.75); - - my $perimeter = $expolygon->contour->split_at_first_point->length; - ok sum(map $_->length, @$polylines) > $perimeter/2/4*3, 'medial axis has a reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [50, 100], - [1000, 102], - [50, 104], - )); - my $res = $expolygon->medial_axis(scale 4, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow triangle is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; -} - -{ - # GH #2474 - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808] - )); - my $polylines = $expolygon->medial_axis(1871238, 500000); - is scalar(@$polylines), 1, 'medial axis is a single polyline'; - my $polyline = $polylines->[0]; - - my $expected_y = $expolygon->bounding_box->center->y; #;; - ok abs(sum(map $_->y, @$polyline) / @$polyline - $expected_y) < scaled_epsilon, #,, - 'medial axis is horizontal and is centered'; - - # order polyline from left to right - $polyline->reverse if $polyline->first_point->x > $polyline->last_point->x; - - my $polyline_bb = $polyline->bounding_box; - is $polyline->first_point->x, $polyline_bb->x_min, 'expected x_min'; - is $polyline->last_point->x, $polyline_bb->x_max, 'expected x_max'; - - is_deeply [ map $_->x, @$polyline ], [ sort map $_->x, @$polyline ], - 'medial axis is not self-overlapping'; -} - -__END__ diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 50b45e384c..4e8821287f 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -1,20 +1,28 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp + test_avoid_crossing_perimeters.cpp + test_bridges.cpp + test_cooling.cpp + test_custom_gcode.cpp test_data.cpp test_data.hpp test_extrusion_entity.cpp test_fill.cpp test_flow.cpp + test_gaps.cpp test_gcode.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp test_model.cpp + test_multi.cpp + test_perimeters.cpp test_print.cpp test_printgcode.cpp test_printobject.cpp test_skirt_brim.cpp test_support_material.cpp + test_thin_walls.cpp test_trianglemesh.cpp ) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) diff --git a/tests/fff_print/test_avoid_crossing_perimeters.cpp b/tests/fff_print/test_avoid_crossing_perimeters.cpp new file mode 100644 index 0000000000..a76ac12b53 --- /dev/null +++ b/tests/fff_print/test_avoid_crossing_perimeters.cpp @@ -0,0 +1,16 @@ +#include + +#include "test_data.hpp" + +using namespace Slic3r; + +SCENARIO("Avoid crossing perimeters", "[AvoidCrossingPerimeters]") { + WHEN("Two 20mm cubes sliced") { + std::string gcode = Slic3r::Test::slice( + { Slic3r::Test::TestMesh::cube_20x20x20, Slic3r::Test::TestMesh::cube_20x20x20 }, + { { "avoid_crossing_perimeters", true } }); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + } +} diff --git a/tests/fff_print/test_bridges.cpp b/tests/fff_print/test_bridges.cpp new file mode 100644 index 0000000000..91ab9b6b29 --- /dev/null +++ b/tests/fff_print/test_bridges.cpp @@ -0,0 +1,133 @@ +#include + +#include +#include + +#include "test_data.hpp" + +using namespace Slic3r; + +SCENARIO("Bridge detector", "[Bridging]") +{ + auto check_angle = [](const ExPolygons &lower, const ExPolygon &bridge, double expected, double tolerance = -1, double expected_coverage = -1) + { + if (expected_coverage < 0) + expected_coverage = bridge.area(); + + BridgeDetector bridge_detector(bridge, lower, scaled(0.5)); // extrusion width + if (tolerance < 0) + tolerance = Geometry::rad2deg(bridge_detector.resolution) + EPSILON; + + bridge_detector.detect_angle(); + double result = bridge_detector.angle; + Polygons coverage = bridge_detector.coverage(); + THEN("correct coverage area") { + REQUIRE(is_approx(area(coverage), expected_coverage)); + } + // our epsilon is equal to the steps used by the bridge detection algorithm + //##use XXX; YYY [ rad2deg($result), $expected ]; + // returned value must be non-negative, check for that too + double delta = Geometry::rad2deg(result) - expected; + if (delta >= 180. - EPSILON) + delta -= 180; + return result >= 0. && std::abs(delta) < tolerance; + }; + GIVEN("O-shaped overhang") { + auto test = [&check_angle](const Point &size, double rotate, double expected_angle, double tolerance = -1) { + ExPolygon lower{ + Polygon::new_scale({ {-2,-2}, {size.x()+2,-2}, {size.x()+2,size.y()+2}, {-2,size.y()+2} }), + Polygon::new_scale({ {0,0}, {0,size.y()}, {size.x(),size.y()}, {size.x(),0} } ) + }; + lower.rotate(Geometry::deg2rad(rotate), size / 2); + ExPolygon bridge_expoly(lower.holes.front()); + bridge_expoly.contour.reverse(); + return check_angle({ lower }, bridge_expoly, expected_angle, tolerance); + }; + WHEN("Bridge size 20x10") { + bool valid = test({20,10}, 0., 90.); + THEN("bridging angle is 90 degrees") { + REQUIRE(valid); + } + } + WHEN("Bridge size 10x20") { + bool valid = test({10,20}, 0., 0.); + THEN("bridging angle is 0 degrees") { + REQUIRE(valid); + } + } + WHEN("Bridge size 20x10, rotated by 45 degrees") { + bool valid = test({20,10}, 45., 135., 20.); + THEN("bridging angle is 135 degrees") { + REQUIRE(valid); + } + } + WHEN("Bridge size 20x10, rotated by 135 degrees") { + bool valid = test({20,10}, 135., 45., 20.); + THEN("bridging angle is 45 degrees") { + REQUIRE(valid); + } + } + } + GIVEN("two-sided bridge") { + ExPolygon bridge{ Polygon::new_scale({ {0,0}, {20,0}, {20,10}, {0,10} }) }; + ExPolygons lower { ExPolygon{ Polygon::new_scale({ {-2,0}, {0,0}, {0,10}, {-2,10} }) } }; + lower.emplace_back(lower.front()); + lower.back().translate(Point::new_scale(22, 0)); + THEN("Bridging angle 0 degrees") { + REQUIRE(check_angle(lower, bridge, 0)); + } + } + GIVEN("for C-shaped overhang") { + ExPolygon bridge{ Polygon::new_scale({ {0,0}, {20,0}, {10,10}, {0,10} }) }; + ExPolygon lower{ Polygon::new_scale({ {0,0}, {0,10}, {10,10}, {10,12}, {-2,12}, {-2,-2}, {22,-2}, {22,0} }) }; + bool valid = check_angle({ lower }, bridge, 135); + THEN("Bridging angle is 135 degrees") { + REQUIRE(valid); + } + } + GIVEN("square overhang with L-shaped anchors") { + ExPolygon bridge{ Polygon::new_scale({ {10,10}, {20,10}, {20,20}, {10,20} }) }; + ExPolygon lower{ Polygon::new_scale({ {10,10}, {10,20}, {20,20}, {30,30}, {0,30}, {0,0} }) }; + bool valid = check_angle({ lower }, bridge, 45., -1., bridge.area() / 2.); + THEN("Bridging angle is 45 degrees") { + REQUIRE(valid); + } + } +} + +SCENARIO("Bridging integration", "[Bridging]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "top_solid_layers", 0 }, + // to prevent bridging on sparse infill + { "bridge_speed", 99 } + }); + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::bridge }, config); + + GCodeReader parser; + const double bridge_speed = config.opt_float("bridge_speed") * 60.; + // angle => length + std::map extrusions; + parser.parse_buffer(gcode, [&extrusions, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + // if the command is a T command, set the the current tool + if (line.cmd() == "G1" && is_approx(bridge_speed, line.new_F(self))) { + // Accumulate lengths of bridging extrusions according to bridging angle. + Line l{ self.xy_scaled(), line.new_XY_scaled(self) }; + size_t angle = scaled(l.direction()); + auto it = extrusions.find(angle); + if (it == extrusions.end()) + it = extrusions.insert(std::make_pair(angle, 0.)).first; + it->second += l.length(); + } + }); + THEN("bridge is generated") { + REQUIRE(! extrusions.empty()); + } + THEN("bridge has the expected direction 0 degrees") { + // Bridging with the longest extrusion. + auto it_longest_extrusion = std::max_element(extrusions.begin(), extrusions.end(), + [](const auto &e1, const auto &e2){ return e1.second < e2.second; }); + REQUIRE(it_longest_extrusion->first == 0); + } +} diff --git a/tests/fff_print/test_cooling.cpp b/tests/fff_print/test_cooling.cpp new file mode 100644 index 0000000000..f743783b11 --- /dev/null +++ b/tests/fff_print/test_cooling.cpp @@ -0,0 +1,274 @@ +#include + +#include +#include + +#include "test_data.hpp" // get access to init_print, etc + +#include "libslic3r/Config.hpp" +#include "libslic3r/GCode.hpp" +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/GCode/CoolingBuffer.hpp" +#include "libslic3r/libslic3r.h" + +using namespace Slic3r; + +std::unique_ptr make_cooling_buffer( + GCode &gcode, + const DynamicPrintConfig &config = DynamicPrintConfig{}, + const std::vector &extruder_ids = { 0 }) +{ + PrintConfig print_config; + print_config.apply(config, true); // ignore_nonexistent + gcode.apply_print_config(print_config); + gcode.set_layer_count(10); + gcode.writer().set_extruders(extruder_ids); + gcode.writer().set_extruder(0); + return std::make_unique(gcode); +} + +SCENARIO("Cooling unit tests", "[Cooling]") { + const std::string gcode1 = "G1 X100 E1 F3000\n"; + // 2 sec + const double print_time1 = 100. / (3000. / 60.); + const std::string gcode2 = gcode1 + "G1 X0 E1 F3000\n"; + // 4 sec + const double print_time2 = 2. * print_time1; + + auto config = DynamicPrintConfig::full_print_config_with({ + // Default cooling settings. + { "bridge_fan_speed", "100" }, + { "cooling", "1" }, + { "fan_always_on", "0" }, + { "fan_below_layer_time", "60" }, + { "max_fan_speed", "100" }, + { "min_print_speed", "10" }, + { "slowdown_below_layer_time", "5" }, + // Default print speeds. + { "bridge_speed", 60 }, + { "external_perimeter_speed", "50%" }, + { "first_layer_speed", 30 }, + { "gap_fill_speed", 20 }, + { "infill_speed", 80 }, + { "perimeter_speed", 60 }, + { "small_perimeter_speed", 15 }, + { "solid_infill_speed", 20 }, + { "top_solid_infill_speed", 15 }, + { "max_print_speed", 80 }, + // Override for tests. + { "disable_fan_first_layers", "0" } + }); + + WHEN("G-code block 3") { + THEN("speed is not altered when elapsed time is greater than slowdown threshold") { + // Print time of gcode. + const double print_time = 100. / (3000. / 60.); + //FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s. + config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true); + bool speed_not_altered = gcode.find("F3000") != gcode.npos; + REQUIRE(speed_not_altered); + } + } + + WHEN("G-code block 4") { + const std::string gcode_src = + "G1 X50 F2500\n" + "G1 F3000;_EXTRUDE_SET_SPEED\n" + "G1 X100 E1\n" + ";_EXTRUDE_END\n" + "G1 E4 F400"; + // Print time of gcode. + const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.); + config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode_src, 0, true); + THEN("speed is altered when elapsed time is lower than slowdown threshold") { + bool speed_is_altered = gcode.find("F3000") == gcode.npos; + REQUIRE(speed_is_altered); + } + THEN("speed is not altered for travel moves") { + bool speed_not_altered = gcode.find("F2500") != gcode.npos; + REQUIRE(speed_not_altered); + } + THEN("speed is not altered for extruder-only moves") { + bool speed_not_altered = gcode.find("F400") != gcode.npos; + REQUIRE(speed_not_altered); + } + } + + WHEN("G-code block 1") { + THEN("fan is not activated when elapsed time is greater than fan threshold") { + config.set_deserialize_strict({ + { "fan_below_layer_time" , int(print_time1 * 0.88) }, + { "slowdown_below_layer_time" , int(print_time1 * 0.99) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode1, 0, true); + bool fan_not_activated = gcode.find("M106") == gcode.npos; + REQUIRE(fan_not_activated); + } + } + WHEN("G-code block 1 with two extruders") { + config.set_deserialize_strict({ + { "cooling", "1, 0" }, + { "fan_below_layer_time", { int(print_time2 + 1.), int(print_time2 + 1.) } }, + { "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 }); + std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true); + THEN("fan is activated for the 1st tool") { + bool ok = gcode.find("M106") == 0; + REQUIRE(ok); + } + THEN("fan is disabled for the 2nd tool") { + bool ok = gcode.find("\nM107") > 0; + REQUIRE(ok); + } + } + WHEN("G-code block 2") { + THEN("slowdown is computed on all objects printing at the same Z") { + config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode2, 0, true); + bool ok = gcode.find("F3000") != gcode.npos; + REQUIRE(ok); + } + THEN("fan is not activated on all objects printing at different Z") { + config.set_deserialize_strict({ + { "fan_below_layer_time", int(print_time2 * 0.65) }, + { "slowdown_below_layer_time", int(print_time2 * 0.7) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + // use an elapsed time which is < the threshold but greater than it when summed twice + std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true); + bool fan_not_activated = gcode.find("M106") == gcode.npos; + REQUIRE(fan_not_activated); + } + THEN("fan is activated on all objects printing at different Z") { + // use an elapsed time which is < the threshold even when summed twice + config.set_deserialize_strict({ + { "fan_below_layer_time", int(print_time2 + 1) }, + { "slowdown_below_layer_time", int(print_time2 + 1) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + // use an elapsed time which is < the threshold but greater than it when summed twice + std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true); + bool fan_activated = gcode.find("M106") != gcode.npos; + REQUIRE(fan_activated); + } + } +} + +SCENARIO("Cooling integration tests", "[Cooling]") { + GIVEN("overhang") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "cooling", { 1 } }, + { "bridge_fan_speed", { 100 } }, + { "fan_below_layer_time", { 0 } }, + { "slowdown_below_layer_time", { 0 } }, + { "bridge_speed", 99 }, + // internal bridges use solid_infil speed + { "bottom_solid_layers", 1 }, + // internal bridges use solid_infil speed + }); + + GCodeReader parser; + int fan = 0; + int fan_with_incorrect_speeds = 0; + int fan_with_incorrect_print_speeds = 0; + int bridge_with_no_fan = 0; + const double bridge_speed = config.opt_float("bridge_speed") * 60; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config), + [&fan, &fan_with_incorrect_speeds, &fan_with_incorrect_print_speeds, &bridge_with_no_fan, bridge_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.cmd_is("M106")) { + line.has_value('S', fan); + if (fan != 255) + ++ fan_with_incorrect_speeds; + } else if (line.cmd_is("M107")) { + fan = 0; + } else if (line.extruding(self) && line.dist_XY(self) > 0) { + if (is_approx(line.new_F(self), bridge_speed)) { + if (fan != 255) + ++ bridge_with_no_fan; + } else { + if (fan != 0) + ++ fan_with_incorrect_print_speeds; + } + } + }); + THEN("bridge fan speed is applied correctly") { + REQUIRE(fan_with_incorrect_speeds == 0); + } + THEN("bridge fan is only turned on for bridges") { + REQUIRE(fan_with_incorrect_print_speeds == 0); + } + THEN("bridge fan is turned on for all bridges") { + REQUIRE(bridge_with_no_fan == 0); + } + } + GIVEN("20mm cube") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "cooling", { 1 } }, + { "fan_below_layer_time", { 0 } }, + { "slowdown_below_layer_time", { 10 } }, + { "min_print_speed", { 0 } }, + { "start_gcode", "" }, + { "first_layer_speed", "100%" }, + { "external_perimeter_speed", 99 } + }); + GCodeReader parser; + const double external_perimeter_speed = config.opt("external_perimeter_speed")->value * 60; + std::vector layer_times; + // z => 1 + std::map layer_external; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config), + [&layer_times, &layer_external, external_perimeter_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.cmd_is("G1")) { + if (line.dist_Z(self) != 0) { + layer_times.emplace_back(0.); + layer_external[scaled(line.new_Z(self))] = 0; + } + double l = line.dist_XY(self); + if (l == 0) + l = line.dist_E(self); + if (l == 0) + l = line.dist_Z(self); + if (l > 0.) { + if (layer_times.empty()) + layer_times.emplace_back(0.); + layer_times.back() += 60. * std::abs(l) / line.new_F(self); + } + if (line.has('F') && line.f() == external_perimeter_speed) + ++ layer_external[scaled(self.z())]; + } + }); + THEN("slowdown_below_layer_time is honored") { + // Account for some inaccuracies. + const double slowdown_below_layer_time = config.opt("slowdown_below_layer_time")->values.front() - 0.2; + size_t minimum_time_honored = std::count_if(layer_times.begin(), layer_times.end(), + [slowdown_below_layer_time](double t){ return t > slowdown_below_layer_time; }); + REQUIRE(minimum_time_honored == layer_times.size()); + } + THEN("slowdown_below_layer_time does not alter external perimeters") { + // Broken by Vojtech + // check that all layers have at least one unaltered external perimeter speed + // my $external = all { $_ > 0 } values %layer_external; + // ok $external, ''; + } + } +} diff --git a/tests/fff_print/test_custom_gcode.cpp b/tests/fff_print/test_custom_gcode.cpp new file mode 100644 index 0000000000..0368b9604f --- /dev/null +++ b/tests/fff_print/test_custom_gcode.cpp @@ -0,0 +1,220 @@ +#include + +#include +#include + +#include "libslic3r/Config.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" + +#include "test_data.hpp" + +using namespace Slic3r; + +#if 0 +SCENARIO("Output file format", "[CustomGCode]") +{ + WHEN("output_file_format set") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "output_filename_format", "ts_[travel_speed]_lh_[layer_height].gcode" }, + { "start_gcode", "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n" } + }); + + Print print; + Model model; + Test::init_print({ Test::TestMesh::cube_2x20x10 }, print, model, config); + + std::string output_file = print.output_filepath(); + THEN("print config options are replaced in output filename") { + output_file.find(std::string("ts_") + ) + } + my ($t, $h) = map $config->$_, qw(travel_speed layer_height); + ok $output_file =~ /ts_${t}_/, ''; + ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename'; + + std::string gcode = print.gcode(print); + THEN("print config options are replaced in custom G-code") { + ok $gcode =~ /TRAVEL:$t/, ''; + } + THEN("region config options are replaced in custom G-code") { + ok $gcode =~ /HEIGHT:$h/, ''; + } + } +} + +SCENARIO("Custom G-code", "[CustomGCode]") +{ + WHEN("start_gcode and layer_gcode set") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "start_gcode", "_MY_CUSTOM_START_GCODE_" }, // to avoid dealing with the nozzle lift in start G-code + { "layer_gcode", "_MY_CUSTOM_LAYER_GCODE_" } + }); + GCodeReader parser; + bool last_move_was_z_change = false; + int num_layer_changes_not_applied = 0; + parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config), + [&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self)) { + if (! was_extruding) + seam_points.emplace_back(self.xy_scaled()); + was_extruding = true; + } else if (! line.cmd_is("M73")) { + // skips remaining time lines (M73) + was_extruding = false; + } + if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) + ++ num_layer_changes_not_applied; + last_move_was_z_change = line.dist_Z(self) > 0; + }); + THEN("custom layer G-code is applied after Z move and before other moves"); + }; + +{ + my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); + $config->set('extruder', 2); + $config->set('first_layer_temperature', [200,205]); + + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for non-zero yet single extruder'; + ok $gcode !~ /M104 S\d+ T0/, 'unused extruder correctly ignored'; + } + + $config->set('infill_extruder', 1); + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /M104 S200 T0/, 'temperature set correctly for first extruder'; + ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder'; + } + + my @start_gcode = (qq! +;__temp0:[first_layer_temperature_0]__ +;__temp1:[first_layer_temperature_1]__ +;__temp2:[first_layer_temperature_2]__ + !, qq! +;__temp0:{first_layer_temperature[0]}__ +;__temp1:{first_layer_temperature[1]}__ +;__temp2:{first_layer_temperature[2]}__ + !); + my @syntax_description = (' (legacy syntax)', ' (new syntax)'); + for my $i (0, 1) { + $config->set('start_gcode', $start_gcode[$i]); + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + # we use the [infill_extruder] placeholder to make sure this test doesn't + # catch a false positive caused by the unparsed start G-code option itself + # being embedded in the G-code + ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated' . $syntax_description[$i]; + ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated' . $syntax_description[$i]; + ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value' . $syntax_description[$i]; + } + } + + $config->set('start_gcode', qq! +;substitution:{if infill_extruder==1}extruder1 + {elsif infill_extruder==2}extruder2 + {else}extruder3{endif} + !); + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /substitution:extruder1/, 'if / else / endif - first block returned'; + } +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('before_layer_gcode', ';BEFORE [layer_num]'); + $config->set('layer_gcode', ';CHANGE [layer_num]'); + $config->set('support_material', 1); + $config->set('layer_height', 0.2); + my $print = Slic3r::Test::init_print('overhang', config => $config); + my $gcode = Slic3r::Test::gcode($print); + + my @before = (); + my @change = (); + foreach my $line (split /\R+/, $gcode) { + if ($line =~ /;BEFORE (\d+)/) { + push @before, $1; + } elsif ($line =~ /;CHANGE (\d+)/) { + push @change, $1; + fail 'inconsistent layer_num before and after layer change' + if $1 != $before[-1]; + } + } + is_deeply \@before, \@change, 'layer_num is consistent before and after layer changes'; + ok !defined(first { $change[$_] != $change[$_-1]+1 } 1..$#change), + 'layer_num grows continously'; # i.e. no duplicates or regressions +} + +{ + my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6,0.6]); + $config->set('start_gcode', qq! +;substitution:{if infill_extruder==1}if block + {elsif infill_extruder==2}elsif block 1 + {elsif infill_extruder==3}elsif block 2 + {elsif infill_extruder==4}elsif block 3 + {else}endif block{endif} + !); + my @returned = ('', 'if block', 'elsif block 1', 'elsif block 2', 'elsif block 3', 'endif block'); + for my $i (1,2,3,4,5) { + $config->set('infill_extruder', $i); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + my $found_other = 0; + for my $j (1,2,3,4,5) { + next if $i == $j; + $found_other = 1 if $gcode =~ /substitution:$returned[$j]/; + } + ok $gcode =~ /substitution:$returned[$i]/, 'if / else / endif - ' . $returned[$i] . ' returned'; + ok !$found_other, 'if / else / endif - only ' . $returned[$i] . ' returned'; + } +} + +{ + my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); + $config->set('start_gcode', + ';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' . + '{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' . + '{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end'); + for my $i (1,2,3) { + $config->set('infill_extruder', $i); + for my $j (1,2) { + $config->set('perimeter_extruder', $j); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /substitution:block$i$j:end/, "two level if / else / endif - block$i$j returned"; + } + } +} + +{ + my $config = Slic3r::Config->new; + $config->set('start_gcode', + ';substitution:{if notes=="MK2"}MK2{elsif notes=="MK3"}MK3{else}MK1{endif}:end'); + for my $printer_name ("MK2", "MK3", "MK1") { + $config->set('notes', $printer_name); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /substitution:$printer_name:end/, "printer name $printer_name matched"; + } +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('complete_objects', 1); + $config->set('between_objects_gcode', '_MY_CUSTOM_GCODE_'); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 3); + my $gcode = Slic3r::Test::gcode($print); + is scalar(() = $gcode =~ /^_MY_CUSTOM_GCODE_/gm), 2, 'between_objects_gcode is applied correctly'; +} + +#endif \ No newline at end of file diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index f7077007db..a52583cfca 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -26,6 +26,7 @@ const std::unordered_map mesh_names { std::pair(TestMesh::V, "V"), std::pair(TestMesh::_40x10, "40x10"), std::pair(TestMesh::cube_20x20x20, "cube_20x20x20"), + std::pair(TestMesh::cube_2x20x10, "cube_2x20x10"), std::pair(TestMesh::sphere_50mm, "sphere_50mm"), std::pair(TestMesh::bridge, "bridge"), std::pair(TestMesh::bridge_with_hole, "bridge_with_hole"), @@ -49,6 +50,9 @@ TriangleMesh mesh(TestMesh m) case TestMesh::cube_20x20x20: mesh = Slic3r::make_cube(20, 20, 20); break; + case TestMesh::cube_2x20x10: + mesh = Slic3r::make_cube(2, 20, 10); + break; case TestMesh::sphere_50mm: mesh = Slic3r::make_sphere(50, PI / 243.0); break; @@ -187,6 +191,19 @@ TriangleMesh mesh(TestMesh m) return mesh; } +TriangleMesh mesh(TestMesh min, Vec3d translate, Vec3d scale) +{ + TriangleMesh m = mesh(min); + m.translate(translate.cast()); + m.scale(scale.cast()); + return m; +} + +TriangleMesh mesh(TestMesh m, Vec3d translate, double scale) +{ + return mesh(m, translate, Vec3d(scale, scale, scale)); +} + static bool verbose_gcode() { const char *v = std::getenv("SLIC3R_TESTS_GCODE"); diff --git a/tests/fff_print/test_data.hpp b/tests/fff_print/test_data.hpp index 573ae58a5c..b699e5e4e6 100644 --- a/tests/fff_print/test_data.hpp +++ b/tests/fff_print/test_data.hpp @@ -21,6 +21,7 @@ enum class TestMesh { V, _40x10, cube_20x20x20, + cube_2x20x10, sphere_50mm, bridge, bridge_with_hole, diff --git a/tests/fff_print/test_extrusion_entity.cpp b/tests/fff_print/test_extrusion_entity.cpp index 9bef52a5c3..c98e0ec1a4 100644 --- a/tests/fff_print/test_extrusion_entity.cpp +++ b/tests/fff_print/test_extrusion_entity.cpp @@ -5,6 +5,7 @@ #include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Point.hpp" +#include "libslic3r/ShortestPath.hpp" #include "libslic3r/libslic3r.h" #include "test_data.hpp" @@ -83,3 +84,67 @@ SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") { } } } + +TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { + struct Test { + Polylines unchained; + Polylines chained; + Point initial_point; + }; + std::vector tests { + { + { + { {0,15}, {0,18}, {0,20} }, + { {0,10}, {0,8}, {0,5} } + }, + { + { {0,20}, {0,18}, {0,15} }, + { {0,10}, {0,8}, {0,5} } + }, + { 0,30 } + }, + { + { + { {4,0}, {10,0}, {15,0} }, + { {10,5}, {15,5}, {20,5} } + }, + { + { {20,5}, {15,5}, {10,5} }, + { {15,0}, {10,0}, {4,0} } + }, + { 30,0 } + }, + { + { + { {15,0}, {10,0}, {4,0} }, + { {10,5}, {15,5}, {20,5} } + }, + { + { {20,5}, {15,5}, {10,5} }, + { {15,0}, {10,0}, {4,0} } + }, + { 30,0 } + }, + }; + for (const Test &test : tests) { + Polylines chained = chain_polylines(test.unchained, &test.initial_point); + REQUIRE(chained == test.chained); + ExtrusionEntityCollection unchained_extrusions; + extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained, + erInternalInfill, 0., 0.4f, 0.3f); + ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); + REQUIRE(chained_extrusions.entities.size() == test.chained.size()); + for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + const Points &p1 = test.chained[i].points; + const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + REQUIRE(p1 == p2); + } + } +} + +TEST_CASE("ExtrusionEntityCollection: Chained path with no explicit starting point", "[ExtrusionEntity]") { + auto polylines = Polylines { { { 0, 15 }, {0, 18}, {0, 20} }, { { 0, 10 }, {0, 8}, {0, 5} } }; + auto target = Polylines { { {0, 5}, {0, 8}, { 0, 10 } }, { { 0, 15 }, {0, 18}, {0, 20} } }; + auto chained = chain_polylines(polylines); + REQUIRE(chained == target); +} diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 8e311282e0..0ccb27d9ab 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -7,6 +7,7 @@ #include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Flow.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SVG.hpp" #include "libslic3r/libslic3r.h" @@ -14,6 +15,7 @@ #include "test_data.hpp" using namespace Slic3r; +using namespace std::literals; bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0); @@ -43,7 +45,7 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { SECTION("Square") { Slic3r::Points test_set; test_set.reserve(4); - std::vector points {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)}; + std::vector points { {0,0}, {100,0}, {100,100}, {0,100} }; for (size_t i = 0; i < 4; ++i) { std::transform(points.cbegin()+i, points.cend(), std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); @@ -118,24 +120,26 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { REQUIRE(std::abs(paths[0].length() - static_cast(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length } - SECTION("Rotated Square") { - Slic3r::Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)}; - Slic3r::ExPolygon expolygon(square); + SECTION("Rotated Square produces one continuous path") { + Slic3r::ExPolygon expolygon(Polygon::new_scale({ {0, 0}, {50, 0}, {50, 50}, {0, 50} })); std::unique_ptr filler(Slic3r::Fill::new_from_type("rectilinear")); - filler->bounding_box = get_extents(expolygon.contour); + filler->bounding_box = get_extents(expolygon); filler->angle = 0; Surface surface(stTop, expolygon); - auto flow = Slic3r::Flow(0.69f, 0.4f, 0.50f); + // width, height, nozzle_dmr + auto flow = Slic3r::Flow(0.69f, 0.4f, 0.5f); FillParams fill_params; - fill_params.density = 1.0; - filler->spacing = flow.spacing(); - - for (auto angle : { 0.0, 45.0}) { - surface.expolygon.rotate(angle, Point(0,0)); - Polylines paths = filler->fill_surface(&surface, fill_params); - REQUIRE(paths.size() == 1); + for (auto density : { 0.4, 1.0 }) { + fill_params.density = density; + filler->spacing = flow.spacing(); + for (auto angle : { 0.0, 45.0}) { + surface.expolygon.rotate(angle, Point(0,0)); + Polylines paths = filler->fill_surface(&surface, fill_params); + // one continuous path + REQUIRE(paths.size() == 1); + } } } @@ -190,202 +194,225 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { } } +SCENARIO("Infill does not exceed perimeters", "[Fill]") +{ + auto test = [](const std::string_view pattern) { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", "0.4, 0.4, 0.4, 0.4" }, + { "fill_pattern", pattern }, + { "top_fill_pattern", pattern }, + { "bottom_fill_pattern", pattern }, + { "perimeters", 1 }, + { "skirts", 0 }, + { "fill_density", 0.2 }, + { "layer_height", 0.05 }, + { "perimeter_extruder", 1 }, + { "infill_extruder", 2 } + }); + + WHEN("40mm cube sliced") { + std::string gcode = Slic3r::Test::slice({ mesh(Slic3r::Test::TestMesh::cube_20x20x20, Vec3d::Zero(), 2.0) }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + THEN("infill does not exceed perimeters") { + GCodeReader parser; + const int perimeter_extruder = config.opt_int("perimeter_extruder"); + const int infill_extruder = config.opt_int("infill_extruder"); + int tool = -1; + Points perimeter_points; + Points infill_points; + parser.parse_buffer(gcode, [&tool, &perimeter_points, &infill_points, perimeter_extruder, infill_extruder] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + // if the command is a T command, set the the current tool + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == perimeter_extruder) + perimeter_points.emplace_back(line.new_XY_scaled(self)); + else if (tool == infill_extruder) + infill_points.emplace_back(line.new_XY_scaled(self)); + } + }); + auto convex_hull = Geometry::convex_hull(perimeter_points); + int num_inside = std::count_if(infill_points.begin(), infill_points.end(), [&convex_hull](const Point &pt){ return convex_hull.contains(pt); }); + REQUIRE(num_inside == infill_points.size()); + } + } + }; + + GIVEN("Rectilinear") { test("rectilinear"sv); } + GIVEN("Honeycomb") { test("honeycomb"sv); } + GIVEN("HilbertCurve") { test("hilbertcurve"sv); } + GIVEN("Concentric") { test("concentric"sv); } +} + +SCENARIO("Infill only where needed", "[Fill]") +{ + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.4, 0.4, 0.4, 0.4" }, + { "infill_only_where_needed", true }, + { "bottom_solid_layers", 0 }, + { "infill_extruder", 2 }, + { "infill_extrusion_width", 0.5 }, + { "wipe_into_infill", false }, + { "fill_density", 0.4 }, + // for preventing speeds from being altered + { "cooling", "0, 0, 0, 0" }, + // for preventing speeds from being altered + { "first_layer_speed", "100%" } + }); + + auto test = [&config]() -> double { + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::pyramid }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + GCodeReader parser; + int tool = -1; + const int infill_extruder = config.opt_int("infill_extruder"); + Points infill_points; + parser.parse_buffer(gcode, [&tool, &infill_points, infill_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + // if the command is a T command, set the the current tool + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == infill_extruder) { + infill_points.emplace_back(self.xy_scaled()); + infill_points.emplace_back(line.new_XY_scaled(self)); + } + } + }); + // prevent calling convex_hull() with no points + THEN("infill not empty") { + REQUIRE(! infill_points.empty()); + } + + auto opt_width = config.opt("infill_extrusion_width"); + REQUIRE(! opt_width->percent); + Polygons convex_hull = expand(Geometry::convex_hull(infill_points), scaled(opt_width->value / 2)); + return SCALING_FACTOR * SCALING_FACTOR * std::accumulate(convex_hull.begin(), convex_hull.end(), 0., [](double acc, const Polygon &poly){ return acc + poly.area(); }); + }; + + double tolerance = 5; // mm^2 + + GIVEN("solid_infill_below_area == 0") { + config.opt_float("solid_infill_below_area") = 0; + WHEN("pyramid is sliced ") { + auto area = test(); + THEN("no infill is generated when using infill_only_where_needed on a pyramid") { + REQUIRE(area < tolerance); + } + } + } + GIVEN("solid_infill_below_area == 70") { + config.opt_float("solid_infill_below_area") = 70; + WHEN("pyramid is sliced ") { + auto area = test(); + THEN("infill is only generated under the forced solid shells") { + REQUIRE(std::abs(area - 70) < tolerance); + } + } + } +} + +SCENARIO("Infill density zero", "[Fill]") +{ + WHEN("20mm cube is sliced") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "skirts", 0 }, + { "perimeters", 1 }, + { "fill_density", 0 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 }, + { "solid_infill_below_area", 20000000 }, + { "solid_infill_every_layers", 2 }, + { "perimeter_speed", 99 }, + { "external_perimeter_speed", 99 }, + { "cooling", "0" }, + { "first_layer_speed", "100%" } + }); + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + THEN("solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0") { + GCodeReader parser; + const double perimeter_speed = config.opt_float("perimeter_speed"); + std::map layers_with_extrusion; + parser.parse_buffer(gcode, [&layers_with_extrusion, perimeter_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + if (std::abs(f - perimeter_speed * 60.) > 0.01) + // It is a perimeter. + layers_with_extrusion[self.z()] = f; + } + }); + REQUIRE(layers_with_extrusion.empty()); + } + } + + WHEN("A is sliced") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "skirts", 0 }, + { "perimeters", 3 }, + { "fill_density", 0 }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "nozzle_diameter", "0.35,0.35,0.35,0.35" }, + { "infill_extruder", 2 }, + { "solid_infill_extruder", 2 }, + { "infill_extrusion_width", 0.52 }, + { "solid_infill_extrusion_width", 0.52 }, + { "first_layer_extrusion_width", 0 } + }); + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::A }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + THEN("no missing parts in solid shell when fill_density is 0") { + GCodeReader parser; + int tool = -1; + const int infill_extruder = config.opt_int("infill_extruder"); + std::map infill; + parser.parse_buffer(gcode, [&tool, &infill, infill_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == infill_extruder) + infill[scaled(self.z())].emplace_back(self.xy_scaled(), line.new_XY_scaled(self)); + } + }); + auto opt_width = config.opt("infill_extrusion_width"); + REQUIRE(! opt_width->percent); + auto grow_d = scaled(opt_width->value / 2); + auto inflate_lines = [grow_d](const Lines &lines) { + Polygons out; + for (const Line &line : lines) + append(out, offset(Polyline{ line.a, line.b }, grow_d, Slic3r::ClipperLib::jtSquare, 3.)); + return union_(out); + }; + Polygons layer0_infill = inflate_lines(infill[scaled(0.2)]); + Polygons layer1_infill = inflate_lines(infill[scaled(0.4)]); + ExPolygons poly = opening_ex(diff_ex(layer0_infill, layer1_infill), grow_d); + const double threshold = 2. * sqr(grow_d * 2.); + int missing_parts = std::count_if(poly.begin(), poly.end(), [threshold](const ExPolygon &poly){ return poly.area() > threshold; }); + REQUIRE(missing_parts == 0); + } + } +} + /* -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([4,0], [10,0], [15,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - 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]), - ); - is_deeply - [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - 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]), - ); - is_deeply - [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { - my $config = Slic3r::Config->new_from_defaults; - $config->set('fill_pattern', $pattern); - $config->set('external_fill_pattern', $pattern); - $config->set('perimeters', 1); - $config->set('skirts', 0); - $config->set('fill_density', 20); - $config->set('layer_height', 0.05); - $config->set('perimeter_extruder', 1); - $config->set('infill_extruder', 2); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); - ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; - my $tool = undef; - my @perimeter_points = my @infill_points = (); - 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) { - if ($tool == $config->perimeter_extruder-1) { - push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } elsif ($tool == $config->infill_extruder-1) { - push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } - } - }); - my $convex_hull = convex_hull(\@perimeter_points); - ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('infill_only_where_needed', 1); - $config->set('bottom_solid_layers', 0); - $config->set('infill_extruder', 2); - $config->set('infill_extrusion_width', 0.5); - $config->set('fill_density', 40); - $config->set('cooling', 0); # for preventing speeds from being altered - $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('pyramid', config => $config); - - my $tool = undef; - my @infill_extrusions = (); # array of polylines - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - push @infill_extrusions, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points - - my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]); - return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))}); - }; - - my $tolerance = 5; # mm^2 - - $config->set('solid_infill_below_area', 0); - ok $test->() < $tolerance, - 'no infill is generated when using infill_only_where_needed on a pyramid'; - - $config->set('solid_infill_below_area', 70); - ok abs($test->() - $config->solid_infill_below_area) < $tolerance, - 'infill is only generated under the forced solid shells'; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('solid_infill_below_area', 20000000); - $config->set('solid_infill_every_layers', 2); - $config->set('perimeter_speed', 99); - $config->set('external_perimeter_speed', 99); - $config->set('cooling', 0); - $config->set('first_layer_speed', '100%'); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my %layers_with_extrusion = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { - if (($args->{F} // $self->F) != $config->perimeter_speed*60) { - $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F); - } - } - }); - - ok !%layers_with_extrusion, - "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('fill_density', 0); - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.35]); - $config->set('infill_extruder', 2); - $config->set('solid_infill_extruder', 2); - $config->set('infill_extrusion_width', 0.52); - $config->set('solid_infill_extrusion_width', 0.52); - $config->set('first_layer_extrusion_width', 0); - - my $print = Slic3r::Test::init_print('A', config => $config); - my %infill = (); # Z => [ Line, Line ... ] - my $tool = undef; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - my $z = 1 * $self->Z; - $infill{$z} ||= []; - push @{$infill{$z}}, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - my $grow_d = scale($config->infill_extrusion_width)/2; - my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); - my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); - my $diff = diff($layer0_infill, $layer1_infill); - $diff = offset2_ex($diff, -$grow_d, +$grow_d); - $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; - is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; -} - { # GH: #2697 my $config = Slic3r::Config->new_from_defaults; @@ -427,6 +454,78 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my @holes = map @{$_->holes}, @$covered; ok sum(map unscale unscale $_->area*-1, @holes) < 1, 'no gaps between top solid infill and perimeters'; } + +{ + skip "The FillRectilinear2 does not fill the surface completely", 1; + + my $test = sub { + my ($expolygon, $flow_spacing, $angle, $density) = @_; + + my $filler = Slic3r::Filler->new_from_type('rectilinear'); + $filler->set_bounding_box($expolygon->bounding_box); + $filler->set_angle($angle // 0); + # Adjust line spacing to fill the region. + $filler->set_dont_adjust(0); + $filler->set_link_max_length(scale(1.2*$flow_spacing)); + my $surface = Slic3r::Surface->new( + surface_type => S_TYPE_BOTTOM, + expolygon => $expolygon, + ); + my $flow = Slic3r::Flow->new( + width => $flow_spacing, + height => 0.4, + nozzle_diameter => $flow_spacing, + ); + $filler->set_spacing($flow->spacing); + my $paths = $filler->fill_surface( + $surface, + layer_height => $flow->height, + density => $density // 1, + ); + + # check whether any part was left uncovered + 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 + my $uncovered_filtered = [ grep $_->area > (scale $flow_spacing)**2, @$uncovered ]; + + is scalar(@$uncovered_filtered), 0, 'solid surface is fully filled'; + + if (0 && @$uncovered_filtered) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("uncovered.svg", + no_arrows => 1, + expolygons => [ $expolygon ], + blue_expolygons => [ @$uncovered ], + red_expolygons => [ @$uncovered_filtered ], + polylines => [ @$paths ], + ); + exit; + } + }; + + my $expolygon = Slic3r::ExPolygon->new([ + [6883102, 9598327.01296997], + [6883102, 20327272.01297], + [3116896, 20327272.01297], + [3116896, 9598327.01296997], + ]); + $test->($expolygon, 0.55); + + for (1..20) { + $expolygon->scale(1.05); + $test->($expolygon, 0.55); + } + + $expolygon = Slic3r::ExPolygon->new( + [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] + ); + $test->($expolygon, 0.524341649025257); + + $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); + $test->($expolygon, 0.5, 45, 0.99); # non-solid infill +} */ bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle, double density) diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index 81f748e19a..b24b59cc61 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -5,8 +5,6 @@ #include "test_data.hpp" // get access to init_print, etc -#include "libslic3r/Config.hpp" -#include "libslic3r/Model.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/GCodeReader.hpp" #include "libslic3r/Flow.hpp" @@ -16,61 +14,118 @@ using namespace Slic3r::Test; using namespace Slic3r; SCENARIO("Extrusion width specifics", "[Flow]") { - GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") { - // this is a sharedptr - DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize_strict({ - { "brim_width", 2 }, - { "skirts", 1 }, - { "perimeters", 3 }, - { "fill_density", "40%" }, - { "first_layer_height", 0.3 } - }); - WHEN("first layer width set to 2mm") { - Slic3r::Model model; - config.set("first_layer_extrusion_width", 2); - Slic3r::Print print; - Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, config); - - std::vector E_per_mm_bottom; - std::string gcode = Test::gcode(print); - Slic3r::GCodeReader parser; - const double layer_height = config.opt_float("layer_height"); - parser.parse_buffer(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) - { - if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer - if (line.extruding(self) && line.dist_XY(self) > 0) { - E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self)); - } - } - }); - THEN(" First layer width applies to everything on first layer.") { - bool pass = false; - double avg_E = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast(E_per_mm_bottom.size()); - - pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0); - REQUIRE(pass == true); - REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion - } - THEN(" First layer width does not apply to upper layer.") { + auto test = [](const DynamicPrintConfig &config) { + Slic3r::GCodeReader parser; + const double layer_height = config.opt_float("layer_height"); + std::vector E_per_mm_bottom; + parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config), + [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) + { + if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer + if (line.extruding(self) && line.dist_XY(self) > 0) + E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self)); } + }); + THEN("First layer width applies to everything on first layer.") { + REQUIRE(E_per_mm_bottom.size() > 0); + const double E_per_mm_avg = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast(E_per_mm_bottom.size()); + bool pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [E_per_mm_avg] (const double& v) { return v == Approx(E_per_mm_avg); }) == 0); + REQUIRE(pass); + } + THEN("First layer width does not apply to upper layer.") { + } + }; + GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 1 }, + { "brim_width", 2 }, + { "perimeters", 3 }, + { "fill_density", "40%" }, + { "first_layer_height", 0.3 }, + { "first_layer_extrusion_width", "2" }, + }); + WHEN("Slicing a 20mm cube") { + test(config); + } + } + GIVEN("A config with more options and a 20mm cube ") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 1 }, + { "brim_width", 2 }, + { "perimeters", 3 }, + { "fill_density", "40%" }, + { "layer_height", "0.35" }, + { "first_layer_height", "0.35" }, + { "bottom_solid_layers", 1 }, + { "first_layer_extrusion_width", "2" }, + { "filament_diameter", "3" }, + { "nozzle_diameter", "0.5" } + }); + WHEN("Slicing a 20mm cube") { + test(config); } } } -// needs gcode export + SCENARIO(" Bridge flow specifics.", "[Flow]") { + auto config = DynamicPrintConfig::full_print_config_with({ + { "bridge_speed", 99 }, + { "bridge_flow_ratio", 1 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" } + }); + + auto test = [](const DynamicPrintConfig &config) { + GCodeReader parser; + const double bridge_speed = config.opt_float("bridge_speed") * 60.; + std::vector E_per_mm; + parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config), + [&E_per_mm, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.extruding(self) && line.dist_XY(self) > 0) { + if (is_approx(line.new_F(self), bridge_speed)) + E_per_mm.emplace_back(line.dist_E(self) / line.dist_XY(self)); + } + }); + const double nozzle_dmr = config.opt("nozzle_diameter")->get_at(0); + const double filament_dmr = config.opt("filament_diameter")->get_at(0); + const double bridge_mm_per_mm = sqr(nozzle_dmr / filament_dmr) * config.opt_float("bridge_flow_ratio"); + size_t num_errors = std::count_if(E_per_mm.begin(), E_per_mm.end(), + [bridge_mm_per_mm](double v){ return std::abs(v - bridge_mm_per_mm) > 0.01; }); + return num_errors == 0; + }; + GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") { - WHEN("bridge_flow_ratio is set to 1.0") { + WHEN("bridge_flow_ratio is set to 0.5 and extrusion width to default") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", "0" } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } - WHEN("bridge_flow_ratio is set to 0.5") { + WHEN("bridge_flow_ratio is set to 2.0 and extrusion width to default") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 2.0}, { "extrusion_width", "0" } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } - WHEN("bridge_flow_ratio is set to 2.0") { + WHEN("bridge_flow_ratio is set to 0.5 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", 0.4 } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); + } + } + WHEN("bridge_flow_ratio is set to 1.0 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 1.0}, { "extrusion_width", 0.4 } }); + THEN("Output flow is as expected.") { + REQUIRE(test(config)); + } + } + WHEN("bridge_flow_ratio is set to 2 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 2.}, { "extrusion_width", 0.4 } }); + THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } } diff --git a/tests/fff_print/test_gaps.cpp b/tests/fff_print/test_gaps.cpp new file mode 100644 index 0000000000..a096087ce6 --- /dev/null +++ b/tests/fff_print/test_gaps.cpp @@ -0,0 +1,60 @@ +#include + +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Layer.hpp" + +#include "test_data.hpp" // get access to init_print, etc + +using namespace Slic3r::Test; +using namespace Slic3r; + +SCENARIO("Gaps", "[Gaps]") { + GIVEN("Two hollow squares") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "perimeter_speed", 66 }, + { "external_perimeter_speed", 66 }, + { "small_perimeter_speed", 66 }, + { "gap_fill_speed", 99 }, + { "perimeters", 1 }, + // to prevent speeds from being altered + { "cooling", 0 }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + { "perimeter_extrusion_width", 0.35 }, + { "first_layer_extrusion_width", 0.35 } + }); + + GCodeReader parser; + const double perimeter_speed = config.opt_float("perimeter_speed") * 60; + const double gap_fill_speed = config.opt_float("gap_fill_speed") * 60; + std::string last; // perimeter or gap + Points perimeter_points; + int gap_fills_outside_last_perimeters = 0; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::two_hollow_squares }, config), + [&perimeter_points, &gap_fills_outside_last_perimeters, &last, perimeter_speed, gap_fill_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + Point point = line.new_XY_scaled(self); + if (is_approx(f, perimeter_speed)) { + if (last == "gap") + perimeter_points.clear(); + perimeter_points.emplace_back(point); + last = "perimeter"; + } else if (is_approx(f, gap_fill_speed)) { + Polygon convex_hull = Geometry::convex_hull(perimeter_points); + if (! convex_hull.contains(point)) + ++ gap_fills_outside_last_perimeters; + last = "gap"; + } + } + }); + THEN("gap fills are printed before leaving islands") { + REQUIRE(gap_fills_outside_last_perimeters == 0); + } + } +} diff --git a/tests/fff_print/test_multi.cpp b/tests/fff_print/test_multi.cpp new file mode 100644 index 0000000000..03c7f644b0 --- /dev/null +++ b/tests/fff_print/test_multi.cpp @@ -0,0 +1,268 @@ +#include + +#include +#include + +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/libslic3r.h" + +#include "test_data.hpp" + +using namespace Slic3r; +using namespace std::literals; + +SCENARIO("Basic tests", "[Multi]") +{ + WHEN("Slicing multi-material print with non-consecutive extruders") { + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, + { + { "nozzle_diameter", "0.6, 0.6, 0.6, 0.6" }, + { "extruder", 2 }, + { "infill_extruder", 4 }, + { "support_material_extruder", 0 } + }); + THEN("Sliced successfully") { + REQUIRE(! gcode.empty()); + } + THEN("T3 toolchange command found") { + bool T1_found = gcode.find("\nT3\n") != gcode.npos; + REQUIRE(T1_found); + } + } + WHEN("Slicing with multiple skirts with a single, non-zero extruder") { + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, + { + { "nozzle_diameter", "0.6, 0.6, 0.6, 0.6" }, + { "perimeter_extruder", 2 }, + { "infill_extruder", 2 }, + { "support_material_extruder", 2 }, + { "support_material_interface_extruder", 2 }, + }); + THEN("Sliced successfully") { + REQUIRE(! gcode.empty()); + } + } +} + +SCENARIO("Ooze prevention", "[Multi]") +{ + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", "0.6, 0.6, 0.6, 0.6" }, + { "raft_layers", 2 }, + { "infill_extruder", 2 }, + { "solid_infill_extruder", 3 }, + { "support_material_extruder", 4 }, + { "ooze_prevention", 1 }, + { "extruder_offset", "0x0, 20x0, 0x20, 20x20" }, + { "temperature", "200, 180, 170, 160" }, + { "first_layer_temperature", "206, 186, 166, 156" }, + // test that it doesn't crash when this is supplied + { "toolchange_gcode", "T[next_extruder] ;toolchange" } + }); + FullPrintConfig print_config; + print_config.apply(config); + + // Since July 2019, PrusaSlicer only emits automatic Tn command in case that the toolchange_gcode is empty + // The "T[next_extruder]" is therefore needed in this test. + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + + GCodeReader parser; + int tool = -1; + int tool_temp[] = { 0, 0, 0, 0}; + Points toolchange_points; + Points extrusion_points; + parser.parse_buffer(gcode, [&tool, &tool_temp, &toolchange_points, &extrusion_points, &print_config] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + // if the command is a T command, set the the current tool + if (boost::starts_with(line.cmd(), "T")) { + // Ignore initial toolchange. + if (tool != -1) { + int expected_temp = is_approx(self.z(), print_config.get_abs_value("first_layer_height") + print_config.z_offset) ? + print_config.first_layer_temperature.get_at(tool) : + print_config.temperature.get_at(tool); + if (tool_temp[tool] != expected_temp + print_config.standby_temperature_delta) + throw std::runtime_error("Standby temperature was not set before toolchange."); + toolchange_points.emplace_back(self.xy_scaled()); + } + tool = atoi(line.cmd().data() + 1); + } else if (line.cmd_is("M104") || line.cmd_is("M109")) { + // May not be defined on this line. + int t = tool; + line.has_value('T', t); + // Should be available on this line. + int s; + if (! line.has_value('S', s)) + throw std::runtime_error("M104 or M109 without S"); + if (tool_temp[t] == 0 && s != print_config.first_layer_temperature.get_at(t) + print_config.standby_temperature_delta) + throw std::runtime_error("initial temperature is not equal to first layer temperature + standby delta"); + tool_temp[t] = s; + } else if (line.cmd_is("G1") && line.extruding(self) && line.dist_XY(self) > 0) { + extrusion_points.emplace_back(line.new_XY_scaled(self) + scaled(print_config.extruder_offset.get_at(tool))); + } + }); + + Polygon convex_hull = Geometry::convex_hull(extrusion_points); + + THEN("all nozzles are outside skirt at toolchange") { + Points t; + sort_remove_duplicates(toolchange_points); + size_t inside = 0; + for (const auto &point : toolchange_points) + for (const Vec2d &offset : print_config.extruder_offset.values) { + Point p = point + scaled(offset); + if (convex_hull.contains(p)) + ++ inside; + } + REQUIRE(inside == 0); + } + +#if 0 + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "ooze_prevention_test.svg", + no_arrows => 1, + polygons => [$convex_hull], + red_points => \@t, + points => \@toolchange_points, + ); +#endif + + THEN("all toolchanges happen within expected area") { + // offset the skirt by the maximum displacement between extruders plus a safety extra margin + const float delta = scaled(20. * sqrt(2.) + 1.); + Polygon outer_convex_hull = expand(convex_hull, delta).front(); + size_t inside = std::count_if(toolchange_points.begin(), toolchange_points.end(), [&outer_convex_hull](const Point &p){ return outer_convex_hull.contains(p); }); + REQUIRE(inside == toolchange_points.size()); + } +} + +std::string slice_stacked_cubes(const DynamicPrintConfig &config, const DynamicPrintConfig &volume1config, const DynamicPrintConfig &volume2config) +{ + Model model; + ModelObject *object = model.add_object(); + object->name = "object.stl"; + ModelVolume *v1 = object->add_volume(Test::mesh(Test::TestMesh::cube_20x20x20)); + v1->set_material_id("lower_material"); + v1->config.assign_config(volume1config); + ModelVolume *v2 = object->add_volume(Test::mesh(Test::TestMesh::cube_20x20x20)); + v2->set_material_id("upper_material"); + v2->translate(0., 0., 20.); + v2->config.assign_config(volume2config); + object->add_instance(); + object->ensure_on_bed(); + Print print; + print.auto_assign_extruders(object); + THEN("auto_assign_extruders() assigned correct extruder to first volume") { + REQUIRE(v1->config.extruder() == 1); + } + THEN("auto_assign_extruders() assigned correct extruder to second volume") { + REQUIRE(v2->config.extruder() == 2); + } + print.apply(model, config); + print.validate(); + return Test::gcode(print); +} + +SCENARIO("Stacked cubes", "[Multi]") +{ + DynamicPrintConfig lower_config; + lower_config.set_deserialize_strict({ + { "extruder", 1 }, + { "bottom_solid_layers", 0 }, + { "top_solid_layers", 1 }, + }); + + DynamicPrintConfig upper_config; + upper_config.set_deserialize_strict({ + { "extruder", 2 }, + { "bottom_solid_layers", 1 }, + { "top_solid_layers", 0 } + }); + + static constexpr const double solid_infill_speed = 99; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", "0.6, 0.6, 0.6, 0.6" }, + { "fill_density", 0 }, + { "solid_infill_speed", solid_infill_speed }, + { "top_solid_infill_speed", solid_infill_speed }, + // for preventing speeds from being altered + { "cooling", "0, 0, 0, 0" }, + // for preventing speeds from being altered + { "first_layer_speed", "100%" } + }); + + auto test_shells = [](const std::string &gcode) { + GCodeReader parser; + int tool = -1; + // Scaled Z heights. + std::set T0_shells, T1_shells; + parser.parse_buffer(gcode, [&tool, &T0_shells, &T1_shells] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1); + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (is_approx(line.new_F(self), solid_infill_speed * 60.) && (tool == 0 || tool == 1)) + (tool == 0 ? T0_shells : T1_shells).insert(scaled(self.z())); + } + }); + return std::make_pair(T0_shells, T1_shells); + }; + + WHEN("Interface shells disabled") { + std::string gcode = slice_stacked_cubes(config, lower_config, upper_config); + auto [t0, t1] = test_shells(gcode); + THEN("no interface shells") { + REQUIRE(t0.empty()); + REQUIRE(t1.empty()); + } + } + WHEN("Interface shells enabled") { + config.set_deserialize_strict("interface_shells", "1"); + std::string gcode = slice_stacked_cubes(config, lower_config, upper_config); + auto [t0, t1] = test_shells(gcode); + THEN("top interface shells") { + REQUIRE(t0.size() == lower_config.opt_int("top_solid_layers")); + } + THEN("bottom interface shells") { + REQUIRE(t1.size() == upper_config.opt_int("bottom_solid_layers")); + } + } + WHEN("Slicing with auto-assigned extruders") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", "0.6,0.6,0.6,0.6" }, + { "layer_height", 0.4 }, + { "first_layer_height", 0.4 }, + { "skirts", 0 } + }); + std::string gcode = slice_stacked_cubes(config, DynamicPrintConfig{}, DynamicPrintConfig{}); + GCodeReader parser; + int tool = -1; + // Scaled Z heights. + std::set T0_shells, T1_shells; + parser.parse_buffer(gcode, [&tool, &T0_shells, &T1_shells](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1); + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == 0 && self.z() > 20) + // Layers incorrectly extruded with T0 at the top object. + T0_shells.insert(scaled(self.z())); + else if (tool == 1 && self.z() < 20) + // Layers incorrectly extruded with T1 at the bottom object. + T1_shells.insert(scaled(self.z())); + } + }); + THEN("T0 is never used for upper object") { + REQUIRE(T0_shells.empty()); + } + THEN("T0 is never used for lower object") { + REQUIRE(T1_shells.empty()); + } + } +} diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp new file mode 100644 index 0000000000..a3f11113aa --- /dev/null +++ b/tests/fff_print/test_perimeters.cpp @@ -0,0 +1,599 @@ +#include + +#include +#include + +#include "libslic3r/Config.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/PerimeterGenerator.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SurfaceCollection.hpp" +#include "libslic3r/libslic3r.h" + +#include "test_data.hpp" + +using namespace Slic3r; + +SCENARIO("Perimeter nesting", "[Perimeters]") +{ + struct TestData { + ExPolygons expolygons; + // expected number of loops + int total; + // expected number of external loops + int external; + // expected external perimeter + std::vector ext_order; + // expected number of internal contour loops + int cinternal; + // expected number of ccw loops + int ccw; + // expected ccw/cw order + std::vector ccw_order; + // expected nesting order + std::vector> nesting; + }; + + FullPrintConfig config; + + auto test = [&config](const TestData &data) { + SurfaceCollection slices; + slices.append(data.expolygons, stInternal); + + ExtrusionEntityCollection loops; + ExtrusionEntityCollection gap_fill; + SurfaceCollection fill_surfaces; + PerimeterGenerator perimeter_generator( + &slices, + 1., // layer height + Flow(1., 1., 1.), + static_cast(&config), + static_cast(&config), + static_cast(&config), + false, // spiral_vase + // output: + &loops, &gap_fill, &fill_surfaces); + perimeter_generator.process(); + + THEN("expected number of collections") { + REQUIRE(loops.entities.size() == data.expolygons.size()); + } + + loops = loops.flatten(); + THEN("expected number of loops") { + REQUIRE(loops.entities.size() == data.total); + } + THEN("expected number of external loops") { + size_t num_external = std::count_if(loops.entities.begin(), loops.entities.end(), + [](const ExtrusionEntity *ee){ return ee->role() == erExternalPerimeter; }); + REQUIRE(num_external == data.external); + } + THEN("expected external order") { + std::vector ext_order; + for (auto *ee : loops.entities) + ext_order.emplace_back(ee->role() == erExternalPerimeter); + REQUIRE(ext_order == data.ext_order); + } + THEN("expected number of internal contour loops") { + size_t cinternal = std::count_if(loops.entities.begin(), loops.entities.end(), + [](const ExtrusionEntity *ee){ return dynamic_cast(ee)->loop_role() == elrContourInternalPerimeter; }); + REQUIRE(cinternal == data.cinternal); + } + THEN("expected number of ccw loops") { + size_t ccw = std::count_if(loops.entities.begin(), loops.entities.end(), + [](const ExtrusionEntity *ee){ return dynamic_cast(ee)->polygon().is_counter_clockwise(); }); + REQUIRE(ccw == data.ccw); + } + THEN("expected ccw/cw order") { + std::vector ccw_order; + for (auto *ee : loops.entities) + ccw_order.emplace_back(dynamic_cast(ee)->polygon().is_counter_clockwise()); + REQUIRE(ccw_order == data.ccw_order); + } + THEN("expected nesting order") { + for (const std::vector &nesting : data.nesting) { + for (size_t i = 1; i < nesting.size(); ++ i) + REQUIRE(dynamic_cast(loops.entities[nesting[i - 1]])->polygon().contains(loops.entities[nesting[i]]->first_point())); + } + } + }; + + WHEN("Rectangle") { + config.perimeters.value = 3; + TestData data; + data.expolygons = { + ExPolygon{ Polygon::new_scale({ {0,0}, {100,0}, {100,100}, {0,100} }) } + }; + data.total = 3; + data.external = 1; + data.ext_order = { false, false, true }; + data.cinternal = 1; + data.ccw = 3; + data.ccw_order = { true, true, true }; + data.nesting = { { 2, 1, 0 } }; + test(data); + } + WHEN("Rectangle with hole") { + config.perimeters.value = 3; + TestData data; + data.expolygons = { + ExPolygon{ Polygon::new_scale({ {0,0}, {100,0}, {100,100}, {0,100} }), + Polygon::new_scale({ {40,40}, {40,60}, {60,60}, {60,40} }) } + }; + data.total = 6; + data.external = 2; + data.ext_order = { false, false, true, false, false, true }; + data.cinternal = 1; + data.ccw = 3; + data.ccw_order = { false, false, false, true, true, true }; + data.nesting = { { 5, 4, 3, 0, 1, 2 } }; + test(data); + } + WHEN("Nested rectangles with holes") { + config.perimeters.value = 3; + TestData data; + data.expolygons = { + ExPolygon{ Polygon::new_scale({ {0,0}, {200,0}, {200,200}, {0,200} }), + Polygon::new_scale({ {20,20}, {20,180}, {180,180}, {180,20} }) }, + ExPolygon{ Polygon::new_scale({ {50,50}, {150,50}, {150,150}, {50,150} }), + Polygon::new_scale({ {80,80}, {80,120}, {120,120}, {120,80} }) } + }; + data.total = 4*3; + data.external = 4; + data.ext_order = { false, false, true, false, false, true, false, false, true, false, false, true }; + data.cinternal = 2; + data.ccw = 2*3; + data.ccw_order = { false, false, false, true, true, true, false, false, false, true, true, true }; + test(data); + } + WHEN("Rectangle with multiple holes") { + config.perimeters.value = 2; + TestData data; + ExPolygon expoly{ Polygon::new_scale({ {0,0}, {50,0}, {50,50}, {0,50} }) }; + expoly.holes.emplace_back(Polygon::new_scale({ {7.5,7.5}, {7.5,12.5}, {12.5,12.5}, {12.5,7.5} })); + expoly.holes.emplace_back(Polygon::new_scale({ {7.5,17.5}, {7.5,22.5}, {12.5,22.5}, {12.5,17.5} })); + expoly.holes.emplace_back(Polygon::new_scale({ {7.5,27.5}, {7.5,32.5}, {12.5,32.5}, {12.5,27.5} })); + expoly.holes.emplace_back(Polygon::new_scale({ {7.5,37.5}, {7.5,42.5}, {12.5,42.5}, {12.5,37.5} })); + expoly.holes.emplace_back(Polygon::new_scale({ {17.5,7.5}, {17.5,12.5}, {22.5,12.5}, {22.5,7.5} })); + data.expolygons = { expoly }; + data.total = 12; + data.external = 6; + data.ext_order = { false, true, false, true, false, true, false, true, false, true, false, true }; + data.cinternal = 1; + data.ccw = 2; + data.ccw_order = { false, false, false, false, false, false, false, false, false, false, true, true }; + data.nesting = { {0,1},{2,3},{4,5},{6,7},{8,9} }; + test(data); + }; +} + +SCENARIO("Perimeters", "[Perimeters]") +{ + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "fill_density", 0 }, + { "perimeters", 3 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" } + }); + + WHEN("Bridging perimeters disabled") { + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config); + + THEN("all perimeters extruded ccw") { + GCodeReader parser; + bool has_cw_loops = false; + Polygon current_loop; + parser.parse_buffer(gcode, [&has_cw_loops, ¤t_loop](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self) && line.dist_XY(self) > 0) { + if (current_loop.empty()) + current_loop.points.emplace_back(self.xy_scaled()); + current_loop.points.emplace_back(line.new_XY_scaled(self)); + } else if (! line.cmd_is("M73")) { + // skips remaining time lines (M73) + if (! current_loop.empty() && current_loop.is_clockwise()) + has_cw_loops = true; + current_loop.clear(); + } + }); + REQUIRE(! has_cw_loops); + } + } + + auto test = [&config](Test::TestMesh model) { + // we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong) + std::string gcode = Slic3r::Test::slice({ model, model }, config); + GCodeReader parser; + bool has_cw_loops = false; + bool has_outwards_move = false; + bool starts_on_convex_point = false; + // print_z => count of external loops + std::map external_loops; + Polygon current_loop; + const double external_perimeter_speed = config.get_abs_value("external_perimeter_speed") * 60.; + parser.parse_buffer(gcode, [&has_cw_loops, &has_outwards_move, &starts_on_convex_point, &external_loops, ¤t_loop, external_perimeter_speed, model] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self) && line.dist_XY(self) > 0) { + if (current_loop.empty()) + current_loop.points.emplace_back(self.xy_scaled()); + current_loop.points.emplace_back(line.new_XY_scaled(self)); + } else if (! line.cmd_is("M73")) { + // skips remaining time lines (M73) + if (! current_loop.empty()) { + if (current_loop.is_clockwise()) + has_cw_loops = true; + if (is_approx(self.f(), external_perimeter_speed)) { + // reset counter for second object + coord_t z = scaled(self.z()); + auto it = external_loops.find(z); + if (it == external_loops.end()) + it = external_loops.insert(std::make_pair(z, 0)).first; + else if (it->second == 2) + it->second = 0; + ++ it->second; + bool is_contour = it->second == 2; + bool is_hole = it->second == 1; + // Testing whether the move point after loop ends up inside the extruded loop. + bool loop_contains_point = current_loop.contains(line.new_XY_scaled(self)); + if (// contour should include destination + (! loop_contains_point && is_contour) || + // hole should not + (loop_contains_point && is_hole)) + has_outwards_move = true; + if (model == Test::TestMesh::cube_with_concave_hole) { + // check that loop starts at a concave vertex + double cross = cross2((current_loop.points.front() - current_loop.points[current_loop.points.size() - 2]).cast(), (current_loop.points[1] - current_loop.points.front()).cast()); + bool convex = cross > 0.; + if ((convex && is_contour) || (! convex && is_hole)) + starts_on_convex_point = true; + } + } + current_loop.clear(); + } + } + }); + THEN("all perimeters extruded ccw") { + REQUIRE(! has_cw_loops); + } + THEN("move inwards after completing external loop") { + REQUIRE(! has_outwards_move); + } + THEN("loops start on concave point if any") { + REQUIRE(! starts_on_convex_point); + } + + }; + // Reusing the config above. + config.set_deserialize_strict({ + { "external_perimeter_speed", 68 } + }); + GIVEN("Cube with hole") { test(Test::TestMesh::cube_with_hole); } + GIVEN("Cube with concave hole") { test(Test::TestMesh::cube_with_concave_hole); } + + WHEN("Bridging perimeters enabled") { + // Reusing the config above. + config.set_deserialize_strict({ + { "perimeters", 1 }, + { "perimeter_speed", 77 }, + { "external_perimeter_speed", 66 }, + { "bridge_speed", 99 }, + { "cooling", "1" }, + { "fan_below_layer_time", "0" }, + { "slowdown_below_layer_time", "0" }, + { "bridge_fan_speed", "100" }, + // arbitrary value + { "bridge_flow_ratio", 33 }, + { "overhangs", true } + }); + + std::string gcode = Slic3r::Test::slice({ mesh(Slic3r::Test::TestMesh::overhang) }, config); + + THEN("Bridging is applied to bridging perimeters") { + GCodeReader parser; + // print Z => speeds + std::map> layer_speeds; + int fan_speed = 0; + const double perimeter_speed = config.opt_float("perimeter_speed") * 60.; + const double external_perimeter_speed = config.get_abs_value("external_perimeter_speed") * 60.; + const double bridge_speed = config.opt_float("bridge_speed") * 60.; + const double nozzle_dmr = config.opt("nozzle_diameter")->get_at(0); + const double filament_dmr = config.opt("filament_diameter")->get_at(0); + const double bridge_mm_per_mm = sqr(nozzle_dmr / filament_dmr) * config.opt_float("bridge_flow_ratio"); + parser.parse_buffer(gcode, [&layer_speeds, &fan_speed, perimeter_speed, external_perimeter_speed, bridge_speed, nozzle_dmr, filament_dmr, bridge_mm_per_mm] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.cmd_is("M107")) + fan_speed = 0; + else if (line.cmd_is("M106")) + line.has_value('S', fan_speed); + else if (line.extruding(self) && line.dist_XY(self) > 0) { + double feedrate = line.new_F(self); + REQUIRE((is_approx(feedrate, perimeter_speed) || is_approx(feedrate, external_perimeter_speed) || is_approx(feedrate, bridge_speed))); + layer_speeds[self.z()].insert(feedrate); + bool bridging = is_approx(feedrate, bridge_speed); + double mm_per_mm = line.dist_E(self) / line.dist_XY(self); + // Fan enabled at full speed when bridging, disabled when not bridging. + REQUIRE((! bridging || fan_speed == 255)); + REQUIRE((bridging || fan_speed == 0)); + // When bridging, bridge flow is applied. + REQUIRE((! bridging || std::abs(mm_per_mm - bridge_mm_per_mm) <= 0.01)); + } + }); + // only overhang layer has more than one speed + size_t num_overhangs = std::count_if(layer_speeds.begin(), layer_speeds.end(), [](const std::pair> &v){ return v.second.size() > 1; }); + REQUIRE(num_overhangs == 1); + } + } + + GIVEN("iPad stand") { + WHEN("Extra perimeters enabled") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "perimeters", 3 }, + { "layer_height", 0.4 }, + { "first_layer_height", 0.35 }, + { "extra_perimeters", 1 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + { "perimeter_speed", 99 }, + { "external_perimeter_speed", 99 }, + { "small_perimeter_speed", 99 }, + { "thin_walls", 0 }, + }); + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::ipadstand }, config); + // z => number of loops + std::map perimeters; + bool in_loop = false; + const double perimeter_speed = config.opt_float("perimeter_speed") * 60.; + GCodeReader parser; + parser.parse_buffer(gcode, [&perimeters, &in_loop, perimeter_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self) && line.dist_XY(self) > 0 && is_approx(line.new_F(self), perimeter_speed)) { + if (! in_loop) { + coord_t z = scaled(self.z()); + auto it = perimeters.find(z); + if (it == perimeters.end()) + it = perimeters.insert(std::make_pair(z, 0)).first; + ++ it->second; + } + in_loop = true; + } else if (! line.cmd_is("M73")) { + // skips remaining time lines (M73) + in_loop = false; + } + }); + THEN("no superfluous extra perimeters") { + const int num_perimeters = config.opt_int("perimeters"); + size_t extra_perimeters = std::count_if(perimeters.begin(), perimeters.end(), [num_perimeters](const std::pair &v){ return (v.second % num_perimeters) > 0; }); + REQUIRE(extra_perimeters == 0); + } + } + } +} + +SCENARIO("Some weird coverage test", "[Perimeters]") +{ + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", "0.4" }, + { "perimeters", 2 }, + { "perimeter_extrusion_width", 0.4 }, + { "external_perimeter_extrusion_width", 0.4 }, + { "infill_extrusion_width", 0.53 }, + { "solid_infill_extrusion_width", 0.53 } + }); + + // we just need a pre-filled Print object + Print print; + Model model; + Slic3r::Test::init_print({ Test::TestMesh::cube_20x20x20 }, print, model, config); + + // override a layer's slices + ExPolygon expolygon; + expolygon.contour = { + {-71974463,-139999376},{-71731792,-139987456},{-71706544,-139985616},{-71682119,-139982639},{-71441248,-139946912},{-71417487,-139942895},{-71379384,-139933984},{-71141800,-139874480}, + {-71105247,-139862895},{-70873544,-139779984},{-70838592,-139765856},{-70614943,-139660064},{-70581783,-139643567},{-70368368,-139515680},{-70323751,-139487872},{-70122160,-139338352}, + {-70082399,-139306639},{-69894800,-139136624},{-69878679,-139121327},{-69707992,-138933008},{-69668575,-138887343},{-69518775,-138685359},{-69484336,-138631632},{-69356423,-138418207}, + {-69250040,-138193296},{-69220920,-138128976},{-69137992,-137897168},{-69126095,-137860255},{-69066568,-137622608},{-69057104,-137582511},{-69053079,-137558751},{-69017352,-137317872}, + {-69014392,-137293456},{-69012543,-137268207},{-68999369,-137000000},{-63999999,-137000000},{-63705947,-136985551},{-63654984,-136977984},{-63414731,-136942351},{-63364756,-136929840}, + {-63129151,-136870815},{-62851950,-136771631},{-62585807,-136645743},{-62377483,-136520895},{-62333291,-136494415},{-62291908,-136463728},{-62096819,-136319023},{-62058644,-136284432}, + {-61878676,-136121328},{-61680968,-135903184},{-61650275,-135861807},{-61505591,-135666719},{-61354239,-135414191},{-61332211,-135367615},{-61228359,-135148063},{-61129179,-134870847}, + {-61057639,-134585262},{-61014451,-134294047},{-61000000,-134000000},{-61000000,-107999999},{-61014451,-107705944},{-61057639,-107414736},{-61129179,-107129152},{-61228359,-106851953}, + {-61354239,-106585808},{-61505591,-106333288},{-61680967,-106096816},{-61878675,-105878680},{-62096820,-105680967},{-62138204,-105650279},{-62333292,-105505591},{-62585808,-105354239}, + {-62632384,-105332207},{-62851951,-105228360},{-62900463,-105211008},{-63129152,-105129183},{-63414731,-105057640},{-63705947,-105014448},{-63999999,-105000000},{-68999369,-105000000}, + {-69012543,-104731792},{-69014392,-104706544},{-69017352,-104682119},{-69053079,-104441248},{-69057104,-104417487},{-69066008,-104379383},{-69125528,-104141799},{-69137111,-104105248}, + {-69220007,-103873544},{-69234136,-103838591},{-69339920,-103614943},{-69356415,-103581784},{-69484328,-103368367},{-69512143,-103323752},{-69661647,-103122160},{-69693352,-103082399}, + {-69863383,-102894800},{-69878680,-102878679},{-70066999,-102707992},{-70112656,-102668576},{-70314648,-102518775},{-70368367,-102484336},{-70581783,-102356424},{-70806711,-102250040}, + {-70871040,-102220919},{-71102823,-102137992},{-71139752,-102126095},{-71377383,-102066568},{-71417487,-102057104},{-71441248,-102053079},{-71682119,-102017352},{-71706535,-102014392}, + {-71731784,-102012543},{-71974456,-102000624},{-71999999,-102000000},{-104000000,-102000000},{-104025536,-102000624},{-104268207,-102012543},{-104293455,-102014392}, + {-104317880,-102017352},{-104558751,-102053079},{-104582512,-102057104},{-104620616,-102066008},{-104858200,-102125528},{-104894751,-102137111},{-105126455,-102220007}, + {-105161408,-102234136},{-105385056,-102339920},{-105418215,-102356415},{-105631632,-102484328},{-105676247,-102512143},{-105877839,-102661647},{-105917600,-102693352}, + {-106105199,-102863383},{-106121320,-102878680},{-106292007,-103066999},{-106331424,-103112656},{-106481224,-103314648},{-106515663,-103368367},{-106643575,-103581783}, + {-106749959,-103806711},{-106779080,-103871040},{-106862007,-104102823},{-106873904,-104139752},{-106933431,-104377383},{-106942896,-104417487},{-106946920,-104441248}, + {-106982648,-104682119},{-106985607,-104706535},{-106987456,-104731784},{-107000630,-105000000},{-112000000,-105000000},{-112294056,-105014448},{-112585264,-105057640}, + {-112870848,-105129184},{-112919359,-105146535},{-113148048,-105228360},{-113194624,-105250392},{-113414191,-105354239},{-113666711,-105505591},{-113708095,-105536279}, + {-113903183,-105680967},{-114121320,-105878679},{-114319032,-106096816},{-114349720,-106138200},{-114494408,-106333288},{-114645760,-106585808},{-114667792,-106632384}, + {-114771640,-106851952},{-114788991,-106900463},{-114870815,-107129151},{-114942359,-107414735},{-114985551,-107705943},{-115000000,-107999999},{-115000000,-134000000}, + {-114985551,-134294048},{-114942359,-134585263},{-114870816,-134870847},{-114853464,-134919359},{-114771639,-135148064},{-114645759,-135414192},{-114494407,-135666720}, + {-114319031,-135903184},{-114121320,-136121327},{-114083144,-136155919},{-113903184,-136319023},{-113861799,-136349712},{-113666711,-136494416},{-113458383,-136619264}, + {-113414192,-136645743},{-113148049,-136771631},{-112870848,-136870815},{-112820872,-136883327},{-112585264,-136942351},{-112534303,-136949920},{-112294056,-136985551}, + {-112000000,-137000000},{-107000630,-137000000},{-106987456,-137268207},{-106985608,-137293440},{-106982647,-137317872},{-106946920,-137558751},{-106942896,-137582511}, + {-106933991,-137620624},{-106874471,-137858208},{-106862888,-137894751},{-106779992,-138126463},{-106765863,-138161424},{-106660080,-138385055},{-106643584,-138418223}, + {-106515671,-138631648},{-106487855,-138676256},{-106338352,-138877839},{-106306647,-138917600},{-106136616,-139105199},{-106121320,-139121328},{-105933000,-139291999}, + {-105887344,-139331407},{-105685351,-139481232},{-105631632,-139515663},{-105418216,-139643567},{-105193288,-139749951},{-105128959,-139779072},{-104897175,-139862016}, + {-104860247,-139873904},{-104622616,-139933423},{-104582511,-139942896},{-104558751,-139946912},{-104317880,-139982656},{-104293463,-139985616},{-104268216,-139987456}, + {-104025544,-139999376},{-104000000,-140000000},{-71999999,-140000000} + }; + expolygon.holes = { + {{-105000000,-138000000},{-105000000,-104000000},{-71000000,-104000000},{-71000000,-138000000}}, + {{-69000000,-132000000},{-69000000,-110000000},{-64991180,-110000000},{-64991180,-132000000}}, + {{-111008824,-132000000},{-111008824,-110000000},{-107000000,-110000000},{-107000000,-132000000}} + }; + PrintObject *object = print.get_object(0); + object->slice(); + Layer *layer = object->get_layer(1); + LayerRegion *layerm = layer->get_region(0); + layerm->slices.clear(); + layerm->slices.append({ expolygon }, stInternal); + + // make perimeters + layer->make_perimeters(); + + // compute the covered area + Flow pflow = layerm->flow(frPerimeter); + Flow iflow = layerm->flow(frInfill); + Polygons covered_by_perimeters; + Polygons covered_by_infill; + { + Polygons acc; + for (const ExtrusionEntity *ee : layerm->perimeters.entities) + for (const ExtrusionEntity *ee : dynamic_cast(ee)->entities) + append(acc, offset(dynamic_cast(ee)->polygon().split_at_first_point(), float(pflow.scaled_width() / 2.f + SCALED_EPSILON))); + covered_by_perimeters = union_(acc); + } + { + Polygons acc; + for (const Surface &surface : layerm->fill_surfaces.surfaces) + append(acc, to_polygons(surface.expolygon)); + for (const ExtrusionEntity *ee : layerm->thin_fills.entities) + append(acc, offset(dynamic_cast(ee)->polyline, float(iflow.scaled_width() / 2.f + SCALED_EPSILON))); + covered_by_infill = union_(acc); + } + + // compute the non covered area + ExPolygons non_covered = diff_ex(to_polygons(layerm->slices.surfaces), union_(covered_by_perimeters, covered_by_infill)); + + /* + if (0) { + printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered); + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "gaps.svg", + expolygons => [ map $_->expolygon, @{$layerm->slices} ], + red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]), + green_expolygons => union_ex($non_covered), + no_arrows => 1, + polylines => [ + map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters}, + ], + ); + } + */ + THEN("no gap between perimeters and infill") { + size_t num_non_convered = std::count_if(non_covered.begin(), non_covered.end(), [&iflow](const ExPolygon &ex){ return ex.area() > sqr(double(iflow.scaled_width())); }); + REQUIRE(num_non_convered == 0); + } +} + +SCENARIO("Perimeters3", "[Perimeters]") +{ + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "perimeters", 3 }, + { "layer_height", 0.4 }, + { "bridge_speed", 99 }, + // to prevent bridging over sparse infill + { "fill_density", 0 }, + { "overhangs", true }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" } + }); + + auto test = [&config](const Vec3d &scale) { + std::string gcode = Slic3r::Test::slice({ mesh(Slic3r::Test::TestMesh::V, Vec3d::Zero(), scale) }, config); + GCodeReader parser; + std::set z_with_bridges; + const double bridge_speed = config.opt_float("bridge_speed") * 60.; + parser.parse_buffer(gcode, [&z_with_bridges, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self) && line.dist_XY(self) > 0 && is_approx(line.new_F(self), bridge_speed)) + z_with_bridges.insert(scaled(self.z())); + }); + return z_with_bridges.size(); + }; + + GIVEN("V shape, unscaled") { + int n = test(Vec3d(1., 1., 1.)); + // except for the two internal solid layers above void + THEN("no overhangs printed with bridge speed") { + REQUIRE(n == 1); + } + } + GIVEN("V shape, scaled 3x in X") { + int n = test(Vec3d(3., 1., 1.)); + // except for the two internal solid layers above void + THEN("overhangs printed with bridge speed") { + REQUIRE(n > 2); + } + } +} + +SCENARIO("Perimeters4", "[Perimeters]") +{ + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "seam_position", "random" } + }); + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("successful generation of G-code with seam_position = random") { + REQUIRE(! gcode.empty()); + } +} + +SCENARIO("Seam alignment", "[Perimeters]") +{ + auto test = [](Test::TestMesh model) { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "seam_position", "aligned" }, + { "skirts", 0 }, + { "perimeters", 1 }, + { "fill_density", 0 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 }, + { "retract_layer_change", "0" } + }); + std::string gcode = Slic3r::Test::slice({ model }, config); + bool was_extruding = false; + Points seam_points; + GCodeReader parser; + parser.parse_buffer(gcode, [&was_extruding, &seam_points](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self)) { + if (! was_extruding) + seam_points.emplace_back(self.xy_scaled()); + was_extruding = true; + } else if (! line.cmd_is("M73")) { + // skips remaining time lines (M73) + was_extruding = false; + } + }); + THEN("seam is aligned") { + size_t num_not_aligned = 0; + for (size_t i = 1; i < seam_points.size(); ++ i) { + double d = (seam_points[i] - seam_points[i - 1]).cast().norm(); + // Seams shall be aligned up to 3mm. + if (d > scaled(3.)) + ++ num_not_aligned; + } + REQUIRE(num_not_aligned == 0); + } + }; + + GIVEN("20mm cube") { + test(Slic3r::Test::TestMesh::cube_20x20x20); + } + GIVEN("small_dorito") { + test(Slic3r::Test::TestMesh::small_dorito); + } +} diff --git a/tests/fff_print/test_support_material.cpp b/tests/fff_print/test_support_material.cpp index 442db7654f..c5087263c3 100644 --- a/tests/fff_print/test_support_material.cpp +++ b/tests/fff_print/test_support_material.cpp @@ -236,3 +236,262 @@ SCENARIO("SupportMaterial: Checking bridge speed", "[SupportMaterial]") } #endif + + +/* + +Old Perl tests, which were disabled by Vojtech at the time of first Support Generator refactoring. + +#if 0 +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('support_material', 1); + my @contact_z = my @top_z = (); + + my $test = sub { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $object_config = $print->print->objects->[0]->config; + my $flow = Slic3r::Flow->new_from_width( + width => $object_config->support_material_extrusion_width || $object_config->extrusion_width, + role => FLOW_ROLE_SUPPORT_MATERIAL, + nozzle_diameter => $print->config->nozzle_diameter->[$object_config->support_material_extruder-1] // $print->config->nozzle_diameter->[0], + layer_height => $object_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($print->print->objects->[0], \@contact_z, \@top_z, $config->layer_height); + my $expected_top_spacing = $support->contact_distance($config->layer_height, $config->nozzle_diameter->[0]); + + is $support_z->[0], $config->first_layer_height, + 'first layer height is honored'; + is scalar(grep { $support_z->[$_]-$support_z->[$_-1] <= 0 } 1..$#$support_z), 0, + 'no null or negative support layers'; + is scalar(grep { $support_z->[$_]-$support_z->[$_-1] > $config->nozzle_diameter->[0] + epsilon } 1..$#$support_z), 0, + 'no layers thicker than nozzle diameter'; + + my $wrong_top_spacing = 0; + foreach my $top_z (@top_z) { + # 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 (or the next one) is spaced with nozzle diameter + $wrong_top_spacing = 1 + 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'; + }; + + $config->set('layer_height', 0.2); + $config->set('first_layer_height', 0.3); + @contact_z = (1.9); + @top_z = (1.1); + $test->(); + + $config->set('first_layer_height', 0.4); + $test->(); + + $config->set('layer_height', $config->nozzle_diameter->[0]); + $test->(); +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('raft_layers', 3); + $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', 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'; + + my $tool = 0; + Slic3r::GCode::Reader->new->parse($gcode, sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($info->{extruding}) { + if ($self->Z <= ($config->raft_layers * $config->layer_height)) { + fail 'not extruding raft with support material extruder' + if $tool != ($config->support_material_extruder-1); + } else { + fail 'support material exceeds raft layers' + if $tool == $config->support_material_extruder-1; + # TODO: we should test that full support is generated when we use raft too + } + } + }); +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('skirts', 0); + $config->set('raft_layers', 3); + $config->set('support_material_pattern', 'honeycomb'); + $config->set('support_material_extrusion_width', 0.6); + $config->set('first_layer_extrusion_width', '100%'); + $config->set('bridge_speed', 99); + $config->set('cooling', [ 0 ]); # prevent speed alteration + $config->set('first_layer_speed', '100%'); # prevent speed alteration + $config->set('start_gcode', ''); # prevent any unexpected Z move + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + + my $layer_id = -1; # so that first Z move sets this to 0 + my @raft = my @first_object_layer = (); + my %first_object_layer_speeds = (); # F => 1 + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($info->{extruding} && $info->{dist_XY} > 0) { + if ($layer_id <= $config->raft_layers) { + # this is a raft layer or the first object layer + my $line = Slic3r::Line->new_scale([ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ]); + my @path = @{$line->grow(scale($config->support_material_extrusion_width/2))}; + if ($layer_id < $config->raft_layers) { + # this is a raft layer + push @raft, @path; + } else { + push @first_object_layer, @path; + $first_object_layer_speeds{ $args->{F} // $self->F } = 1; + } + } + } elsif ($cmd eq 'G1' && $info->{dist_Z} > 0) { + $layer_id++; + } + }); + + ok !@{diff(\@first_object_layer, \@raft)}, + 'first object layer is completely supported by raft'; + is scalar(keys %first_object_layer_speeds), 1, + 'only one speed used in first object layer'; + ok +(keys %first_object_layer_speeds)[0] == $config->bridge_speed*60, + 'bridge speed used in first object layer'; +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('skirts', 0); + $config->set('layer_height', 0.35); + $config->set('first_layer_height', 0.3); + $config->set('nozzle_diameter', [0.5]); + $config->set('support_material_extruder', 2); + $config->set('support_material_interface_extruder', 2); + + my $test = sub { + my ($raft_layers) = @_; + $config->set('raft_layers', $raft_layers); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my %raft_z = (); # z => 1 + my $tool = undef; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($info->{extruding} && $info->{dist_XY} > 0) { + if ($tool == $config->support_material_extruder-1) { + $raft_z{$self->Z} = 1; + } + } + }); + + is scalar(keys %raft_z), $config->raft_layers, 'correct number of raft layers is generated'; + }; + + $test->(2); + $test->(70); + + $config->set('layer_height', 0.4); + $config->set('first_layer_height', 0.35); + $test->(3); + $test->(70); +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('brim_width', 0); + $config->set('skirts', 0); + $config->set('support_material', 1); + $config->set('top_solid_layers', 0); # so that we don't have the internal bridge over infill + $config->set('bridge_speed', 99); + $config->set('cooling', [ 0 ]); + $config->set('first_layer_speed', '100%'); + + my $test = sub { + my $print = Slic3r::Test::init_print('overhang', config => $config); + + my $has_bridge_speed = 0; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($info->{extruding}) { + if (($args->{F} // $self->F) == $config->bridge_speed*60) { + $has_bridge_speed = 1; + } + } + }); + return $has_bridge_speed; + }; + + $config->set('support_material_contact_distance', 0.2); + ok $test->(), 'bridge speed is used when support_material_contact_distance > 0'; + + $config->set('support_material_contact_distance', 0); + ok !$test->(), 'bridge speed is not used when support_material_contact_distance == 0'; + + $config->set('raft_layers', 5); + $config->set('support_material_contact_distance', 0.2); + ok $test->(), 'bridge speed is used when raft_layers > 0 and support_material_contact_distance > 0'; + + $config->set('support_material_contact_distance', 0); + ok !$test->(), 'bridge speed is not used when raft_layers > 0 and support_material_contact_distance == 0'; +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('skirts', 0); + $config->set('start_gcode', ''); + $config->set('raft_layers', 8); + $config->set('nozzle_diameter', [0.4, 1]); + $config->set('layer_height', 0.1); + $config->set('first_layer_height', 0.8); + $config->set('support_material_extruder', 2); + $config->set('support_material_interface_extruder', 2); + $config->set('support_material_contact_distance', 0); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + ok my $gcode = Slic3r::Test::gcode($print), 'first_layer_height is validated with support material extruder nozzle diameter when using raft layers'; + + my $tool = undef; + my @z = (0); + my %layer_heights_by_tool = (); # tool => [ lh, lh... ] + Slic3r::GCode::Reader->new->parse($gcode, sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($cmd eq 'G1' && exists $args->{Z} && $args->{Z} != $self->Z) { + push @z, $args->{Z}; + } elsif ($info->{extruding} && $info->{dist_XY} > 0) { + $layer_heights_by_tool{$tool} ||= []; + push @{ $layer_heights_by_tool{$tool} }, $z[-1] - $z[-2]; + } + }); + + ok !defined(first { $_ > $config->nozzle_diameter->[0] + epsilon } + @{ $layer_heights_by_tool{$config->perimeter_extruder-1} }), + 'no object layer is thicker than nozzle diameter'; + + ok !defined(first { abs($_ - $config->layer_height) < epsilon } + @{ $layer_heights_by_tool{$config->support_material_extruder-1} }), + 'no support material layer is as thin as object layers'; +} + +*/ diff --git a/tests/fff_print/test_thin_walls.cpp b/tests/fff_print/test_thin_walls.cpp new file mode 100644 index 0000000000..cd80f3bd66 --- /dev/null +++ b/tests/fff_print/test_thin_walls.cpp @@ -0,0 +1,191 @@ +#include + +#include +#include + +#include "test_data.hpp" // get access to init_print, etc + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/libslic3r.h" + +using namespace Slic3r; + +SCENARIO("Medial Axis", "[ThinWalls]") { + GIVEN("Square with hole") { + auto square = Polygon::new_scale({ {100, 100}, {200, 100}, {200, 200}, {100, 200} }); + auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} }); + ExPolygon expolygon{ square, hole_in_square }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(40.), scaled(0.5)); + THEN("medial axis of a square shape is a single path") { + REQUIRE(res.size() == 1); + } + THEN("polyline forms a closed loop") { + REQUIRE(res.front().first_point() == res.front().last_point()); + } + THEN("medial axis loop has reasonable length") { + REQUIRE(res.front().length() > hole_in_square.length()); + REQUIRE(res.front().length() < square.length()); + } + } + } + GIVEN("narrow rectangle") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of a narrow rectangle is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } +#if 0 + //FIXME this test never worked + GIVEN("narrow rectangle with an extra vertex") { + ExPolygon expolygon{ Polygon::new_scale({ + {100, 100}, {120, 100}, {120, 200}, + {105, 200} /* extra point in the short side*/, + {100, 200} + })}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(1.), scaled(0.5)); + THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has still a reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + THEN("extra vertices don't influence medial axis") { + size_t invalid = 0; + for (const Polyline &pl : res) + for (const Point &p : pl.points) + if (std::abs(p.y() - scaled(150.)) < SCALED_EPSILON) + ++ invalid; + REQUIRE(invalid == 0); + } + } + } +#endif + GIVEN("semicircumference") { + ExPolygon expolygon{{ + {1185881,829367},{1421988,1578184},{1722442,2303558},{2084981,2999998},{2506843,3662186},{2984809,4285086},{3515250,4863959},{4094122,5394400}, + {4717018,5872368},{5379210,6294226},{6075653,6656769},{6801033,6957229},{7549842,7193328},{8316383,7363266},{9094809,7465751},{9879211,7500000}, + {10663611,7465750},{11442038,7363265},{12208580,7193327},{12957389,6957228},{13682769,6656768},{14379209,6294227},{15041405,5872366}, + {15664297,5394401},{16243171,4863960},{16758641,4301424},{17251579,3662185},{17673439,3000000},{18035980,2303556},{18336441,1578177}, + {18572539,829368},{18750748,0},{19758422,0},{19727293,236479},{19538467,1088188},{19276136,1920196},{18942292,2726179},{18539460,3499999}, + {18070731,4235755},{17539650,4927877},{16950279,5571067},{16307090,6160437},{15614974,6691519},{14879209,7160248},{14105392,7563079}, + {13299407,7896927},{12467399,8159255},{11615691,8348082},{10750769,8461952},{9879211,8500000},{9007652,8461952},{8142729,8348082}, + {7291022,8159255},{6459015,7896927},{5653029,7563079},{4879210,7160247},{4143447,6691519},{3451331,6160437},{2808141,5571066},{2218773,4927878}, + {1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0} + }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(1.324888), scaled(0.25)); + THEN("medial axis of a semicircumference is a single line") { + REQUIRE(res.size() == 1); + } + THEN("all medial axis segments of a semicircumference have the same orientation") { + int nccw = 0; + int ncw = 0; + for (const Polyline &pl : res) + for (size_t i = 1; i + 1 < pl.size(); ++ i) { + double cross = cross2((pl.points[i] - pl.points[i - 1]).cast(), (pl.points[i + 1] - pl.points[i]).cast()); + if (cross > 0.) + ++ nccw; + else if (cross < 0.) + ++ ncw; + } + REQUIRE((ncw == 0 || nccw == 0)); + } + } + } + GIVEN("narrow trapezoid") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of a narrow trapezoid is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } + GIVEN("L shape") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of an L shape is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + // 20 is the thickness of the expolygon, which is subtracted from the ends + auto len = unscale(res.front().length()) + 20; + REQUIRE(len > 80. * 2.); + REQUIRE(len < 100. * 2.); + } + } + } + GIVEN("whatever shape") { + ExPolygon expolygon{{ + {-203064906,-51459966},{-219312231,-51459966},{-219335477,-51459962},{-219376095,-51459962},{-219412047,-51459966}, + {-219572094,-51459966},{-219624814,-51459962},{-219642183,-51459962},{-219656665,-51459966},{-220815482,-51459966}, + {-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024}, + }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(819998., 102499.75); + THEN("medial axis is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + double perimeter = expolygon.contour.split_at_first_point().length(); + REQUIRE(total_length(res) > perimeter / 2. / 4. * 3.); + } + } + } + GIVEN("narrow triangle") { + ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(4.), scaled(0.5)); + THEN("medial axis of a narrow triangle is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } + GIVEN("GH #2474") { + ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(1871238, 500000); + THEN("medial axis is a single line") { + REQUIRE(res.size() == 1); + } + Polyline &polyline = res.front(); + THEN("medial axis is horizontal and is centered") { + double expected_y = expolygon.contour.bounding_box().center().y(); + double center_y = 0.; + for (auto &p : polyline.points) + center_y += double(p.y()); + REQUIRE(std::abs(center_y / polyline.size() - expected_y) < SCALED_EPSILON); + } + // order polyline from left to right + if (polyline.first_point().x() > polyline.last_point().x()) + polyline.reverse(); + BoundingBox polyline_bb = polyline.bounding_box(); + THEN("expected x_min") { + REQUIRE(polyline.first_point().x() == polyline_bb.min.x()); + } + THEN("expected x_max") { + REQUIRE(polyline.last_point().x() == polyline_bb.max.x()); + } + THEN("medial axis is monotonous in x (not self intersecting)") { + Polyline sorted { polyline }; + std::sort(sorted.begin(), sorted.end()); + REQUIRE(polyline == sorted); + } + } + } +} diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 0e206cc2f4..c68ba897cc 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests test_geometry.cpp test_placeholder_parser.cpp test_polygon.cpp + test_polyline.cpp test_mutable_polygon.cpp test_mutable_priority_queue.cpp test_stl.cpp diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 1b2b45eca7..048ebba107 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -188,6 +188,46 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { REQUIRE(intersection.front().area() == Approx(match.area())); } } + + ExPolygons expolygons { ExPolygon { square, hole_in_square } }; + WHEN("Clipping line 1") { + Polylines intersection = intersection_pl({ Polyline { { 15, 18 }, { 15, 15 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(15, 18) - Vec2f(15, 16)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 2") { + Polylines intersection = intersection_pl({ Polyline { { 15, 15 }, { 15, 12 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(15, 14) - Vec2f(15, 12)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 3") { + Polylines intersection = intersection_pl({ Polyline { { 12, 18 }, { 18, 18 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(18, 18) - Vec2f(12, 18)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 4") { + Polylines intersection = intersection_pl({ Polyline { { 5, 15 }, { 30, 15 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(14, 15) - Vec2f(10, 15)).norm() == Approx(intersection.front().length())); + REQUIRE((Vec2f(20, 15) - Vec2f(16, 15)).norm() == Approx(intersection[1].length())); + } + } + WHEN("Clipping line 5") { + Polylines intersection = intersection_pl({ Polyline { { 30, 15 }, { 5, 15 } } }, expolygons); + THEN("reverse line is clipped to square with hole") { + REQUIRE((Vec2f(20, 15) - Vec2f(16, 15)).norm() == Approx(intersection.front().length())); + REQUIRE((Vec2f(14, 15) - Vec2f(10, 15)).norm() == Approx(intersection[1].length())); + } + } + WHEN("Clipping line 6") { + Polylines intersection = intersection_pl({ Polyline { { 10, 18 }, { 20, 18 } } }, expolygons); + THEN("tangent line is clipped to square with hole") { + REQUIRE((Vec2f(20, 18) - Vec2f(10, 18)).norm() == Approx(intersection.front().length())); + } + } } GIVEN("square with hole 2") { // CCW oriented contour @@ -223,6 +263,49 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { } } } + GIVEN("circle") { + Slic3r::ExPolygon circle_with_hole { Polygon::new_scale({ + { 151.8639,288.1192 }, {133.2778,284.6011}, { 115.0091,279.6997 }, { 98.2859,270.8606 }, { 82.2734,260.7933 }, + { 68.8974,247.4181 }, { 56.5622,233.0777 }, { 47.7228,216.3558 }, { 40.1617,199.0172 }, { 36.6431,180.4328 }, + { 34.932,165.2312 }, { 37.5567,165.1101 }, { 41.0547,142.9903 }, { 36.9056,141.4295 }, { 40.199,124.1277 }, + { 47.7776,106.7972 }, { 56.6335,90.084 }, { 68.9831,75.7557 }, { 82.3712,62.3948 }, { 98.395,52.3429 }, + { 115.1281,43.5199 }, { 133.4004,38.6374 }, { 151.9884,35.1378 }, { 170.8905,35.8571 }, { 189.6847,37.991 }, + { 207.5349,44.2488 }, { 224.8662,51.8273 }, { 240.0786,63.067 }, { 254.407,75.4169 }, { 265.6311,90.6406 }, + { 275.6832,106.6636 }, { 281.9225,124.52 }, { 286.8064,142.795 }, { 287.5061,161.696 }, { 286.7874,180.5972 }, + { 281.8856,198.8664 }, { 275.6283,216.7169 }, { 265.5604,232.7294 }, { 254.3211,247.942 }, { 239.9802,260.2776 }, + { 224.757,271.5022 }, { 207.4179,279.0635 }, { 189.5605,285.3035 }, { 170.7649,287.4188 } + }) }; + circle_with_hole.holes = { Polygon::new_scale({ + { 158.227,215.9007 }, { 164.5136,215.9007 }, { 175.15,214.5007 }, { 184.5576,210.6044 }, { 190.2268,207.8743 }, + { 199.1462,201.0306 }, { 209.0146,188.346 }, { 213.5135,177.4829 }, { 214.6979,168.4866 }, { 216.1025,162.3325 }, + { 214.6463,151.2703 }, { 213.2471,145.1399 }, { 209.0146,134.9203 }, { 199.1462,122.2357 }, { 189.8944,115.1366 }, + { 181.2504,111.5567 }, { 175.5684,108.8205 }, { 164.5136,107.3655 }, { 158.2269,107.3655 }, { 147.5907,108.7656 }, + { 138.183,112.6616 }, { 132.5135,115.3919 }, { 123.5943,122.2357 }, { 113.7259,134.92 }, { 109.2269,145.7834 }, + { 108.0426,154.7799 }, { 106.638,160.9339 }, { 108.0941,171.9957 }, { 109.4933,178.1264 }, { 113.7259,188.3463 }, + { 123.5943,201.0306 }, { 132.8461,208.1296 }, { 141.4901,211.7094 }, { 147.172,214.4458 } + }) }; + THEN("contour is counter-clockwise") { + REQUIRE(circle_with_hole.contour.is_counter_clockwise()); + } + THEN("hole is counter-clockwise") { + REQUIRE(circle_with_hole.holes.size() == 1); + REQUIRE(circle_with_hole.holes.front().is_clockwise()); + } + + WHEN("clipping a line") { + auto line = Polyline::new_scale({ { 152.742,288.086671142818 }, { 152.742,34.166466971035 } }); + Polylines intersection = intersection_pl(line, Polygons{ circle_with_hole }); + THEN("clipped to two pieces") { + REQUIRE(intersection.front().length() == Approx((Vec2d(152742000, 215178843) - Vec2d(152742000, 288086661)).norm())); + REQUIRE(intersection[1].length() == Approx((Vec2d(152742000, 35166477) - Vec2d(152742000, 108087507)).norm())); + } + } + } + GIVEN("line") { + THEN("expand by 5") { + REQUIRE(offset(Polyline({10,10}, {20,10}), 5).front().area() == Polygon({ {10,5}, {20,5}, {20,15}, {10,15} }).area()); + } + } } template diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 025459d689..50e61b3b04 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -1,5 +1,6 @@ #include +#include "libslic3r/Config.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/LocalesUtils.hpp" @@ -13,20 +14,20 @@ using namespace Slic3r; SCENARIO("Generic config validation performs as expected.", "[Config]") { GIVEN("A config generated from default options") { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - WHEN( "perimeter_extrusion_width is set to 250%, a valid value") { + WHEN("perimeter_extrusion_width is set to 250%, a valid value") { config.set_deserialize_strict("perimeter_extrusion_width", "250%"); THEN( "The config is read as valid.") { REQUIRE(config.validate().empty()); } } - WHEN( "perimeter_extrusion_width is set to -10, an invalid value") { + WHEN("perimeter_extrusion_width is set to -10, an invalid value") { config.set("perimeter_extrusion_width", -10); THEN( "Validate returns error") { REQUIRE(! config.validate().empty()); } } - WHEN( "perimeters is set to -10, an invalid value") { + WHEN("perimeters is set to -10, an invalid value") { config.set("perimeters", -10); THEN( "Validate returns error") { REQUIRE(! config.validate().empty()); @@ -36,8 +37,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") { } SCENARIO("Config accessor functions perform as expected.", "[Config]") { - GIVEN("A config generated from default options") { - Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + auto test = [](ConfigBase &config) { WHEN("A boolean option is set to a boolean value") { REQUIRE_NOTHROW(config.set("gcode_comments", true)); THEN("The underlying value is set correctly.") { @@ -70,8 +70,8 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } #if 0 - //FIXME better design accessors for vector elements. - WHEN("An integer-based option is set through the integer interface") { + //FIXME better design accessors for vector elements. + WHEN("An integer-based option is set through the integer interface") { config.set("bed_temperature", 100); THEN("The underlying value is set correctly.") { REQUIRE(config.opt("bed_temperature")->get_at(0) == 100); @@ -193,6 +193,14 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { REQUIRE(config.opt_float("layer_height") == 0.5); } } + }; + GIVEN("DynamicPrintConfig generated from default options") { + auto config = Slic3r::DynamicPrintConfig::full_print_config(); + test(config); + } + GIVEN("FullPrintConfig generated from default options") { + Slic3r::FullPrintConfig config; + test(config); } } diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 34e625e6db..41ef69aaaf 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -162,14 +162,34 @@ TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){ } -TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){ - BoundingBox bb(std::vector({Point(0, 1), Point(10, 2), Point(20, 2)})); - bb.scale(2); - REQUIRE(bb.min == Point(0,2)); - REQUIRE(bb.max == Point(40,4)); +SCENARIO("BoundingBox", "[Geometry]") { + WHEN("Bounding boxes are scaled") { + BoundingBox bb(std::vector({Point(0, 1), Point(10, 2), Point(20, 2)})); + bb.scale(2); + REQUIRE(bb.min == Point(0,2)); + REQUIRE(bb.max == Point(40,4)); + } + WHEN("BoundingBox constructed from points") { + BoundingBox bb(Points{ {100,200}, {100, 200}, {500, -600} }); + THEN("minimum is correct") { + REQUIRE(bb.min == Point{100,-600}); + } + THEN("maximum is correct") { + REQUIRE(bb.max == Point{500,200}); + } + } + WHEN("BoundingBox constructed from a single point") { + BoundingBox bb; + bb.merge({10, 10}); + THEN("minimum equals to the only defined point") { + REQUIRE(bb.min == Point{10,10}); + } + THEN("maximum equals to the only defined point") { + REQUIRE(bb.max == Point{10,10}); + } + } } - TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){ Slic3r::Polyline tmp = { Point(10,10), Point(20,10) }; Slic3r::Polygon area = offset(tmp,5).at(0); diff --git a/tests/libslic3r/test_polygon.cpp b/tests/libslic3r/test_polygon.cpp index f2c78cacec..7774b44a6a 100644 --- a/tests/libslic3r/test_polygon.cpp +++ b/tests/libslic3r/test_polygon.cpp @@ -148,3 +148,65 @@ SCENARIO("Remove collinear points from Polygon", "[Polygon]") { } } } + +SCENARIO("Simplify polygon", "[Polygon]") +{ + GIVEN("gear") { + auto gear = Polygon::new_scale({ + {144.9694,317.1543}, {145.4181,301.5633}, {146.3466,296.921}, {131.8436,294.1643}, {131.7467,294.1464}, + {121.7238,291.5082}, {117.1631,290.2776}, {107.9198,308.2068}, {100.1735,304.5101}, {104.9896,290.3672}, + {106.6511,286.2133}, {93.453,279.2327}, {81.0065,271.4171}, {67.7886,286.5055}, {60.7927,280.1127}, + {69.3928,268.2566}, {72.7271,264.9224}, {61.8152,253.9959}, {52.2273,242.8494}, {47.5799,245.7224}, + {34.6577,252.6559}, {30.3369,245.2236}, {42.1712,236.3251}, {46.1122,233.9605}, {43.2099,228.4876}, + {35.0862,211.5672}, {33.1441,207.0856}, {13.3923,212.1895}, {10.6572,203.3273}, {6.0707,204.8561}, + {7.2775,204.4259}, {29.6713,196.3631}, {25.9815,172.1277}, {25.4589,167.2745}, {19.8337,167.0129}, + {5.0625,166.3346}, {5.0625,156.9425}, {5.3701,156.9282}, {21.8636,156.1628}, {25.3713,156.4613}, + {25.4243,155.9976}, {29.3432,155.8157}, {30.3838,149.3549}, {26.3596,147.8137}, {27.1085,141.2604}, + {29.8466,126.8337}, {24.5841,124.9201}, {10.6664,119.8989}, {13.4454,110.9264}, {33.1886,116.0691}, + {38.817,103.1819}, {45.8311,89.8133}, {30.4286,76.81}, {35.7686,70.0812}, {48.0879,77.6873}, + {51.564,81.1635}, {61.9006,69.1791}, {72.3019,58.7916}, {60.5509,42.5416}, {68.3369,37.1532}, + {77.9524,48.1338}, {80.405,52.2215}, {92.5632,44.5992}, {93.0123,44.3223}, {106.3561,37.2056}, + {100.8631,17.4679}, {108.759,14.3778}, {107.3148,11.1283}, {117.0002,32.8627}, {140.9109,27.3974}, + {145.7004,26.4994}, {145.1346,6.1011}, {154.502,5.4063}, {156.9398,25.6501}, {171.0557,26.2017}, + {181.3139,27.323}, {186.2377,27.8532}, {191.6031,8.5474}, {200.6724,11.2756}, {197.2362,30.2334}, + {220.0789,39.1906}, {224.3261,41.031}, {236.3506,24.4291}, {243.6897,28.6723}, {234.2956,46.7747}, + {245.6562,55.1643}, {257.2523,65.0901}, {261.4374,61.5679}, {273.1709,52.8031}, {278.555,59.5164}, + {268.4334,69.8001}, {264.1615,72.3633}, {268.2763,77.9442}, {278.8488,93.5305}, {281.4596,97.6332}, + {286.4487,95.5191}, {300.2821,90.5903}, {303.4456,98.5849}, {286.4523,107.7253}, {293.7063,131.1779}, + {294.9748,135.8787}, {314.918,133.8172}, {315.6941,143.2589}, {300.9234,146.1746}, {296.6419,147.0309}, + {297.1839,161.7052}, {296.6136,176.3942}, {302.1147,177.4857}, {316.603,180.3608}, {317.1658,176.7341}, + {315.215,189.6589}, {315.1749,189.6548}, {294.9411,187.5222}, {291.13,201.7233}, {286.2615,215.5916}, + {291.1944,218.2545}, {303.9158,225.1271}, {299.2384,233.3694}, {285.7165,227.6001}, {281.7091,225.1956}, + {273.8981,237.6457}, {268.3486,245.2248}, {267.4538,246.4414}, {264.8496,250.0221}, {268.6392,253.896}, + {278.5017,265.2131}, {272.721,271.4403}, {257.2776,258.3579}, {234.4345,276.5687}, {242.6222,294.8315}, + {234.9061,298.5798}, {227.0321,286.2841}, {225.2505,281.8301}, {211.5387,287.8187}, {202.3025,291.0935}, + {197.307,292.831}, {199.808,313.1906}, {191.5298,315.0787}, {187.3082,299.8172}, {186.4201,295.3766}, + {180.595,296.0487}, {161.7854,297.4248}, {156.8058,297.6214}, {154.3395,317.8592} + }); + + WHEN("simplified") { + size_t num_points = gear.size(); + Polygons simplified = gear.simplify(1000.); + THEN("gear simplified to a single polygon") { + REQUIRE(simplified.size() == 1); + } + THEN("gear was reduced using Douglas-Peucker") { + //note printf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]}); + REQUIRE(simplified.front().size() < num_points); + } + } + } + GIVEN("hole in square") { + // CW oriented + auto hole_in_square = Polygon{ {140, 140}, {140, 160}, {160, 160}, {160, 140} }; + WHEN("simplified") { + Polygons simplified = hole_in_square.simplify(2.); + THEN("hole simplification returns one polygon") { + REQUIRE(simplified.size() == 1); + } + THEN("hole simplification turns cw polygon into ccw polygon") { + REQUIRE(simplified.front().is_counter_clockwise()); + } + } + } +} diff --git a/tests/libslic3r/test_polyline.cpp b/tests/libslic3r/test_polyline.cpp new file mode 100644 index 0000000000..8271554041 --- /dev/null +++ b/tests/libslic3r/test_polyline.cpp @@ -0,0 +1,28 @@ +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" + +using namespace Slic3r; + +SCENARIO("Simplify polyline", "[Polyline]") +{ + GIVEN("polyline 1") { + auto polyline = Polyline{ {0,0},{1,0},{2,0},{2,1},{2,2},{1,2},{0,2},{0,1},{0,0} }; + WHEN("simplified with Douglas-Peucker") { + polyline.simplify(1.); + THEN("simplified correctly") { + REQUIRE(polyline == Polyline{ {0,0}, {2,0}, {2,2}, {0,2}, {0,0} }); + } + } + } + GIVEN("polyline 2") { + auto polyline = Polyline{ {0,0}, {50,50}, {100,0}, {125,-25}, {150,50} }; + WHEN("simplified with Douglas-Peucker") { + polyline.simplify(25.); + THEN("not simplified") { + REQUIRE(polyline == Polyline{ {0,0}, {50,50}, {125,-25}, {150,50} }); + } + } + } +} diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 06fc98322f..c2343b032c 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -44,30 +44,18 @@ set(XSP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/xsp) #FIXME list the dependecies explicitely, add dependency on the typemap. set(XS_XSP_FILES ${XSP_DIR}/BoundingBox.xsp - ${XSP_DIR}/BridgeDetector.xsp - ${XSP_DIR}/Clipper.xsp ${XSP_DIR}/Config.xsp ${XSP_DIR}/ExPolygon.xsp - ${XSP_DIR}/ExPolygonCollection.xsp ${XSP_DIR}/ExtrusionEntityCollection.xsp ${XSP_DIR}/ExtrusionLoop.xsp - ${XSP_DIR}/ExtrusionMultiPath.xsp ${XSP_DIR}/ExtrusionPath.xsp - ${XSP_DIR}/ExtrusionSimulator.xsp - ${XSP_DIR}/Filler.xsp - ${XSP_DIR}/Flow.xsp - ${XSP_DIR}/GCode.xsp - # ${XSP_DIR}/GCodeSender.xsp ${XSP_DIR}/Geometry.xsp ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp - ${XSP_DIR}/PerimeterGenerator.xsp - ${XSP_DIR}/PlaceholderParser.xsp ${XSP_DIR}/Point.xsp ${XSP_DIR}/Polygon.xsp ${XSP_DIR}/Polyline.xsp - ${XSP_DIR}/PolylineCollection.xsp ${XSP_DIR}/Print.xsp ${XSP_DIR}/Surface.xsp ${XSP_DIR}/SurfaceCollection.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 6d3bf35cf2..87fb267c51 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -4,21 +4,6 @@ use strict; our $VERSION = '0.01'; -# We have to load these modules in order to have Wx.pm find the correct paths -# for wxWidgets dlls on MSW. -# We avoid loading these on OS X because Wx::Load() initializes a Wx App -# automatically and it steals focus even when we're not running Slic3r in GUI mode. -# TODO: only load these when compiling with GUI support -BEGIN { - if ($^O eq 'MSWin32') { - eval "use Wx"; - eval "use Wx::GLCanvas"; - eval "use Wx::GLContext"; - eval "use Wx::Html"; - eval "use Wx::Print"; # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code) - } -} - use Carp qw(); use XSLoader; XSLoader::load(__PACKAGE__, $VERSION); @@ -58,21 +43,11 @@ use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -package Slic3r::Polyline::Collection; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - package Slic3r::Polygon; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -package Slic3r::ExPolygon::Collection; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - package Slic3r::ExtrusionPath::Collection; use overload '@{}' => sub { $_[0]->arrayref }, @@ -99,11 +74,6 @@ sub new_from_paths { return $loop; } -package Slic3r::ExtrusionMultiPath; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - package Slic3r::ExtrusionPath; use overload '@{}' => sub { $_[0]->arrayref }, @@ -133,42 +103,6 @@ sub clone { ); } -package Slic3r::ExtrusionSimulator; - -sub new { - my ($class, %args) = @_; - return $class->_new(); -} - -package Slic3r::Filler; - -sub fill_surface { - my ($self, $surface, %args) = @_; - $self->set_density($args{density}) if defined($args{density}); - $self->set_dont_adjust($args{dont_adjust}) if defined($args{dont_adjust}); - $self->set_complete($args{complete}) if defined($args{complete}); - return $self->_fill_surface($surface); -} - -package Slic3r::Flow; - -sub new { - my ($class, %args) = @_; - - my $self = $class->_new( - @args{qw(width height nozzle_diameter)}, - ); - return $self; -} - -sub new_from_width { - my ($class, %args) = @_; - - return $class->_new_from_width( - @args{qw(role width nozzle_diameter layer_height)}, - ); -} - package Slic3r::Surface; sub new { @@ -214,65 +148,20 @@ sub new { return $self; } -package Slic3r::Print::SupportMaterial2; - -sub new { - my ($class, %args) = @_; - - return $class->_new( - $args{print_config}, # required - $args{object_config}, # required - $args{first_layer_flow}, # required - $args{flow}, # required - $args{interface_flow}, # required - $args{soluble_interface} // 0 - ); -} - -package Slic3r::GUI::_3DScene::GLVolume::Collection; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -package Slic3r::GUI::PresetCollection; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - package main; for my $class (qw( - Slic3r::BridgeDetector Slic3r::Config - Slic3r::Config::Full Slic3r::Config::GCode Slic3r::Config::Print - Slic3r::Config::PrintObject - Slic3r::Config::PrintRegion Slic3r::Config::Static Slic3r::ExPolygon - Slic3r::ExPolygon::Collection Slic3r::ExtrusionLoop - Slic3r::ExtrusionMultiPath Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection - Slic3r::ExtrusionSimulator - Slic3r::Filler - Slic3r::Flow - Slic3r::GCode - Slic3r::GCode::PlaceholderParser Slic3r::Geometry::BoundingBox - Slic3r::Geometry::BoundingBoxf - Slic3r::Geometry::BoundingBoxf3 - Slic3r::GUI::_3DScene::GLShader - Slic3r::GUI::_3DScene::GLVolume - Slic3r::GUI::Preset - Slic3r::GUI::PresetCollection - Slic3r::GUI::Tab Slic3r::Layer Slic3r::Layer::Region - Slic3r::Layer::Support Slic3r::Line - Slic3r::Linef3 Slic3r::Model Slic3r::Model::Instance Slic3r::Model::Material @@ -288,10 +177,8 @@ for my $class (qw( Slic3r::Print Slic3r::Print::Object Slic3r::Print::Region - Slic3r::Print::State Slic3r::Surface Slic3r::Surface::Collection - Slic3r::Print::SupportMaterial2 Slic3r::TriangleMesh )) { diff --git a/xs/main.xs.in b/xs/main.xs.in index c10f432d83..d8db108be5 100644 --- a/xs/main.xs.in +++ b/xs/main.xs.in @@ -2,7 +2,6 @@ #include #include #include -// #include #ifdef __cplusplus /* extern "C" { */ diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 20288243e9..8484b1d64f 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -4,26 +4,15 @@ namespace Slic3r { REGISTER_CLASS(ExPolygon, "ExPolygon"); -REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection"); -REGISTER_CLASS(ExtrusionMultiPath, "ExtrusionMultiPath"); REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); -REGISTER_CLASS(ExtrusionSimulator, "ExtrusionSimulator"); -REGISTER_CLASS(Filler, "Filler"); -REGISTER_CLASS(Flow, "Flow"); -REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(Layer, "Layer"); -REGISTER_CLASS(SupportLayer, "Layer::Support"); REGISTER_CLASS(LayerRegion, "Layer::Region"); REGISTER_CLASS(Line, "Line"); -REGISTER_CLASS(Linef3, "Linef3"); -REGISTER_CLASS(PerimeterGenerator, "Layer::PerimeterGenerator"); -REGISTER_CLASS(PlaceholderParser, "GCode::PlaceholderParser"); REGISTER_CLASS(Polygon, "Polygon"); REGISTER_CLASS(Polyline, "Polyline"); -REGISTER_CLASS(PolylineCollection, "Polyline::Collection"); REGISTER_CLASS(Print, "Print"); REGISTER_CLASS(PrintObject, "Print::Object"); REGISTER_CLASS(PrintRegion, "Print::Region"); @@ -33,22 +22,16 @@ REGISTER_CLASS(ModelObject, "Model::Object"); REGISTER_CLASS(ModelVolume, "Model::Volume"); REGISTER_CLASS(ModelInstance, "Model::Instance"); REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox"); -REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf"); -REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3"); -REGISTER_CLASS(BridgeDetector, "BridgeDetector"); REGISTER_CLASS(Point, "Point"); __REGISTER_CLASS(Vec2d, "Pointf"); __REGISTER_CLASS(Vec3d, "Pointf3"); REGISTER_CLASS(DynamicPrintConfig, "Config"); REGISTER_CLASS(StaticPrintConfig, "Config::Static"); -REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject"); -REGISTER_CLASS(PrintRegionConfig, "Config::PrintRegion"); REGISTER_CLASS(GCodeConfig, "Config::GCode"); REGISTER_CLASS(PrintConfig, "Config::Print"); -REGISTER_CLASS(FullPrintConfig, "Config::Full"); REGISTER_CLASS(Surface, "Surface"); REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); -REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2"); +REGISTER_CLASS(FullPrintConfig, "Config::Full"); REGISTER_CLASS(TriangleMesh, "TriangleMesh"); SV* ConfigBase__as_hash(ConfigBase* THIS) diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t deleted file mode 100644 index 453cc92189..0000000000 --- a/xs/t/01_trianglemesh.t +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 5; - -my $cube = { - vertices => [ [20,20,0], [20,0,0], [0,0,0], [0,20,0], [20,20,20], [0,20,20], [0,0,20], [20,0,20] ], - facets => [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5] ], -}; - -{ - my $m = Slic3r::TriangleMesh->new; - $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); - my ($vertices, $facets) = ($m->vertices, $m->facets); - - is_deeply $vertices, $cube->{vertices}, 'vertices arrayref roundtrip'; - is_deeply $facets, $cube->{facets}, 'facets arrayref roundtrip'; - - { - my $m2 = $m->clone; - is_deeply $m2->vertices, $cube->{vertices}, 'cloned vertices arrayref roundtrip'; - is_deeply $m2->facets, $cube->{facets}, 'cloned facets arrayref roundtrip'; - $m2->scale(3); # check that it does not affect $m - } - - { - my $stats = $m->stats; - is $stats->{number_of_facets}, scalar(@{ $cube->{facets} }), 'stats.number_of_facets'; - } -} - -__END__ diff --git a/xs/t/03_point.t b/xs/t/03_point.t index c950998fbb..f888349b3d 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,10 +4,9 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 24; +use Test::More tests => 21; my $point = Slic3r::Point->new(10, 15); -is_deeply [ @$point ], [10, 15], 'point roundtrip'; my $point2 = $point->clone; $point2->scale(2); @@ -16,9 +15,6 @@ is_deeply [ @$point2 ], [20, 30], 'scale'; $point2->translate(10, -15); is_deeply [ @$point2 ], [30, 15], 'translate'; -ok $point->coincides_with($point->clone), 'coincides_with'; -ok !$point->coincides_with($point2), 'coincides_with'; - { my $point3 = Slic3r::Point->new(4300000, -9880845); is $point->[0], $point->x, 'x accessor'; diff --git a/xs/t/04_expolygon.t b/xs/t/04_expolygon.t index 65e274ab9a..9132c44b96 100644 --- a/xs/t/04_expolygon.t +++ b/xs/t/04_expolygon.t @@ -5,7 +5,7 @@ use warnings; use List::Util qw(first sum); use Slic3r::XS; -use Test::More tests => 21; +use Test::More tests => 7; use constant PI => 4 * atan2(1, 1); @@ -25,21 +25,6 @@ my $hole_in_square = [ # cw my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); ok $expolygon->is_valid, 'is_valid'; -is ref($expolygon->pp), 'ARRAY', 'expolygon pp is unblessed'; -is_deeply $expolygon->pp, [$square, $hole_in_square], 'expolygon roundtrip'; - -is ref($expolygon->arrayref), 'ARRAY', 'expolygon arrayref is unblessed'; -isa_ok $expolygon->[0], 'Slic3r::Polygon::Ref', 'expolygon polygon is blessed'; -isa_ok $expolygon->contour, 'Slic3r::Polygon::Ref', 'expolygon contour is blessed'; -isa_ok $expolygon->holes->[0], 'Slic3r::Polygon::Ref', 'expolygon hole is blessed'; -isa_ok $expolygon->[0][0], 'Slic3r::Point::Ref', 'expolygon point is blessed'; - -{ - my $expolygon2 = $expolygon->clone; - my $polygon = $expolygon2->[0]; - $polygon->scale(2); - is $expolygon2->[0][0][0], $polygon->[0][0], 'polygons are returned by reference'; -} is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone'; @@ -81,28 +66,4 @@ is $expolygon->area, 100*100-20*20, 'area'; ], 'rotate around pure-Perl Point'; } -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->scale(2); - my $collection = Slic3r::ExPolygon::Collection->new($expolygon->pp, $expolygon2->pp); - is_deeply $collection->pp, [ $expolygon->pp, $expolygon2->pp ], - 'expolygon collection (pure Perl) roundtrip'; - - my $collection2 = Slic3r::ExPolygon::Collection->new($expolygon, $expolygon2); - is_deeply $collection->pp, $collection2->pp, - 'expolygon collection (XS) roundtrip'; - - $collection->clear; - is scalar(@$collection), 0, 'clear collection'; - - $collection->append($expolygon); - is scalar(@$collection), 1, 'append to collection'; - - my $exp = $collection->[0]; - $exp->scale(3); - is $collection->[0][0][0][0], $exp->[0][0][0], 'collection items are returned by reference'; - - is_deeply $collection->[0]->clone->pp, $collection->[0]->pp, 'clone collection item'; -} - __END__ diff --git a/xs/t/05_surface.t b/xs/t/05_surface.t index 34feb47346..4d9eb5b891 100644 --- a/xs/t/05_surface.t +++ b/xs/t/05_surface.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 15; +use Test::More tests => 11; my $square = [ # ccw [100, 100], @@ -27,10 +27,6 @@ my $surface = Slic3r::Surface->new( $surface = $surface->clone; -isa_ok $surface->expolygon, 'Slic3r::ExPolygon::Ref', 'expolygon'; -is_deeply [ @{$surface->expolygon->pp} ], [$square, $hole_in_square], 'expolygon roundtrip'; -is scalar(@{$surface->polygons}), 2, 'polygons roundtrip'; - is $surface->surface_type, Slic3r::Surface::S_TYPE_INTERNAL, 'surface_type'; $surface->surface_type(Slic3r::Surface::S_TYPE_BOTTOM); is $surface->surface_type, Slic3r::Surface::S_TYPE_BOTTOM, 'modify surface_type'; @@ -59,7 +55,6 @@ is $surface->extra_perimeters, 2, 'extra_perimeters'; is scalar(@$collection), 1, 'append to collection'; my $item = $collection->[0]; - isa_ok $item, 'Slic3r::Surface::Ref'; $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL); is $item->surface_type, $collection->[0]->surface_type, 'collection returns items by reference'; } diff --git a/xs/t/06_polygon.t b/xs/t/06_polygon.t deleted file mode 100644 index 7bbcd5356b..0000000000 --- a/xs/t/06_polygon.t +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 3; - -my $square = [ # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -]; - -my $polygon = Slic3r::Polygon->new(@$square); -is ref($polygon->arrayref), 'ARRAY', 'polygon arrayref is unblessed'; -isa_ok $polygon->[0], 'Slic3r::Point::Ref', 'polygon point is blessed'; -ok ref($polygon->first_point) eq 'Slic3r::Point', 'first_point'; - -__END__ diff --git a/xs/t/07_extrusionpath.t b/xs/t/07_extrusionpath.t index 008b51b001..084b4f03ea 100644 --- a/xs/t/07_extrusionpath.t +++ b/xs/t/07_extrusionpath.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 7; +use Test::More tests => 5; my $points = [ [100, 100], @@ -17,8 +17,6 @@ my $path = Slic3r::ExtrusionPath->new( role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ); -isa_ok $path->polyline, 'Slic3r::Polyline::Ref', 'path polyline'; -is_deeply $path->polyline->pp, $points, 'path points roundtrip'; $path->reverse; is_deeply $path->polyline->pp, [ reverse @$points ], 'reverse path'; diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t index e0660a9fd9..3abfbd728a 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 => 47; +use Test::More tests => 46; { my $square = [ @@ -33,7 +33,6 @@ use Test::More tests => 47; is scalar(@$loop), 1, 'loop contains one path'; { my $path = $loop->[0]; - isa_ok $path, 'Slic3r::ExtrusionPath::Ref'; is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; } diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t index 5203ec5ef4..7da74b93da 100644 --- a/xs/t/09_polyline.t +++ b/xs/t/09_polyline.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 18; +use Test::More tests => 15; my $points = [ [100, 100], @@ -14,11 +14,6 @@ my $points = [ my $polyline = Slic3r::Polyline->new(@$points); -is_deeply $polyline->pp, $points, 'polyline roundtrip'; - -is ref($polyline->arrayref), 'ARRAY', 'polyline arrayref is unblessed'; -isa_ok $polyline->[0], 'Slic3r::Point::Ref', 'polyline point is blessed'; - my $lines = $polyline->lines; is_deeply [ map $_->pp, @$lines ], [ [ [100, 100], [200, 100] ], @@ -88,41 +83,4 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; is scalar(@$p2), 4, 'split_at'; } -# disabled because we now use a more efficient but incomplete algorithm -#if (0) { -# my $polyline = Slic3r::Polyline->new( -# map [$_,10], (0,10,20,30,40,50,60) -# ); -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [25,0], [55,0], [55,30], [25,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,10,20,30,50,60) -# ], 'simplify_by_visibility()'; -# } -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [-15,0], [75,0], [75,30], [-15,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,60) -# ], 'simplify_by_visibility()'; -# } -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [-15,0], [25,0], [25,30], [-15,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,20,30,40,50,60) -# ], 'simplify_by_visibility()'; -# } -#} - __END__ diff --git a/xs/t/10_line.t b/xs/t/10_line.t index 8f82e988c6..886573f7b6 100644 --- a/xs/t/10_line.t +++ b/xs/t/10_line.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 40; +use Test::More tests => 35; use constant PI => 4 * atan2(1, 1); use constant EPSILON => 1E-4; @@ -15,21 +15,6 @@ my $points = [ ]; my $line = Slic3r::Line->new(@$points); -is_deeply $line->pp, $points, 'line roundtrip'; - -is ref($line->arrayref), 'ARRAY', 'line arrayref is unblessed'; -isa_ok $line->[0], 'Slic3r::Point::Ref', 'line point is blessed'; - -{ - my $clone = $line->clone; - $clone->reverse; - is_deeply $clone->pp, [ reverse @$points ], 'reverse'; -} - -{ - my $line2 = Slic3r::Line->new($line->a->clone, $line->b->clone); - is_deeply $line2->pp, $points, 'line roundtrip with cloned points'; -} { my $clone = $line->clone; diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t index e7e0b1316a..e028542451 100644 --- a/xs/t/12_extrusionpathcollection.t +++ b/xs/t/12_extrusionpathcollection.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 18; +use Test::More tests => 13; my $points = [ [100, 100], @@ -41,12 +41,6 @@ is scalar(@$collection), 3, 'append ExtrusionPath'; $collection->append($loop); is scalar(@$collection), 4, 'append ExtrusionLoop'; -isa_ok $collection->[1], 'Slic3r::ExtrusionPath::Collection::Ref', 'correct object returned for collection'; -isa_ok $collection->[2], 'Slic3r::ExtrusionPath::Ref', 'correct object returned for path'; -isa_ok $collection->[3], 'Slic3r::ExtrusionLoop::Ref', 'correct object returned for loop'; -is ref($collection->[2]->clone), 'Slic3r::ExtrusionPath', 'correct object returned for cloned path'; -is ref($collection->[3]->clone), 'Slic3r::ExtrusionLoop', 'correct object returned for cloned loop'; - is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { diff --git a/xs/t/13_polylinecollection.t b/xs/t/13_polylinecollection.t deleted file mode 100644 index 9b36e7ffa7..0000000000 --- a/xs/t/13_polylinecollection.t +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 3; - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->y, map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained_path_from'; - is_deeply - [ map $_->y, map @$_, @{$collection->chained_path(0)} ], - [15, 18, 20, 10, 8, 5], - 'chained_path'; -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([15,0], [10,0], [4,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->x, map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained_path_from'; -} - -__END__ diff --git a/xs/t/17_boundingbox.t b/xs/t/17_boundingbox.t deleted file mode 100644 index 349e0024de..0000000000 --- a/xs/t/17_boundingbox.t +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 5; - -{ - my @points = ( - Slic3r::Point->new(100, 200), - Slic3r::Point->new(500, -600), - ); - my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@points); - isa_ok $bb, 'Slic3r::Geometry::BoundingBox', 'new_from_points'; - is_deeply $bb->min_point->pp, [100,-600], 'min_point'; - is_deeply $bb->max_point->pp, [500,200], 'max_point'; -} - -{ - my $bb = Slic3r::Geometry::BoundingBox->new; - $bb->merge_point(Slic3r::Point->new(10, 10)); - is_deeply $bb->min_point->pp, [10,10], 'min_point equals to the only defined point'; - is_deeply $bb->max_point->pp, [10,10], 'max_point equals to the only defined point'; -} - -__END__ diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp index a34cad0bc8..75592e7c38 100644 --- a/xs/xsp/BoundingBox.xsp +++ b/xs/xsp/BoundingBox.xsp @@ -50,69 +50,3 @@ new_from_points(CLASS, points) %} }; -%name{Slic3r::Geometry::BoundingBoxf} class BoundingBoxf { - BoundingBoxf(); - ~BoundingBoxf(); - Clone clone() - %code{% RETVAL = THIS; %}; - void merge(BoundingBoxf* bb) %code{% THIS->merge(*bb); %}; - void merge_point(Vec2d* point) %code{% THIS->merge(*point); %}; - void scale(double factor); - void translate(double x, double y); - Clone size(); - Clone center(); - double radius(); - bool empty() %code{% RETVAL = empty(*THIS); %}; - Clone min_point() %code{% RETVAL = THIS->min; %}; - Clone max_point() %code{% RETVAL = THIS->max; %}; - double x_min() %code{% RETVAL = THIS->min(0); %}; - double x_max() %code{% RETVAL = THIS->max(0); %}; - double y_min() %code{% RETVAL = THIS->min(1); %}; - double y_max() %code{% RETVAL = THIS->max(1); %}; - void set_x_min(double val) %code{% THIS->min(0) = val; %}; - void set_x_max(double val) %code{% THIS->max(0) = val; %}; - void set_y_min(double val) %code{% THIS->min(1) = val; %}; - void set_y_max(double val) %code{% THIS->max(1) = val; %}; - std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf;%lf,%lf", THIS->min(0), THIS->min(1), THIS->max(0), THIS->max(1)); RETVAL = buf; %}; - bool defined() %code{% RETVAL = THIS->defined; %}; - -%{ - -BoundingBoxf* -new_from_points(CLASS, points) - char* CLASS - Pointfs points - CODE: - RETVAL = new BoundingBoxf(points); - OUTPUT: - RETVAL - -%} -}; - -%name{Slic3r::Geometry::BoundingBoxf3} class BoundingBoxf3 { - BoundingBoxf3(); - ~BoundingBoxf3(); - Clone clone() - %code{% RETVAL = THIS; %}; - void merge(BoundingBoxf3* bb) %code{% THIS->merge(*bb); %}; - void merge_point(Vec3d* point) %code{% THIS->merge(*point); %}; - void scale(double factor); - void translate(double x, double y, double z); - void offset(double delta); - bool contains_point(Vec3d* point) %code{% RETVAL = THIS->contains(*point); %}; - Clone size(); - Clone center(); - double radius(); - bool empty() %code{% RETVAL = empty(*THIS); %}; - Clone min_point() %code{% RETVAL = THIS->min; %}; - Clone max_point() %code{% RETVAL = THIS->max; %}; - double x_min() %code{% RETVAL = THIS->min(0); %}; - double x_max() %code{% RETVAL = THIS->max(0); %}; - double y_min() %code{% RETVAL = THIS->min(1); %}; - double y_max() %code{% RETVAL = THIS->max(1); %}; - double z_min() %code{% RETVAL = THIS->min(2); %}; - double z_max() %code{% RETVAL = THIS->max(2); %}; - std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf,%lf;%lf,%lf,%lf", THIS->min(0), THIS->min(1), THIS->min(2), THIS->max(0), THIS->max(1), THIS->max(2)); RETVAL = buf; %}; - bool defined() %code{% RETVAL = THIS->defined; %}; -}; diff --git a/xs/xsp/BridgeDetector.xsp b/xs/xsp/BridgeDetector.xsp deleted file mode 100644 index eb3793cf7c..0000000000 --- a/xs/xsp/BridgeDetector.xsp +++ /dev/null @@ -1,43 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/BridgeDetector.hpp" -%} - -%name{Slic3r::BridgeDetector} class BridgeDetector { - ~BridgeDetector(); - - bool detect_angle(); - Polygons coverage(); - %name{coverage_by_angle} Polygons coverage(double angle); - Polylines unsupported_edges(); - %name{unsupported_edges_by_angle} Polylines unsupported_edges(double angle); - double angle() - %code{% RETVAL = THIS->angle; %}; - double resolution() - %code{% RETVAL = THIS->resolution; %}; -%{ - -BridgeDetector* -BridgeDetector::new(expolygon, lower_slices, extrusion_width) - ExPolygon* expolygon; - ExPolygonCollection* lower_slices; - int extrusion_width; - CODE: - RETVAL = new BridgeDetector(*expolygon, lower_slices->expolygons, extrusion_width); - OUTPUT: - RETVAL - -BridgeDetector* -BridgeDetector::new_expolygons(expolygons, lower_slices, extrusion_width) - ExPolygonCollection* expolygons; - ExPolygonCollection* lower_slices; - int extrusion_width; - CODE: - RETVAL = new BridgeDetector(expolygons->expolygons, lower_slices->expolygons, extrusion_width); - OUTPUT: - RETVAL - -%} -}; diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp deleted file mode 100644 index eae3afeffb..0000000000 --- a/xs/xsp/Clipper.xsp +++ /dev/null @@ -1,132 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ClipperUtils.hpp" -%} - -%package{Slic3r::Geometry::Clipper}; - -%{ - -IV -_constant() - ALIAS: - JT_MITER = jtMiter - JT_ROUND = jtRound - JT_SQUARE = jtSquare - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -Polygons -offset(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - RETVAL = offset(polygons, delta, joinType, miterLimit); - OUTPUT: - RETVAL - -ExPolygons -offset_ex(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - RETVAL = offset_ex(polygons, delta, joinType, miterLimit); - OUTPUT: - RETVAL - -ExPolygons -offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta1 - const float delta2 - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - RETVAL = offset2_ex(union_ex(polygons), delta1, delta2, joinType, miterLimit); - OUTPUT: - RETVAL - -Polygons -diff(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - RETVAL = diff(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); - OUTPUT: - RETVAL - -ExPolygons -diff_ex(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - RETVAL = diff_ex(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); - OUTPUT: - RETVAL - -Polylines -diff_pl(subject, clip) - Polylines subject - Polygons clip - CODE: - RETVAL = diff_pl(subject, clip); - OUTPUT: - RETVAL - -Polygons -intersection(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - RETVAL = intersection(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); - OUTPUT: - RETVAL - -ExPolygons -intersection_ex(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - RETVAL = intersection_ex(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); - OUTPUT: - RETVAL - -Polylines -intersection_pl(subject, clip) - Polylines subject - Polygons clip - CODE: - RETVAL = intersection_pl(subject, clip); - OUTPUT: - RETVAL - -Polygons -union(subject, safety_offset = false) - Polygons subject - bool safety_offset - CODE: - RETVAL = safety_offset ? union_safety_offset(subject) : union_(subject); - OUTPUT: - RETVAL - -ExPolygons -union_ex(subject, safety_offset = false) - Polygons subject - bool safety_offset - CODE: - RETVAL = safety_offset ? union_safety_offset_ex(subject) : union_ex(subject); - OUTPUT: - RETVAL - -%} diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 7034270351..14f235d93d 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -76,10 +76,6 @@ %code{% RETVAL = new GCodeConfig(); %}; static StaticPrintConfig* new_PrintConfig() %code{% RETVAL = static_cast(new PrintConfig()); %}; - static StaticPrintConfig* new_PrintObjectConfig() - %code{% RETVAL = new PrintObjectConfig(); %}; - static StaticPrintConfig* new_PrintRegionConfig() - %code{% RETVAL = new PrintRegionConfig(); %}; static StaticPrintConfig* new_FullPrintConfig() %code{% RETVAL = static_cast(new FullPrintConfig()); %}; ~StaticPrintConfig(); diff --git a/xs/xsp/ExPolygon.xsp b/xs/xsp/ExPolygon.xsp index a57bcfbcb8..50b32544ee 100644 --- a/xs/xsp/ExPolygon.xsp +++ b/xs/xsp/ExPolygon.xsp @@ -29,8 +29,6 @@ %code{% RETVAL = THIS->contains(*point); %}; ExPolygons simplify(double tolerance); Polygons simplify_p(double tolerance); - Polylines medial_axis(double max_width, double min_width) - %code{% THIS->medial_axis(max_width, min_width, &RETVAL); %}; %{ ExPolygon* diff --git a/xs/xsp/ExPolygonCollection.xsp b/xs/xsp/ExPolygonCollection.xsp deleted file mode 100644 index 5321afb155..0000000000 --- a/xs/xsp/ExPolygonCollection.xsp +++ /dev/null @@ -1,81 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExPolygonCollection.hpp" -%} - -%name{Slic3r::ExPolygon::Collection} class ExPolygonCollection { - ~ExPolygonCollection(); - Clone clone() - %code{% RETVAL = THIS; %}; - void clear() - %code{% THIS->expolygons.clear(); %}; - void scale(double factor); - void translate(double x, double y); - void rotate(double angle, Point* center) - %code{% THIS->rotate(angle, *center); %}; - int count() - %code{% RETVAL = THIS->expolygons.size(); %}; - bool contains_point(Point* point) - %code{% RETVAL = THIS->contains(*point); %}; - bool contains_line(Line* line) - %code{% RETVAL = THIS->contains(*line); %}; - bool contains_polyline(Polyline* polyline) - %code{% RETVAL = THIS->contains(*polyline); %}; - void simplify(double tolerance); - Polygons polygons() - %code{% RETVAL = (Polygons)*THIS; %}; - Clone convex_hull(); -%{ - -ExPolygonCollection* -ExPolygonCollection::new(...) - CODE: - RETVAL = new ExPolygonCollection (); - // ST(0) is class name, others are expolygons - RETVAL->expolygons.resize(items-1); - for (unsigned int i = 1; i < items; i++) { - // Note: a COPY of the input is stored - from_SV_check(ST(i), &RETVAL->expolygons[i-1]); - } - OUTPUT: - RETVAL - -SV* -ExPolygonCollection::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->expolygons.size()-1); - int i = 0; - for (ExPolygons::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) { - av_store(av, i++, perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -SV* -ExPolygonCollection::pp() - CODE: - AV* av = newAV(); - av_fill(av, THIS->expolygons.size()-1); - int i = 0; - for (ExPolygons::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) { - av_store(av, i++, to_SV_pureperl(&*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -void -ExPolygonCollection::append(...) - CODE: - for (unsigned int i = 1; i < items; i++) { - ExPolygon expolygon; - from_SV_check(ST(i), &expolygon); - THIS->expolygons.push_back(expolygon); - } - -%} -}; diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp index a01788c56c..1c33373037 100644 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ b/xs/xsp/ExtrusionEntityCollection.xsp @@ -55,8 +55,6 @@ ExtrusionEntityCollection::arrayref() // return our item by reference if (ExtrusionPath* path = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(path), path ); - } else if (ExtrusionMultiPath* multipath = dynamic_cast(*it)) { - sv_setref_pv( sv, perl_class_name_ref(multipath), multipath ); } else if (ExtrusionLoop* loop = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(loop), loop ); } else if (ExtrusionEntityCollection* collection = dynamic_cast(*it)) { @@ -81,8 +79,6 @@ ExtrusionEntityCollection::append(...) // append COPIES if (ExtrusionPath* path = dynamic_cast(entity)) { THIS->entities.push_back( new ExtrusionPath(*path) ); - } else if (ExtrusionMultiPath* multipath = dynamic_cast(entity)) { - THIS->entities.push_back( new ExtrusionMultiPath(*multipath) ); } else if (ExtrusionLoop* loop = dynamic_cast(entity)) { THIS->entities.push_back( new ExtrusionLoop(*loop) ); } else if(ExtrusionEntityCollection* collection = dynamic_cast(entity)) { diff --git a/xs/xsp/ExtrusionMultiPath.xsp b/xs/xsp/ExtrusionMultiPath.xsp deleted file mode 100644 index 5dd9382450..0000000000 --- a/xs/xsp/ExtrusionMultiPath.xsp +++ /dev/null @@ -1,38 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionEntity.hpp" -%} - -%name{Slic3r::ExtrusionMultiPath} class ExtrusionMultiPath { - ExtrusionMultiPath(); - ~ExtrusionMultiPath(); - Clone clone() - %code{% RETVAL = THIS; %}; - void reverse(); - Clone first_point(); - Clone last_point(); - void append(ExtrusionPath* path) - %code{% THIS->paths.push_back(*path); %}; - double length(); - Polygons polygons_covered_by_width(); - Polygons polygons_covered_by_spacing(); - Clone polyline() - %code{% RETVAL = THIS->as_polyline(); %}; -%{ - -SV* -ExtrusionMultiPath::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->paths.size()-1); - for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) { - av_store(av, it - THIS->paths.begin(), perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -%} -}; diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp index 078e6fe721..1dbc009174 100644 --- a/xs/xsp/ExtrusionPath.xsp +++ b/xs/xsp/ExtrusionPath.xsp @@ -95,22 +95,6 @@ ExtrusionPath::append(...) THIS->polyline.points.push_back(p); } -ExtrusionEntityCollection* -ExtrusionPath::intersect_expolygons(ExPolygonCollection* collection) - CODE: - RETVAL = new ExtrusionEntityCollection (); - THIS->intersect_expolygons(*collection, RETVAL); - OUTPUT: - RETVAL - -ExtrusionEntityCollection* -ExtrusionPath::subtract_expolygons(ExPolygonCollection* collection) - CODE: - RETVAL = new ExtrusionEntityCollection (); - THIS->subtract_expolygons(*collection, RETVAL); - OUTPUT: - RETVAL - %} }; diff --git a/xs/xsp/ExtrusionSimulator.xsp b/xs/xsp/ExtrusionSimulator.xsp deleted file mode 100644 index 9395913b41..0000000000 --- a/xs/xsp/ExtrusionSimulator.xsp +++ /dev/null @@ -1,50 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionSimulator.hpp" -%} - -%name{Slic3r::ExtrusionSimulator} class ExtrusionSimulator { - ~ExtrusionSimulator(); - %name{_new} ExtrusionSimulator(); - - Clone clone() - %code{% RETVAL = THIS; %}; - - void set_image_size(Point *image_size) - %code{% THIS->set_image_size(*image_size); %}; - void set_viewport(BoundingBox *viewport) - %code{% THIS->set_viewport(*viewport); %}; - void set_bounding_box(BoundingBox *bbox) - %code{% THIS->set_bounding_box(*bbox); %}; - - void reset_accumulator(); - void extrude_to_accumulator(ExtrusionPath *path, Point *shift, ExtrusionSimulationType simulationType) - %code{% THIS->extrude_to_accumulator(*path, *shift, simulationType); %}; - void evaluate_accumulator(ExtrusionSimulationType simulationType); - void* image_ptr() - %code{% RETVAL = const_cast(const_cast(THIS)->image_ptr()); %}; - -%{ - -%} -}; - -%package{Slic3r::ExtrusionSimulator}; -%{ - -IV -_constant() - ALIAS: - EXTRSIM_SIMPLE = ExtrusionSimulationSimple - EXTRSIM_DONT_SPREAD = ExtrusionSimulationDontSpread - EXTRSIM_SPREAD_NFULL = ExtrisopmSimulationSpreadNotOverfilled - EXTRSIM_SPREAD_FULL = ExtrusionSimulationSpreadFull - EXTRSIM_SPREAD_EXCESS = ExtrusionSimulationSpreadExcess - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp deleted file mode 100644 index 2ea2b34d5a..0000000000 --- a/xs/xsp/Filler.xsp +++ /dev/null @@ -1,65 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Fill/Fill.hpp" -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/ExtrusionEntityCollection.hpp" -%} - -%name{Slic3r::Filler} class Filler { - ~Filler(); - - void set_bounding_box(BoundingBox *bbox) - %code{% THIS->fill->set_bounding_box(*bbox); %}; - void set_spacing(coordf_t spacing) - %code{% THIS->fill->spacing = spacing; %}; - coordf_t spacing() - %code{% RETVAL = THIS->fill->spacing; %}; - void set_layer_id(size_t layer_id) - %code{% THIS->fill->layer_id = layer_id; %}; - void set_z(coordf_t z) - %code{% THIS->fill->z = z; %}; - void set_angle(float angle) - %code{% THIS->fill->angle = angle; %}; - void set_link_max_length(coordf_t len) - %code{% THIS->fill->link_max_length = len; %}; - void set_loop_clipping(coordf_t clipping) - %code{% THIS->fill->loop_clipping = clipping; %}; - - bool use_bridge_flow() - %code{% RETVAL = THIS->fill->use_bridge_flow(); %}; - bool no_sort() - %code{% RETVAL = THIS->fill->no_sort(); %}; - - void set_density(float density) - %code{% THIS->params.density = density; %}; - void set_dont_adjust(bool dont_adjust) - %code{% THIS->params.dont_adjust = dont_adjust; %}; - - PolylineCollection* _fill_surface(Surface *surface) - %code{% - PolylineCollection *pc = NULL; - if (THIS->fill != NULL) { - pc = new PolylineCollection(); - pc->polylines = THIS->fill->fill_surface(surface, THIS->params); - } - RETVAL = pc; - %}; - -%{ - -Filler* -new_from_type(CLASS, type) - char* CLASS; - std::string type; - CODE: - Filler *filler = new Filler(); - filler->fill = Fill::new_from_type(type); - RETVAL = filler; - OUTPUT: - RETVAL - -%} - -}; diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp deleted file mode 100644 index 3056b40015..0000000000 --- a/xs/xsp/Flow.xsp +++ /dev/null @@ -1,60 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Flow.hpp" -%} - -%name{Slic3r::Flow} class Flow { - ~Flow(); - %name{_new} Flow(float width, float height, float nozzle_diameter); - Clone clone() - %code{% RETVAL = THIS; %}; - - float width(); - float height(); - float nozzle_diameter(); - bool bridge(); - float spacing(); - int scaled_width(); - int scaled_spacing(); - double mm3_per_mm(); -%{ - -Flow* -_new_from_width(CLASS, role, width, nozzle_diameter, height) - char* CLASS; - FlowRole role; - std::string width; - float nozzle_diameter; - float height; - CODE: - ConfigOptionFloatOrPercent optwidth; - optwidth.deserialize(width, ForwardCompatibilitySubstitutionRule::Disable); - RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height)); - OUTPUT: - RETVAL - -%} -}; - -%package{Slic3r::Flow}; -%{ - -IV -_constant() - ALIAS: - FLOW_ROLE_EXTERNAL_PERIMETER = frExternalPerimeter - FLOW_ROLE_PERIMETER = frPerimeter - FLOW_ROLE_INFILL = frInfill - FLOW_ROLE_SOLID_INFILL = frSolidInfill - FLOW_ROLE_TOP_SOLID_INFILL = frTopSolidInfill - FLOW_ROLE_SUPPORT_MATERIAL = frSupportMaterial - FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE = frSupportMaterialInterface - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} - diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp deleted file mode 100644 index 4c25838946..0000000000 --- a/xs/xsp/GCode.xsp +++ /dev/null @@ -1,53 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/GCode.hpp" -#include "libslic3r/GCode/CoolingBuffer.hpp" -%} - -%name{Slic3r::GCode::CoolingBuffer} class CoolingBuffer { - CoolingBuffer(GCode* gcode) - %code{% RETVAL = new CoolingBuffer(*gcode); %}; - ~CoolingBuffer(); - std::string process_layer(std::string gcode, size_t layer_id) - %code{% RETVAL = THIS->process_layer(std::move(gcode), layer_id, true); %}; - -}; - -%name{Slic3r::GCode} class GCode { - GCode(); - ~GCode(); - void do_export(Print *print, const char *path) - %code%{ - try { - THIS->do_export(print, path); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; - - Ref origin() - %code{% RETVAL = &(THIS->origin()); %}; - void set_origin(Vec2d* pointf) - %code{% THIS->set_origin(*pointf); %}; - Ref last_pos() - %code{% RETVAL = &(THIS->last_pos()); %}; - - unsigned int layer_count() const; - void set_layer_count(unsigned int value); - void set_extruders(std::vector extruders) - %code{% THIS->writer().set_extruders(extruders); THIS->writer().set_extruder(0); %}; - - void apply_print_config(StaticPrintConfig* print_config) - %code{% - if (const PrintConfig* config = dynamic_cast(print_config)) { - THIS->apply_print_config(*config); - } else { - CONFESS("A PrintConfig object was not supplied to apply_print_config()"); - } - %}; - - Ref config() - %code{% RETVAL = const_cast(static_cast(static_cast(&THIS->config()))); %}; -}; diff --git a/xs/xsp/GCodeSender.xsp b/xs/xsp/GCodeSender.xsp deleted file mode 100644 index f99244a1ff..0000000000 --- a/xs/xsp/GCodeSender.xsp +++ /dev/null @@ -1,24 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/GCodeSender.hpp" -%} - -%name{Slic3r::GCode::Sender} class GCodeSender { - GCodeSender(); - ~GCodeSender(); - - bool connect(std::string port, unsigned int baud_rate); - void disconnect(); - bool is_connected(); - bool wait_connected(unsigned int timeout = 3); - int queue_size(); - void send(std::string s, bool priority = false); - void pause_queue(); - void resume_queue(); - void purge_queue(bool priority = false); - std::vector purge_log(); - std::string getT(); - std::string getB(); -}; diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index e59e8793b2..f6365a20a5 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -10,35 +10,8 @@ %package{Slic3r::Geometry}; -Pointfs arrange(size_t total_parts, Vec2d* part, coordf_t dist, BoundingBoxf* bb = NULL) - %code{% - Pointfs points; - if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points)) - CONFESS("%zu parts won't fit in your print area!\n", total_parts); - RETVAL = points; - %}; - %{ -bool -directions_parallel(angle1, angle2) - double angle1 - double angle2 - CODE: - RETVAL = Slic3r::Geometry::directions_parallel(angle1, angle2); - OUTPUT: - RETVAL - -bool -directions_parallel_within(angle1, angle2, max_diff) - double angle1 - double angle2 - double max_diff - CODE: - RETVAL = Slic3r::Geometry::directions_parallel(angle1, angle2, max_diff); - OUTPUT: - RETVAL - Clone convex_hull(points) Points points @@ -47,14 +20,6 @@ convex_hull(points) OUTPUT: RETVAL -std::vector -chained_path(points) - Points points - CODE: - RETVAL = chain_points(points); - OUTPUT: - RETVAL - std::vector chained_path_from(points, start_from) Points points @@ -72,14 +37,6 @@ rad2deg(angle) OUTPUT: RETVAL -double -rad2deg_dir(angle) - double angle - CODE: - RETVAL = Slic3r::Geometry::rad2deg_dir(angle); - OUTPUT: - RETVAL - double deg2rad(angle) double angle @@ -88,16 +45,6 @@ deg2rad(angle) OUTPUT: RETVAL -Polygons -simplify_polygons(polygons, tolerance) - Polygons polygons - double tolerance - CODE: - Slic3r::Geometry::simplify_polygons(polygons, tolerance, &RETVAL); - OUTPUT: - RETVAL - - IV _constant() ALIAS: diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index b97e340bdb..e42486985d 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -3,7 +3,6 @@ %{ #include #include "libslic3r/Layer.hpp" -#include "libslic3r/ExPolygonCollection.hpp" %} %name{Slic3r::Layer::Region} class LayerRegion { @@ -24,8 +23,6 @@ Ref fills() %code%{ RETVAL = &THIS->fills; %}; - Clone flow(FlowRole role) - %code%{ RETVAL = THIS->flow(role); %}; void prepare_fill_surfaces(); void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) %code%{ THIS->make_perimeters(*slices, fill_surfaces); %}; @@ -59,15 +56,9 @@ Ref get_region(int idx); Ref add_region(PrintRegion* print_region); - ExPolygonCollection* slices() - %code%{ RETVAL = new ExPolygonCollection(THIS->lslices); %}; - int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; - Ref as_support_layer() - %code%{ RETVAL = dynamic_cast(THIS); %}; - void make_slices(); void backup_untyped_slices(); void restore_untyped_slices(); @@ -79,41 +70,3 @@ void export_region_slices_to_svg_debug(const char *name); void export_region_fill_surfaces_to_svg_debug(const char *name); }; - -%name{Slic3r::Layer::Support} class SupportLayer { - // owned by PrintObject, no constructor/destructor - - Ref as_layer() - %code%{ RETVAL = THIS; %}; - - Ref support_islands() - %code%{ RETVAL = &THIS->support_islands; %}; - Ref support_fills() - %code%{ RETVAL = &THIS->support_fills; %}; - - // copies of some Layer methods, because the parameter wrapper code - // gets confused about getting a Layer::Support instead of a Layer - int id(); - void set_id(int id); - Ref object(); - bool slicing_errors() - %code%{ RETVAL = THIS->slicing_errors; %}; - coordf_t slice_z() - %code%{ RETVAL = THIS->slice_z; %}; - coordf_t print_z() - %code%{ RETVAL = THIS->print_z; %}; - coordf_t height() - %code%{ RETVAL = THIS->height; %}; - - size_t region_count(); - Ref get_region(int idx); - Ref add_region(PrintRegion* print_region); - - ExPolygonCollection* slices() - %code%{ RETVAL = new ExPolygonCollection(THIS->lslices); %}; - - void export_region_slices_to_svg(const char *path); - void export_region_fill_surfaces_to_svg(const char *path); - void export_region_slices_to_svg_debug(const char *name); - void export_region_fill_surfaces_to_svg_debug(const char *name); -}; diff --git a/xs/xsp/Line.xsp b/xs/xsp/Line.xsp index 36181c3bab..67308721a3 100644 --- a/xs/xsp/Line.xsp +++ b/xs/xsp/Line.xsp @@ -76,17 +76,3 @@ Line::coincides_with(line_sv) %} }; - -%name{Slic3r::Linef3} class Linef3 { - Linef3(Vec3d* a, Vec3d* 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); - void scale(double factor); -}; diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 34795681ef..2194089a54 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -71,7 +71,6 @@ %code%{ RETVAL = THIS->materials.size(); %}; bool add_default_instances(); - Clone bounding_box(); void center_instances_around_point(Vec2d* point) %code%{ THIS->center_instances_around_point(*point); %}; void translate(double x, double y, double z); @@ -80,11 +79,8 @@ ModelObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; - bool arrange_objects(double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) arrange_objects(*THIS, scaled(*bb), ap); else arrange_objects(*THIS, InfiniteBed{}, ap); %}; - void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) duplicate(*THIS, copies_num, scaled(*bb), ap); else duplicate(*THIS, copies_num, InfiniteBed{}, ap); %}; - void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) duplicate_objects(*THIS, copies_num, scaled(*bb), ap); else duplicate_objects(*THIS, copies_num, InfiniteBed{}, ap); %}; - void duplicate_objects_grid(unsigned int x, unsigned int y, double dist); - + bool arrange_objects(double dist) %code%{ ArrangeParams ap{scaled(dist)}; arrange_objects(*THIS, InfiniteBed{}, ap); %}; + void duplicate(unsigned int copies_num, double dist) %code%{ ArrangeParams ap{scaled(dist)}; duplicate(*THIS, copies_num, InfiniteBed{}, ap); %}; bool looks_like_multipart_object() const; void convert_multipart_object(unsigned int max_extruders); @@ -150,9 +146,6 @@ ModelMaterial::attributes() void invalidate_bounding_box(); Clone mesh(); Clone raw_mesh(); - Clone instance_bounding_box(int idx) - %code%{ RETVAL = THIS->instance_bounding_box(idx, true); %}; - Clone bounding_box(); %name{_add_volume} Ref add_volume(TriangleMesh* mesh) %code%{ RETVAL = THIS->add_volume(*mesh); %}; @@ -290,7 +283,4 @@ ModelMaterial::attributes() THIS->set_offset(X, (*offset)(0)); THIS->set_offset(Y, (*offset)(1)); %}; - - void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; - void transform_polygon(Polygon* polygon) const; }; diff --git a/xs/xsp/PerimeterGenerator.xsp b/xs/xsp/PerimeterGenerator.xsp deleted file mode 100644 index a2f589d0b2..0000000000 --- a/xs/xsp/PerimeterGenerator.xsp +++ /dev/null @@ -1,40 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/PerimeterGenerator.hpp" -#include "libslic3r/Layer.hpp" -%} - -%name{Slic3r::Layer::PerimeterGenerator} class PerimeterGenerator { - PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow* flow, - StaticPrintConfig* region_config, StaticPrintConfig* object_config, - StaticPrintConfig* print_config, ExtrusionEntityCollection* loops, - ExtrusionEntityCollection* gap_fill, - SurfaceCollection* fill_surfaces) - %code{% RETVAL = new PerimeterGenerator(slices, layer_height, *flow, - dynamic_cast(region_config), - dynamic_cast(object_config), - dynamic_cast(print_config), - false, - loops, gap_fill, fill_surfaces); %}; - ~PerimeterGenerator(); - - void set_lower_slices(ExPolygonCollection* lower_slices) - %code{% THIS->lower_slices = &lower_slices->expolygons; %}; - void set_layer_id(int layer_id) - %code{% THIS->layer_id = layer_id; %}; - void set_perimeter_flow(Flow* flow) - %code{% THIS->perimeter_flow = *flow; %}; - void set_ext_perimeter_flow(Flow* flow) - %code{% THIS->ext_perimeter_flow = *flow; %}; - void set_overhang_flow(Flow* flow) - %code{% THIS->overhang_flow = *flow; %}; - void set_solid_infill_flow(Flow* flow) - %code{% THIS->solid_infill_flow = *flow; %}; - - Ref config() - %code{% RETVAL = THIS->config; %}; - - void process(); -}; diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp deleted file mode 100644 index 5fa4e33aa1..0000000000 --- a/xs/xsp/PlaceholderParser.xsp +++ /dev/null @@ -1,33 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include -#include "libslic3r/PlaceholderParser.hpp" -%} - -%name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser { - PlaceholderParser(); - ~PlaceholderParser(); - - void apply_config(DynamicPrintConfig *config) - %code%{ THIS->apply_config(*config); %}; - void set(std::string key, int value); - std::string process(std::string str) const - %code%{ - try { - RETVAL = THIS->process(str, 0); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; - - bool evaluate_boolean_expression(const char *str) const - %code%{ - try { - RETVAL = THIS->evaluate_boolean_expression(str, THIS->config()); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; -}; diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 6b6d525240..873c4bcc93 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -23,7 +23,6 @@ %code{% RETVAL = THIS->split_at_vertex(*point); %}; Clone split_at_index(int index); Clone split_at_first_point(); - Points equally_spaced_points(double distance); double length(); double area(); bool is_counter_clockwise(); @@ -35,18 +34,8 @@ bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; Polygons simplify(double tolerance); - Polygons triangulate_convex() - %code{% THIS->triangulate_convex(&RETVAL); %}; Clone centroid(); Clone bounding_box(); - Clone point_projection(Point* point) - %code{% RETVAL = THIS->point_projection(*point); %}; - Clone intersection(Line* line) - %code{% - Point p; - (void)THIS->intersection(*line, &p); - RETVAL = p; - %}; Clone first_intersection(Line* line) %code{% Point p; diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 7846ea5f4c..3534e329ed 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -3,7 +3,6 @@ %{ #include #include "libslic3r/BoundingBox.hpp" -#include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Polyline.hpp" %} @@ -23,7 +22,6 @@ Lines lines(); Clone first_point(); Clone last_point(); - Points equally_spaced_points(double distance); double length(); bool is_valid(); void clip_end(double distance); @@ -33,9 +31,7 @@ void simplify(double tolerance); void split_at(Point* point, Polyline* p1, Polyline* p2) %code{% THIS->split_at(*point, p1, p2); %}; - bool is_straight(); Clone bounding_box(); - void remove_duplicate_points(); %{ Polyline* @@ -76,15 +72,5 @@ Polyline::rotate(angle, center_sv) from_SV_check(center_sv, ¢er); THIS->rotate(angle, center); -Polygons -Polyline::grow(delta, joinType = Slic3r::ClipperLib::jtSquare, miterLimit = 3) - const float delta - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - RETVAL = offset(*THIS, delta, joinType, miterLimit); - OUTPUT: - RETVAL - %} }; diff --git a/xs/xsp/PolylineCollection.xsp b/xs/xsp/PolylineCollection.xsp deleted file mode 100644 index d8bb41b497..0000000000 --- a/xs/xsp/PolylineCollection.xsp +++ /dev/null @@ -1,81 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include - -#include "libslic3r.h" -#include "Polyline.hpp" -#include "ShortestPath.hpp" - -%} - -%name{Slic3r::Polyline::Collection} class PolylineCollection { - ~PolylineCollection(); - Clone clone() - %code{% RETVAL = THIS; %}; - void clear() - %code{% THIS->polylines.clear(); %}; - PolylineCollection* chained_path(bool no_reverse) - %code{% - RETVAL = new PolylineCollection(); - RETVAL->polylines = chain_polylines(THIS->polylines, &THIS->polylines.front().first_point()); - %}; - PolylineCollection* chained_path_from(Point* start_near, bool no_reverse) - %code{% - RETVAL = new PolylineCollection(); - RETVAL->polylines = chain_polylines(THIS->polylines, start_near); - %}; - int count() - %code{% RETVAL = THIS->polylines.size(); %}; -%{ - -PolylineCollection* -PolylineCollection::new(...) - CODE: - RETVAL = new PolylineCollection (); - // ST(0) is class name, others are Polylines - RETVAL->polylines.resize(items-1); - for (unsigned int i = 1; i < items; i++) { - // Note: a COPY of the input is stored - from_SV_check(ST(i), &RETVAL->polylines[i-1]); - } - OUTPUT: - RETVAL - -SV* -PolylineCollection::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->polylines.size()-1); - int i = 0; - for (Polylines::iterator it = THIS->polylines.begin(); it != THIS->polylines.end(); ++it) { - av_store(av, i++, perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -SV* -PolylineCollection::pp() - CODE: - AV* av = newAV(); - av_fill(av, THIS->polylines.size()-1); - int i = 0; - for (Polylines::iterator it = THIS->polylines.begin(); it != THIS->polylines.end(); ++it) { - av_store(av, i++, to_SV_pureperl(&*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -void -PolylineCollection::append(...) - CODE: - for (unsigned int i = 1; i < items; i++) { - Polyline polyline; - from_SV_check(ST(i), &polyline); - THIS->polylines.push_back(polyline); - } - -%} -}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index bffdd933fb..97cbdafe32 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -3,27 +3,6 @@ %{ #include #include "libslic3r/Print.hpp" -#include "libslic3r/PlaceholderParser.hpp" -%} - -%package{Slic3r::Print::State}; -%{ - -IV -_constant() - ALIAS: - STEP_SLICE = posSlice - STEP_PERIMETERS = posPerimeters - STEP_PREPARE_INFILL = posPrepareInfill - STEP_INFILL = posInfill - STEP_SUPPORTMATERIAL = posSupportMaterial - STEP_SKIRTBRIM = psSkirtBrim - STEP_WIPE_TOWER = psWipeTower - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - %} %name{Slic3r::Print::Region} class PrintRegion { @@ -45,12 +24,6 @@ _constant() size_t layer_count(); Ref get_layer(int idx); - size_t support_layer_count(); - Ref get_support_layer(int idx); - - bool step_done(PrintObjectStep step) - %code%{ RETVAL = THIS->is_step_done(step); %}; - void slice(); }; @@ -62,16 +35,10 @@ _constant() %code%{ RETVAL = const_cast(&THIS->model()); %}; Ref config() %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; - Ref placeholder_parser() - %code%{ RETVAL = const_cast(&THIS->placeholder_parser()); %}; Ref skirt() %code%{ RETVAL = const_cast(&THIS->skirt()); %}; Ref brim() %code%{ RETVAL = const_cast(&THIS->brim()); %}; -// std::string estimated_normal_print_time() -// %code%{ RETVAL = THIS->print_statistics().estimated_normal_print_time; %}; -// std::string estimated_silent_print_time() -// %code%{ RETVAL = THIS->print_statistics().estimated_silent_print_time; %}; double total_used_filament() %code%{ RETVAL = THIS->print_statistics().total_used_filament; %}; double total_extruded_volume() @@ -96,25 +63,6 @@ _constant() PrintRegionPtrs* regions() %code%{ RETVAL = const_cast(&THIS->print_regions_mutable()); %}; - bool step_done(PrintStep step) - %code%{ RETVAL = THIS->is_step_done(step); %}; - bool object_step_done(PrintObjectStep step) - %code%{ RETVAL = THIS->is_step_done(step); %}; - - SV* filament_stats() - %code%{ - HV* hv = newHV(); - for (std::map::const_iterator it = THIS->print_statistics().filament_stats.begin(); it != THIS->print_statistics().filament_stats.end(); ++it) { - // stringify extruder_id - std::ostringstream ss; - ss << it->first; - std::string str = ss.str(); - - (void)hv_store( hv, str.c_str(), str.length(), newSViv(it->second), 0 ); - RETVAL = newRV_noinc((SV*)hv); - } - %}; - bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object); std::string output_filepath(std::string path = "") %code%{ @@ -138,7 +86,6 @@ _constant() mat.second->config.touch(); RETVAL = THIS->apply(*model, *config); %}; - bool has_infinite_skirt(); std::vector extruders() const; int validate() %code%{ std::string err = THIS->validate(); @@ -147,10 +94,7 @@ _constant() RETVAL = 1; %}; - void set_callback_event(int evt) %code%{ - %}; void set_status_silent(); - void set_status(int percent, const char *message); void process() %code%{ try { diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp index 49d988333c..3fffea9ab8 100644 --- a/xs/xsp/Surface.xsp +++ b/xs/xsp/Surface.xsp @@ -3,7 +3,6 @@ %{ #include #include "libslic3r/Surface.hpp" -#include "libslic3r/ClipperUtils.hpp" %} %name{Slic3r::Surface} class Surface { @@ -82,16 +81,6 @@ Surface::polygons() OUTPUT: RETVAL -Surfaces -Surface::offset(delta, joinType = ClipperLib::jtMiter, miterLimit = 3) - const float delta - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - surfaces_append(RETVAL, offset_ex(THIS->expolygon, delta, joinType, miterLimit), *THIS); - OUTPUT: - RETVAL - %} }; diff --git a/xs/xsp/SurfaceCollection.xsp b/xs/xsp/SurfaceCollection.xsp index 19cf3f8286..0d31c5ae3b 100644 --- a/xs/xsp/SurfaceCollection.xsp +++ b/xs/xsp/SurfaceCollection.xsp @@ -42,20 +42,6 @@ SurfaceCollection::filter_by_type(surface_type) OUTPUT: RETVAL -void -SurfaceCollection::replace(index, surface) - int index - Surface* surface - CODE: - THIS->surfaces[index] = *surface; - -void -SurfaceCollection::set_surface_type(index, surface_type) - int index - SurfaceType surface_type; - CODE: - THIS->surfaces[index].surface_type = surface_type; - SV* SurfaceCollection::group() CODE: diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index a9dbe654d3..5dc0df7465 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -28,7 +28,6 @@ void merge(TriangleMesh* mesh) %code{% THIS->merge(*mesh); %}; Clone convex_hull(); - Clone bounding_box(); Clone center() %code{% RETVAL = THIS->bounding_box().center(); %}; int facets_count(); @@ -62,22 +61,6 @@ TriangleMesh::ReadFromPerl(vertices, facets) } *THIS = TriangleMesh(std::move(out_vertices), std::move(out_indices)); -SV* -TriangleMesh::stats() - CODE: - HV* hv = newHV(); - (void)hv_stores( hv, "number_of_facets", newSViv(THIS->facets_count()) ); - (void)hv_stores( hv, "number_of_parts", newSViv(THIS->stats().number_of_parts) ); - (void)hv_stores( hv, "volume", newSVnv(THIS->stats().volume) ); - (void)hv_stores( hv, "degenerate_facets", newSViv(THIS->stats().repaired_errors.degenerate_facets) ); - (void)hv_stores( hv, "edges_fixed", newSViv(THIS->stats().repaired_errors.edges_fixed) ); - (void)hv_stores( hv, "facets_removed", newSViv(THIS->stats().repaired_errors.facets_removed) ); - (void)hv_stores( hv, "facets_reversed", newSViv(THIS->stats().repaired_errors.facets_reversed) ); - (void)hv_stores( hv, "backwards_edges", newSViv(THIS->stats().repaired_errors.backwards_edges) ); - RETVAL = (SV*)newRV_noinc((SV*)hv); - OUTPUT: - RETVAL - SV* TriangleMesh::vertices() CODE: @@ -128,78 +111,5 @@ TriangleMesh::size() OUTPUT: RETVAL -SV* -TriangleMesh::slice(z) - std::vector z - CODE: - // convert doubles to floats - std::vector z_f = cast(z); - - std::vector layers = slice_mesh_ex(THIS->its, z_f, 0.049f); - - AV* layers_av = newAV(); - size_t len = layers.size(); - if (len > 0) av_extend(layers_av, len-1); - for (unsigned int i = 0; i < layers.size(); i++) { - AV* expolygons_av = newAV(); - len = layers[i].size(); - if (len > 0) av_extend(expolygons_av, len-1); - unsigned int j = 0; - for (ExPolygons::iterator it = layers[i].begin(); it != layers[i].end(); ++it) { - av_store(expolygons_av, j++, perl_to_SV_clone_ref(*it)); - } - av_store(layers_av, i, newRV_noinc((SV*)expolygons_av)); - } - RETVAL = (SV*)newRV_noinc((SV*)layers_av); - OUTPUT: - RETVAL - -void -TriangleMesh::cut(z, upper_mesh, lower_mesh) - float z; - TriangleMesh* upper_mesh; - TriangleMesh* lower_mesh; - CODE: - indexed_triangle_set upper, lower; - cut_mesh(THIS->its, z, upper_mesh ? &upper : nullptr, lower_mesh ? &lower : nullptr); - if (upper_mesh) - *upper_mesh = TriangleMesh(upper); - if (lower_mesh) - *lower_mesh = TriangleMesh(lower); - -std::vector -TriangleMesh::bb3() - CODE: - RETVAL.push_back(THIS->stats().min(0)); - RETVAL.push_back(THIS->stats().min(1)); - RETVAL.push_back(THIS->stats().max(0)); - RETVAL.push_back(THIS->stats().max(1)); - RETVAL.push_back(THIS->stats().min(2)); - RETVAL.push_back(THIS->stats().max(2)); - OUTPUT: - RETVAL - - -Clone -cube(double x, double y, double z) - CODE: - RETVAL = make_cube(x,y,z); - OUTPUT: - RETVAL - -Clone -cylinder(double r, double h) - CODE: - RETVAL = make_cylinder(r, h); - OUTPUT: - RETVAL - -Clone -sphere(double rho) - CODE: - RETVAL = make_sphere(rho); - OUTPUT: - RETVAL - %} }; diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index 68ea282bc0..66a35366bd 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -35,129 +35,4 @@ set_logging_level(level) CODE: Slic3r::set_logging_level(level); -void -trace(level, message) - unsigned int level; - char *message; - CODE: - Slic3r::trace(level, message); - -void -disable_multi_threading() - CODE: - Slic3r::disable_multi_threading(); - -void -set_var_dir(dir) - char *dir; - CODE: - Slic3r::set_var_dir(dir); - -void -set_local_dir(dir) - char *dir; - CODE: - Slic3r::set_local_dir(dir); - -char* -var_dir() - CODE: - RETVAL = const_cast(Slic3r::var_dir().c_str()); - OUTPUT: RETVAL - -void -set_resources_dir(dir) - char *dir; - CODE: - Slic3r::set_resources_dir(dir); - -char* -resources_dir() - CODE: - RETVAL = const_cast(Slic3r::resources_dir().c_str()); - OUTPUT: RETVAL - -std::string -var(file_name) - const char *file_name; - CODE: - RETVAL = Slic3r::var(file_name); - OUTPUT: RETVAL - -void -set_data_dir(dir) - char *dir; - CODE: - Slic3r::set_data_dir(dir); - -char* -data_dir() - CODE: - RETVAL = const_cast(Slic3r::data_dir().c_str()); - OUTPUT: RETVAL - -local_encoded_string -encode_path(src) - const char *src; - CODE: - RETVAL = Slic3r::encode_path(src); - OUTPUT: RETVAL - -std::string -decode_path(src) - const char *src; - CODE: - RETVAL = Slic3r::decode_path(src); - OUTPUT: RETVAL - -std::string -normalize_utf8_nfc(src) - const char *src; - CODE: - RETVAL = Slic3r::normalize_utf8_nfc(src); - OUTPUT: RETVAL - -std::string -path_to_filename(src) - const char *src; - CODE: - RETVAL = Slic3r::PerlUtils::path_to_filename(src); - OUTPUT: RETVAL - -local_encoded_string -path_to_filename_raw(src) - const char *src; - CODE: - RETVAL = Slic3r::PerlUtils::path_to_filename(src); - OUTPUT: RETVAL - -std::string -path_to_stem(src) - const char *src; - CODE: - RETVAL = Slic3r::PerlUtils::path_to_stem(src); - OUTPUT: RETVAL - -std::string -path_to_extension(src) - const char *src; - CODE: - RETVAL = Slic3r::PerlUtils::path_to_extension(src); - OUTPUT: RETVAL - -std::string -path_to_parent_path(src) - const char *src; - CODE: - RETVAL = Slic3r::PerlUtils::path_to_parent_path(src); - OUTPUT: RETVAL - -void -xspp_test_croak_hangs_on_strawberry() - CODE: - try { - throw 1; - } catch (...) { - croak("xspp_test_croak_hangs_on_strawberry: exception catched\n"); - } -%} \ No newline at end of file +%} diff --git a/xs/xsp/my.map b/xs/xsp/my.map index ca26750dc5..42ca74292c 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -1,7 +1,6 @@ coordf_t T_NV std::string T_STD_STRING -local_encoded_string T_STD_STRING_LOCAL_ENCODING t_config_option_key T_STD_STRING t_model_material_id T_STD_STRING @@ -15,21 +14,10 @@ std::vector T_STD_VECTOR_UINT std::vector T_STD_VECTOR_DOUBLE -t_layer_height_ranges T_LAYER_HEIGHT_RANGES - - BoundingBox* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -BoundingBoxf* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -BoundingBoxf3* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - DynamicPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -37,12 +25,6 @@ Clone O_OBJECT_SLIC3R_T StaticPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -PrintObjectConfig* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -PrintRegionConfig* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - GCodeConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T @@ -52,8 +34,6 @@ Ref O_OBJECT_SLIC3R_T FullPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -ZTable* O_OBJECT - TriangleMesh* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -78,18 +58,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 -PolylineCollection* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - Polygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -98,18 +70,10 @@ ExPolygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -ExPolygonCollection* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - ExtrusionEntityCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -ExtrusionMultiPath* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - ExtrusionPath* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -118,21 +82,6 @@ ExtrusionLoop* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -ExtrusionSimulator* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Filler* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Flow* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -PrintState* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - Surface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -176,43 +125,10 @@ Ref O_OBJECT_SLIC3R_T Layer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -SupportLayer* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -PlaceholderParser* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -CoolingBuffer* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -GCode* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -BridgeDetector* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -PerimeterGenerator* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -PrintObjectSupportMaterial* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV -ExtrusionSimulationType T_UV -FlowRole T_UV -PrintStep T_UV -PrintObjectStep T_UV SurfaceType T_UV -Slic3r::ClipperLib::JoinType T_UV -Slic3r::ClipperLib::PolyFillType T_UV # we return these types whenever we want the items to be cloned Points T_ARRAYREF @@ -234,7 +150,6 @@ ModelInstancePtrs* T_PTR_ARRAYREF_PTR PrintRegionPtrs* T_PTR_ARRAYREF_PTR PrintObjectPtrs* T_PTR_ARRAYREF_PTR LayerPtrs* T_PTR_ARRAYREF_PTR -SupportLayerPtrs* T_PTR_ARRAYREF_PTR # we return these types whenever we want the items to be returned # by reference and not marked ::Ref because they're newly allocated @@ -252,14 +167,6 @@ T_STD_STRING $var = std::string(c, len); } -INPUT -T_STD_STRING_LOCAL_ENCODING - { - size_t len; - const char * c = SvPV($arg, len); - $var = std::string(c, len); - } - T_STD_VECTOR_STD_STRING if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); @@ -367,61 +274,11 @@ T_ARRAYREF ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); -T_LAYER_HEIGHT_RANGES - { - if (!SvROK($arg) || SvTYPE(SvRV($arg)) != SVt_PVAV) { - Perl_croak(aTHX_ \"%s: %s is not an array reference\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, - \"$var\"); - } - - AV* av = (AV*)SvRV($arg); - const unsigned int len = av_len(av)+1; - t_layer_height_ranges tmp_ranges; - for (unsigned int i = 0; i < len; i++) { - SV* elem = *av_fetch(av, i, 0); - if (!SvROK(elem) || SvTYPE(SvRV(elem)) != SVt_PVAV) { - Perl_croak( - aTHX_ \"%s: %s contains something that is not an array reference\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, - \"$var\"); - } - - AV* elemAV = (AV*)SvRV(elem); - if (av_len(elemAV) + 1 != 3) { - Perl_croak( - aTHX_ \"%s: %s contains an array that isn't 3 elements long\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, - \"$var\"); - } - - coordf_t vals[3]; - for (unsigned int j = 0; j < 3; ++j) { - SV *elem_elem = *av_fetch(elemAV, j, 0); - if (!looks_like_number(elem_elem)) { - Perl_croak( - aTHX_ \"%s: layer ranges and heights must be numbers\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}); - } - - vals[j] = SvNV(elem_elem); - } - - tmp_ranges[t_layer_height_range(vals[0], vals[1])] = vals[2]; - } - - $var = tmp_ranges; - } - - OUTPUT T_STD_STRING $arg = newSVpvn_utf8( $var.c_str(), $var.length(), true ); -T_STD_STRING_LOCAL_ENCODING - $arg = newSVpvn( $var.c_str(), $var.length() ); - T_STD_VECTOR_STD_STRING AV* av = newAV(); $arg = newRV_noinc((SV*)av); @@ -525,26 +382,3 @@ T_PTR_ARRAYREF av_store(av, i++, to_SV(*it)); } -T_LAYER_HEIGHT_RANGES - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var.size(); - if (len > 0) av_extend(av, len-1); - // map is sorted, so we can just copy it in order - int i = 0; - for (${type}::iterator it = $var.begin(); it != $var.end(); ++it) { - const coordf_t range_values[] = { - it->first.first, // key's first = minz - it->first.second, // key's second = maxz - it->second, // value = height - }; - - AV *rangeAV = newAV(); - av_extend(rangeAV, 2); - for (int j = 0; j < 3; ++j) { - av_store(rangeAV, j, newSVnv(range_values[j])); - } - - av_store(av, i++, (SV*)newRV_noinc((SV*)rangeAV)); - } diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index f9e61c6a03..bf1e8e2acc 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -12,7 +12,6 @@ %typemap{std::vector}; %typemap{std::vector*}; %typemap{std::vector}; -%typemap{t_layer_height_ranges}; %typemap{void*}; %typemap{SV*}; %typemap{AV*}; @@ -31,21 +30,11 @@ %typemap{BoundingBox*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{BoundingBoxf*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{BoundingBoxf3*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{DynamicPrintConfig*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{StaticPrintConfig*}; %typemap{Ref}{simple}; -%typemap{PrintObjectConfig*}; -%typemap{Ref}{simple}; -%typemap{PrintRegionConfig*}; -%typemap{Ref}{simple}; %typemap{GCodeConfig*}; %typemap{Ref}{simple}; %typemap{PrintConfig*}; @@ -55,21 +44,9 @@ %typemap{ExPolygon*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{ExPolygonCollection*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{Filler*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{Flow*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{Line*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{Linef3*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{Polyline*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -79,33 +56,18 @@ %typemap{ExtrusionEntityCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{ExtrusionMultiPath*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{ExtrusionPath*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionLoop*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{ExtrusionSimulator*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{TriangleMesh*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{PolylineCollection*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{BridgeDetector*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{SurfaceCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{PerimeterGenerator*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{Surface*}; %typemap{Ref}{simple}; @@ -130,29 +92,6 @@ %typemap{Layer*}; %typemap{Ref}{simple}; -%typemap{SupportLayer*}; -%typemap{Ref}{simple}; - -%typemap{PrintObjectSupportMaterial*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{PlaceholderParser*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{CoolingBuffer*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{GCode*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -//%typemap{GCodePreviewData*}; -//%typemap{Ref}{simple}; -//%typemap{Clone}{simple}; - %typemap{Points}; %typemap{Pointfs}; %typemap{Lines}; @@ -187,13 +126,10 @@ %typemap{ModelInstancePtrs*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{PresetHints*}; -%typemap{Ref}{simple}; %typemap{PrintRegionPtrs*}; %typemap{PrintObjectPtrs*}; %typemap{LayerPtrs*}; -%typemap{SupportLayerPtrs*}; %typemap{Axis}{parsed}{ %cpp_type{Axis}; @@ -219,27 +155,3 @@ $CVar = (ExtrusionRole)SvUV($PerlVar); %}; }; -%typemap{ExtrusionSimulationType}{parsed}{ - %cpp_type{ExtrusionSimulationType}; - %precall_code{% - $CVar = (ExtrusionSimulationType)SvUV($PerlVar); - %}; -}; -%typemap{FlowRole}{parsed}{ - %cpp_type{FlowRole}; - %precall_code{% - $CVar = (FlowRole)SvUV($PerlVar); - %}; -}; -%typemap{PrintStep}{parsed}{ - %cpp_type{PrintStep}; - %precall_code{% - $CVar = (PrintStep)SvUV($PerlVar); - %}; -}; -%typemap{PrintObjectStep}{parsed}{ - %cpp_type{PrintObjectStep}; - %precall_code{% - $CVar = (PrintObjectStep)SvUV($PerlVar); - %}; -};