Merge branch 'master' into fs_emboss
# Conflicts: # src/slic3r/GUI/GLCanvas3D.cpp
This commit is contained in:
commit
05354eda0c
146 changed files with 3246 additions and 5960 deletions
|
@ -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') ? '<br>' : "\n";
|
||||
# Heading
|
||||
$tags{h2start} = ($format eq 'html') ? '<b>' : '';
|
||||
$tags{h2end} = ($format eq 'html') ? '</b>' : '';
|
||||
# Bold font
|
||||
$tags{bstart} = ($format eq 'html') ? '<b>' : '';
|
||||
$tags{bend} = ($format eq 'html') ? '</b>' : '';
|
||||
# Verbatim
|
||||
$tags{vstart} = ($format eq 'html') ? '<pre>' : '';
|
||||
$tags{vend} = ($format eq 'html') ? '</pre>' : '';
|
||||
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. <br />' .
|
||||
'Copyright © 2011-2016 Alessandro Ranellucci. <br />' .
|
||||
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
|
||||
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
|
||||
'<br /><br /><br />' .
|
||||
'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. <br />' .
|
||||
'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> 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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
1;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,8 +44,6 @@ set(SLIC3R_SOURCES
|
|||
enum_bitmask.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
ExPolygonCollection.cpp
|
||||
ExPolygonCollection.hpp
|
||||
Extruder.cpp
|
||||
Extruder.hpp
|
||||
ExtrusionEntity.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<ClipperLib::PolyTree>(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)
|
||||
|
|
|
@ -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 <clipper2.clipper.h>
|
||||
|
||||
#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);
|
||||
|
|
|
@ -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<int> 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<float> 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<double> 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
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include "libslic3r.h"
|
||||
#include "clonable_ptr.hpp"
|
||||
|
@ -1949,6 +1950,11 @@ public:
|
|||
throw BadOptionTypeException("Conversion to a wrong type");
|
||||
return static_cast<TYPE*>(opt);
|
||||
}
|
||||
|
||||
template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
|
||||
{ return dynamic_cast<T*>(this->optptr(opt_key, create)); }
|
||||
template<class T> const T* opt(const t_config_option_key &opt_key) const
|
||||
{ return dynamic_cast<const T*>(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<int> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<int> 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<float> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<float> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const char *opt_key, const std::initializer_list<double> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<double> 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<int> values);
|
||||
static std::string format(std::initializer_list<float> values);
|
||||
static std::string format(std::initializer_list<double> values);
|
||||
};
|
||||
// May throw BadOptionTypeException() if the operation fails.
|
||||
void set_deserialize(std::initializer_list<SetDeserializeItem> 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<ConfigOptionString>(opt_key, create)->value; }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<ConfigBase*>(this)->opt_string(opt_key); }
|
||||
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<ConfigBase*>(this)->opt_string(opt_key, idx); }
|
||||
|
||||
double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
|
||||
const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
|
||||
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
|
||||
const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
|
||||
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
|
||||
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
|
||||
int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(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<typename ENUM>
|
||||
ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
|
||||
|
||||
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
|
||||
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(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<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
|
||||
{ return dynamic_cast<T*>(this->option(opt_key, create)); }
|
||||
template<class T> const T* opt(const t_config_option_key &opt_key) const
|
||||
{ return dynamic_cast<const T*>(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<ConfigOptionString>(opt_key, create)->value; }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
|
||||
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); }
|
||||
|
||||
double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
|
||||
const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
|
||||
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
|
||||
const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
|
||||
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
|
||||
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
|
||||
int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(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<typename ENUM>
|
||||
ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
|
||||
|
||||
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
|
||||
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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<Points> &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<Contour>& 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<Contour> m_contours;
|
||||
|
||||
// Referencing a contour and a line segment of m_contours.
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
typedef std::vector<ExPolygon> ExPolygons;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
|
||||
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).
|
||||
|
|
|
@ -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 <class T>
|
||||
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<Point>(const Point &item) const;
|
||||
template bool ExPolygonCollection::contains<Line>(const Line &item) const;
|
||||
template bool ExPolygonCollection::contains<Polyline>(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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ExPolygonCollection> 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 <class T> 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
|
|
@ -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)
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygonCollection;
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
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);
|
||||
|
|
|
@ -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<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
|
||||
|
|
|
@ -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<unsigned int>(-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
|
||||
|
|
|
@ -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<double>().norm());
|
||||
if (ab_len >= to_be_reduced) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -3063,7 +3063,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
|
|||
if (role == erSupportMaterial) {
|
||||
const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(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!
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -50,13 +50,6 @@ bool contains(const std::vector<T> &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;
|
||||
|
|
|
@ -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<class T> bool contains(const std::vector<T> &vector, const Point &point);
|
||||
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
|
||||
double rad2deg_dir(double angle);
|
||||
template<typename T> constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); }
|
||||
template<typename T> T angle_to_0_2PI(T angle)
|
||||
{
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
#ifndef slic3r_Geometry_ConvexHull_hpp_
|
||||
#define slic3r_Geometry_ConvexHull_hpp_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
|
||||
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.
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
#include "Flow.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
class Layer;
|
||||
using LayerPtrs = std::vector<Layer*>;
|
||||
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;
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#include "Polyline.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include <iostream>
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<SetDeserializeItem> items) {
|
||||
auto config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict(items);
|
||||
return config;
|
||||
}
|
||||
static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &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)
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
//#include "igl/random_points_on_mesh.h"
|
||||
//#include "igl/AABB.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#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())};
|
||||
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -426,8 +426,6 @@ struct PageMode: ConfigWizardPage
|
|||
PageMode(ConfigWizard *parent);
|
||||
|
||||
void serialize_mode(AppConfig *app_config) const;
|
||||
|
||||
virtual void on_activate();
|
||||
};
|
||||
|
||||
struct PageVendors: ConfigWizardPage
|
||||
|
|
|
@ -3941,9 +3941,8 @@ void GCodeViewer::render_legend(float& legend_height)
|
|||
PartialTimes items;
|
||||
|
||||
std::vector<CustomGCode::Item> 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<ColorRGBA> last_color(extruders_count);
|
||||
for (int i = 0; i < extruders_count; ++i) {
|
||||
std::vector<ColorRGBA> 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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<bool>(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<bool>(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<bool>(value) ? "1" : "";
|
||||
else if (opt_key == "notify_release") {
|
||||
if (opt_key == "notify_release") {
|
||||
int val_int = boost::any_cast<int>(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<bool>(value) ? "1" : "0";
|
||||
|
||||
}
|
||||
if (opt_key == "use_custom_toolbar_size") {
|
||||
m_icon_size_sizer->ShowItems(boost::any_cast<bool>(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<bool>(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<bool>(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<bool>(value) ? "1" : "";
|
||||
else
|
||||
m_values[opt_key] = boost::any_cast<bool>(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<bool>(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<bool>(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<wxButton*>(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<ConfigOptionsGroup> 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<wxWindow*>{ slider, label, val_label }) {
|
||||
for (wxWindow* win : std::vector<wxWindow*>{ 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<wxString> 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<wxString> 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);
|
||||
|
|
|
@ -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<ConfigOptionsGroup> 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<ConfigOptionsGroup> og);
|
||||
void create_icon_size_slider();
|
||||
void create_settings_mode_widget();
|
||||
void create_settings_text_color_widget();
|
||||
|
|
|
@ -490,8 +490,11 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector<int>& vector)
|
|||
auto* app_config = wxGetApp().app_config;
|
||||
auto hasget = [app_config](const std::string& name, std::vector<int>& 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;
|
||||
};
|
||||
|
|
|
@ -3481,6 +3481,28 @@ void Tab::activate_selected_page(std::function<void()> 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<void()>([this](){
|
||||
#ifdef WIN32
|
||||
wxCheckForInterrupt(m_treectrl);
|
||||
CheckForInterrupt(m_treectrl);
|
||||
if (m_page_switch_planned)
|
||||
throw UIBuildCanceled();
|
||||
#else // WIN32
|
||||
|
|
93
t/angles.t
93
t/angles.t
|
@ -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;
|
||||
}
|
|
@ -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__
|
137
t/bridges.t
137
t/bridges.t
|
@ -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__
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
||||
#==========================================================
|
|
@ -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__
|
||||
|
|
20
t/config.t
20
t/config.t
|
@ -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__
|
214
t/cooling.t
214
t/cooling.t
|
@ -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__
|
93
t/dynamic.t
93
t/dynamic.t
|
@ -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__
|
318
t/fill.t
318
t/fill.t
|
@ -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__
|
83
t/flow.t
83
t/flow.t
|
@ -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__
|
60
t/gaps.t
60
t/gaps.t
|
@ -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__
|
|
@ -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]);
|
||||
|
|
109
t/geometry.t
109
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)
|
||||
|
|
57
t/loops.t
57
t/loops.t
|
@ -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__
|
221
t/multi.t
221
t/multi.t
|
@ -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__
|
444
t/perimeters.t
444
t/perimeters.t
File diff suppressed because one or more lines are too long
121
t/polyclip.t
121
t/polyclip.t
|
@ -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';
|
||||
}
|
||||
|
||||
#==========================================================
|
152
t/slice.t
152
t/slice.t
|
@ -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__
|
272
t/support.t
272
t/support.t
|
@ -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__
|
185
t/thin.t
185
t/thin.t
|
@ -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__
|
|
@ -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)
|
||||
|
|
16
tests/fff_print/test_avoid_crossing_perimeters.cpp
Normal file
16
tests/fff_print/test_avoid_crossing_perimeters.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#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());
|
||||
}
|
||||
}
|
||||
}
|
133
tests/fff_print/test_bridges.cpp
Normal file
133
tests/fff_print/test_bridges.cpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <libslic3r/BridgeDetector.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
|
||||
#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<coord_t>(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<coord_t, double> 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<double>(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<coord_t>(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);
|
||||
}
|
||||
}
|
274
tests/fff_print/test_cooling.cpp
Normal file
274
tests/fff_print/test_cooling.cpp
Normal file
|
@ -0,0 +1,274 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#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<CoolingBuffer> make_cooling_buffer(
|
||||
GCode &gcode,
|
||||
const DynamicPrintConfig &config = DynamicPrintConfig{},
|
||||
const std::vector<unsigned int> &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<CoolingBuffer>(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<double>(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<ConfigOptionFloatOrPercent>("external_perimeter_speed")->value * 60;
|
||||
std::vector<double> layer_times;
|
||||
// z => 1
|
||||
std::map<coord_t, int> 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<coord_t>(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<coord_t>(self.z())];
|
||||
}
|
||||
});
|
||||
THEN("slowdown_below_layer_time is honored") {
|
||||
// Account for some inaccuracies.
|
||||
const double slowdown_below_layer_time = config.opt<ConfigOptionInts>("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, '';
|
||||
}
|
||||
}
|
||||
}
|
220
tests/fff_print/test_custom_gcode.cpp
Normal file
220
tests/fff_print/test_custom_gcode.cpp
Normal file
|
@ -0,0 +1,220 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#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
|
|
@ -26,6 +26,7 @@ const std::unordered_map<TestMesh, const char*, TestMeshHash> mesh_names {
|
|||
std::pair<TestMesh, const char*>(TestMesh::V, "V"),
|
||||
std::pair<TestMesh, const char*>(TestMesh::_40x10, "40x10"),
|
||||
std::pair<TestMesh, const char*>(TestMesh::cube_20x20x20, "cube_20x20x20"),
|
||||
std::pair<TestMesh, const char*>(TestMesh::cube_2x20x10, "cube_2x20x10"),
|
||||
std::pair<TestMesh, const char*>(TestMesh::sphere_50mm, "sphere_50mm"),
|
||||
std::pair<TestMesh, const char*>(TestMesh::bridge, "bridge"),
|
||||
std::pair<TestMesh, const char*>(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<float>());
|
||||
m.scale(scale.cast<float>());
|
||||
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");
|
||||
|
|
|
@ -21,6 +21,7 @@ enum class TestMesh {
|
|||
V,
|
||||
_40x10,
|
||||
cube_20x20x20,
|
||||
cube_2x20x10,
|
||||
sphere_50mm,
|
||||
bridge,
|
||||
bridge_with_hole,
|
||||
|
|
|
@ -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<Test> 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<const ExtrusionPath*>(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);
|
||||
}
|
||||
|
|
|
@ -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<Vec2d> points {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)};
|
||||
std::vector<Vec2d> 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<double>(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<Slic3r::Fill> 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<ConfigOptionFloatOrPercent>("infill_extrusion_width");
|
||||
REQUIRE(! opt_width->percent);
|
||||
Polygons convex_hull = expand(Geometry::convex_hull(infill_points), scaled<float>(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<double, double> 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<coord_t, Lines> 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<coord_t>(self.z())].emplace_back(self.xy_scaled(), line.new_XY_scaled(self));
|
||||
}
|
||||
});
|
||||
auto opt_width = config.opt<ConfigOptionFloatOrPercent>("infill_extrusion_width");
|
||||
REQUIRE(! opt_width->percent);
|
||||
auto grow_d = scaled<float>(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<coord_t>(0.2)]);
|
||||
Polygons layer1_infill = inflate_lines(infill[scaled<coord_t>(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)
|
||||
|
|
|
@ -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<double> 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<double>(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<double> 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<double>(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<double> 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<double>(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<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
|
||||
const double filament_dmr = config.opt<ConfigOptionFloats>("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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
60
tests/fff_print/test_gaps.cpp
Normal file
60
tests/fff_print/test_gaps.cpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
268
tests/fff_print/test_multi.cpp
Normal file
268
tests/fff_print/test_multi.cpp
Normal file
|
@ -0,0 +1,268 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#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<double>(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<coord_t>(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<coord_t>(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<float>(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<coord_t> 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<double>(line.new_F(self), solid_infill_speed * 60.) && (tool == 0 || tool == 1))
|
||||
(tool == 0 ? T0_shells : T1_shells).insert(scaled<coord_t>(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<coord_t> 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<coord_t>(self.z()));
|
||||
else if (tool == 1 && self.z() < 20)
|
||||
// Layers incorrectly extruded with T1 at the bottom object.
|
||||
T1_shells.insert(scaled<coord_t>(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());
|
||||
}
|
||||
}
|
||||
}
|
599
tests/fff_print/test_perimeters.cpp
Normal file
599
tests/fff_print/test_perimeters.cpp
Normal file
|
@ -0,0 +1,599 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#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<bool> ext_order;
|
||||
// expected number of internal contour loops
|
||||
int cinternal;
|
||||
// expected number of ccw loops
|
||||
int ccw;
|
||||
// expected ccw/cw order
|
||||
std::vector<bool> ccw_order;
|
||||
// expected nesting order
|
||||
std::vector<std::vector<int>> 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<const PrintRegionConfig*>(&config),
|
||||
static_cast<const PrintObjectConfig*>(&config),
|
||||
static_cast<const PrintConfig*>(&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<bool> 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<const ExtrusionLoop*>(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<const ExtrusionLoop*>(ee)->polygon().is_counter_clockwise(); });
|
||||
REQUIRE(ccw == data.ccw);
|
||||
}
|
||||
THEN("expected ccw/cw order") {
|
||||
std::vector<bool> ccw_order;
|
||||
for (auto *ee : loops.entities)
|
||||
ccw_order.emplace_back(dynamic_cast<const ExtrusionLoop*>(ee)->polygon().is_counter_clockwise());
|
||||
REQUIRE(ccw_order == data.ccw_order);
|
||||
}
|
||||
THEN("expected nesting order") {
|
||||
for (const std::vector<int> &nesting : data.nesting) {
|
||||
for (size_t i = 1; i < nesting.size(); ++ i)
|
||||
REQUIRE(dynamic_cast<const ExtrusionLoop*>(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<coord_t, int> 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<double>(self.f(), external_perimeter_speed)) {
|
||||
// reset counter for second object
|
||||
coord_t z = scaled<coord_t>(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<double>(), (current_loop.points[1] - current_loop.points.front()).cast<double>());
|
||||
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<coord_t, std::set<double>> 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<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
|
||||
const double filament_dmr = config.opt<ConfigOptionFloats>("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<double, std::set<double>> &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<coord_t, int> 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<double>(line.new_F(self), perimeter_speed)) {
|
||||
if (! in_loop) {
|
||||
coord_t z = scaled<coord_t>(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<const coord_t, int> &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<const ExtrusionEntityCollection*>(ee)->entities)
|
||||
append(acc, offset(dynamic_cast<const ExtrusionLoop*>(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<const ExtrusionPath*>(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<coord_t> 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<double>(line.new_F(self), bridge_speed))
|
||||
z_with_bridges.insert(scaled<coord_t>(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<double>().norm();
|
||||
// Seams shall be aligned up to 3mm.
|
||||
if (d > scaled<double>(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);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
191
tests/fff_print/test_thin_walls.cpp
Normal file
191
tests/fff_print/test_thin_walls.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#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<double>(40.), scaled<double>(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<double>(20.), scaled<double>(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<double>(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<double>(1.), scaled<double>(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<double>(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<coord_t>(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<double>(1.324888), scaled<double>(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<double>(), (pl.points[i + 1] - pl.points[i]).cast<double>());
|
||||
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<double>(20.), scaled<double>(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<double>(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<double>(20.), scaled<double>(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<double>(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<double>(4.), scaled<double>(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<double>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<e_ordering o = e_ordering::OFF, class P, class Tree>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#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<ConfigOptionInts>("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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>({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>({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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
tests/libslic3r/test_polyline.cpp
Normal file
28
tests/libslic3r/test_polyline.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#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} });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue