Merge branch 'master' into sender

Conflicts:
	Build.PL
	lib/Slic3r.pm
	xs/MANIFEST
	xs/src/libslic3r/PrintConfig.hpp
This commit is contained in:
Alessandro Ranellucci 2015-11-01 19:12:13 +01:00
commit 9b21ac877a
93 changed files with 3339 additions and 2050 deletions

View File

@ -8,7 +8,8 @@ use File::Spec;
my %prereqs = qw(
Devel::CheckLib 0
Encode::Locale 0
Encode 0
Encode::Locale 1.05
ExtUtils::MakeMaker 6.80
ExtUtils::ParseXS 3.22
File::Basename 0
@ -24,6 +25,7 @@ my %prereqs = qw(
IO::Scalar 0
threads 1.96
Time::HiRes 0
Unicode::Normalize 0
);
my %recommends = qw(
Class::XSAccessor 0
@ -119,7 +121,11 @@ EOF
foreach my $module (sort keys %modules) {
my $version = $modules{$module};
my @cmd = ($cpanm, @cpanm_args);
push @cmd, '--notest', if $module eq 'OpenGL'; # temporary workaround for upstream bug in test
# temporary workaround for upstream bug in test
push @cmd, '--notest'
if $module =~ /^(?:OpenGL|Math::PlanePath|Test::Harness)$/;
push @cmd, "$module~$version";
if ($module eq 'XML::SAX::ExpatXS' && $^O eq 'MSWin32') {
my $mingw = 'C:\dev\CitrusPerl\mingw64';

View File

@ -30,7 +30,7 @@ warn "Running Slic3r under Perl 5.16 is not supported nor recommended\n"
if $^V == v5.16;
use FindBin;
our $var = "$FindBin::Bin/var";
our $var = decode_path($FindBin::Bin) . "/var";
use Moo 1.003001;
@ -44,11 +44,9 @@ use Slic3r::Flow;
use Slic3r::Format::AMF;
use Slic3r::Format::OBJ;
use Slic3r::Format::STL;
use Slic3r::GCode;
use Slic3r::GCode::ArcFitting;
use Slic3r::GCode::CoolingBuffer;
use Slic3r::GCode::MotionPlanner;
use Slic3r::GCode::PlaceholderParser;
use Slic3r::GCode::PressureRegulator;
use Slic3r::GCode::Reader;
use Slic3r::GCode::SpiralVase;
@ -56,8 +54,6 @@ use Slic3r::GCode::VibrationLimit;
use Slic3r::Geometry qw(PI);
use Slic3r::Geometry::Clipper;
use Slic3r::Layer;
use Slic3r::Layer::PerimeterGenerator;
use Slic3r::Layer::Region;
use Slic3r::Line;
use Slic3r::Model;
use Slic3r::Point;
@ -71,15 +67,15 @@ use Slic3r::Print::SupportMaterial;
use Slic3r::Surface;
our $build = eval "use Slic3r::Build; 1";
use Thread::Semaphore;
use Encode::Locale 1.05;
use Encode;
use Unicode::Normalize;
use constant SCALING_FACTOR => 0.000001;
use constant RESOLUTION => 0.0125;
use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR;
use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI;
use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15;
use constant INFILL_OVERLAP_OVER_SPACING => 0.3;
use constant EXTERNAL_INFILL_MARGIN => 3;
use constant INSET_OVERLAP_TOLERANCE => 0.4;
# keep track of threads we created
my @my_threads = ();
@ -192,12 +188,17 @@ sub thread_cleanup {
*Slic3r::ExtrusionPath::DESTROY = sub {};
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
*Slic3r::Flow::DESTROY = sub {};
*Slic3r::GCode::DESTROY = sub {};
*Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {};
*Slic3r::GCode::OozePrevention::DESTROY = sub {};
*Slic3r::GCode::PlaceholderParser::DESTROY = sub {};
*Slic3r::GCode::Sender::DESTROY = sub {};
*Slic3r::GCode::Wipe::DESTROY = sub {};
*Slic3r::GCode::Writer::DESTROY = sub {};
*Slic3r::Geometry::BoundingBox::DESTROY = sub {};
*Slic3r::Geometry::BoundingBoxf::DESTROY = sub {};
*Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {};
*Slic3r::Layer::PerimeterGenerator::DESTROY = sub {};
*Slic3r::Line::DESTROY = sub {};
*Slic3r::Linef3::DESTROY = sub {};
*Slic3r::Model::DESTROY = sub {};
@ -262,14 +263,24 @@ sub resume_all_threads {
sub encode_path {
my ($path) = @_;
utf8::downgrade($path) if $^O eq 'MSWin32';
$path = Unicode::Normalize::NFC($path);
$path = Encode::encode(locale_fs => $path);
return $path;
}
sub decode_path {
my ($path) = @_;
utf8::upgrade($path) if $^O eq 'MSWin32';
$path = Encode::decode(locale_fs => $path)
unless utf8::is_utf8($path);
# The filesystem might force a normalization form (like HFS+ does) so
# if we rely on the filename being comparable after the open() + readdir()
# roundtrip (like when creating and then selecting a preset), we need to
# restore our normalization form.
$path = Unicode::Normalize::NFC($path);
return $path;
}

View File

@ -192,14 +192,6 @@ sub save {
__PACKAGE__->write_ini($file, $self->as_ini);
}
sub setenv {
my $self = shift;
foreach my $opt_key (@{$self->get_keys}) {
$ENV{"SLIC3R_" . uc $opt_key} = $self->serialize($opt_key);
}
}
# this method is idempotent by design and only applies to ::DynamicConfig or ::Full
# objects because it performs cross checks
sub validate {

View File

@ -6,7 +6,8 @@ use parent qw(Exporter);
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE);
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE
EXTR_ROLE_NONE);
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
1;

View File

@ -1,6 +1,7 @@
package Slic3r::Fill;
use Moo;
use List::Util qw(max);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Fill::3DHoneycomb;
use Slic3r::Fill::Base;
@ -20,6 +21,7 @@ has 'fillers' => (is => 'rw', default => sub { {} });
our %FillTypes = (
archimedeanchords => 'Slic3r::Fill::ArchimedeanChords',
rectilinear => 'Slic3r::Fill::Rectilinear',
grid => 'Slic3r::Fill::Grid',
flowsnake => 'Slic3r::Fill::Flowsnake',
octagramspiral => 'Slic3r::Fill::OctagramSpiral',
hilbertcurve => 'Slic3r::Fill::HilbertCurve',
@ -47,9 +49,9 @@ sub make_fill {
my $self = shift;
my ($layerm) = @_;
Slic3r::debugf "Filling layer %d:\n", $layerm->id;
Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id;
my $fill_density = $layerm->config->fill_density;
my $fill_density = $layerm->region->config->fill_density;
my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL);
my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL);
my $top_solid_infill_flow = $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL);
@ -73,13 +75,13 @@ sub make_fill {
for (my $i = 0; $i <= $#groups; $i++) {
# we can only merge solid non-bridge surfaces, so discard
# non-solid surfaces
if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) {
if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->layer->id == 0)) {
$is_solid[$i] = 1;
$fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP)
? $top_solid_infill_flow->width
: $solid_infill_flow->width;
$pattern[$i] = $groups[$i][0]->is_external
? $layerm->config->external_fill_pattern
? $layerm->region->config->external_fill_pattern
: 'rectilinear';
} else {
$is_solid[$i] = 0;
@ -143,8 +145,12 @@ sub make_fill {
# we are going to grow such regions by overlapping them with the void (if any)
# TODO: detect and investigate whether there could be narrow regions without
# any void neighbors
my $distance_between_surfaces = $infill_flow->scaled_spacing;
{
my $distance_between_surfaces = max(
$infill_flow->scaled_spacing,
$solid_infill_flow->scaled_spacing,
$top_solid_infill_flow->scaled_spacing,
);
my $collapsed = diff(
[ map @{$_->expolygon}, @surfaces ],
offset2([ map @{$_->expolygon}, @surfaces ], -$distance_between_surfaces/2, +$distance_between_surfaces/2),
@ -163,9 +169,6 @@ sub make_fill {
)};
}
# add spacing between surfaces
@surfaces = map @{$_->offset(-$distance_between_surfaces / 2)}, @surfaces;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg",
@ -177,19 +180,19 @@ sub make_fill {
my @fills = ();
SURFACE: foreach my $surface (@surfaces) {
next if $surface->surface_type == S_TYPE_INTERNALVOID;
my $filler = $layerm->config->fill_pattern;
my $filler = $layerm->region->config->fill_pattern;
my $density = $fill_density;
my $role = ($surface->surface_type == S_TYPE_TOP) ? FLOW_ROLE_TOP_SOLID_INFILL
: $surface->is_solid ? FLOW_ROLE_SOLID_INFILL
: FLOW_ROLE_INFILL;
my $is_bridge = $layerm->id > 0 && $surface->is_bridge;
my $is_bridge = $layerm->layer->id > 0 && $surface->is_bridge;
my $is_solid = $surface->is_solid;
if ($surface->is_solid) {
$density = 100;
$filler = 'rectilinear';
if ($surface->is_external && !$is_bridge) {
$filler = $layerm->config->external_fill_pattern;
$filler = $layerm->region->config->external_fill_pattern;
}
} else {
next SURFACE unless $density > 0;
@ -199,14 +202,14 @@ sub make_fill {
my $f = $self->filler($filler);
# calculate the actual flow we'll be using for this infill
my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness;
my $h = $surface->thickness == -1 ? $layerm->layer->height : $surface->thickness;
my $flow = $layerm->region->flow(
$role,
$h,
$is_bridge || $f->use_bridge_flow,
$layerm->id == 0,
$layerm->layer->id == 0,
-1,
$layerm->object,
$layerm->layer->object,
);
# calculate flow spacing for infill pattern generation
@ -218,11 +221,11 @@ sub make_fill {
# layer height
my $internal_flow = $layerm->region->flow(
FLOW_ROLE_INFILL,
$layerm->object->config->layer_height, # TODO: handle infill_every_layers?
$layerm->layer->object->config->layer_height, # TODO: handle infill_every_layers?
0, # no bridge
0, # no first layer
-1, # auto width
$layerm->object,
$layerm->layer->object,
);
$f->spacing($internal_flow->spacing);
$using_internal_flow = 1;
@ -230,15 +233,18 @@ sub make_fill {
$f->spacing($flow->spacing);
}
$f->layer_id($layerm->id);
$f->z($layerm->print_z);
$f->angle(deg2rad($layerm->config->fill_angle));
$f->layer_id($layerm->layer->id);
$f->z($layerm->layer->print_z);
$f->angle(deg2rad($layerm->region->config->fill_angle));
$f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
my @polylines = $f->fill_surface(
$surface,
# apply half spacing using this flow's own spacing and generate infill
my @polylines = map $f->fill_surface(
$_,
density => $density/100,
layer_height => $h,
);
), @{ $surface->offset(-scale($f->spacing)/2) };
next unless @polylines;
# calculate actual flow from spacing (which might have been adjusted by the infill

View File

@ -12,6 +12,8 @@ has '_line_oscillation' => (is => 'rw');
use Slic3r::Geometry qw(scale unscale scaled_epsilon);
use Slic3r::Geometry::Clipper qw(intersection_pl);
sub horizontal_lines { 0 }
sub fill_surface {
my $self = shift;
my ($surface, %params) = @_;
@ -44,9 +46,18 @@ sub fill_surface {
# generate the basic pattern
my $x_max = $bounding_box->x_max + scaled_epsilon;
my @vertical_lines = ();
my @lines = ();
for (my $x = $bounding_box->x_min; $x <= $x_max; $x += $self->_line_spacing) {
push @vertical_lines, $self->_line($#vertical_lines, $x, $bounding_box->y_min, $bounding_box->y_max);
push @lines, $self->_line($#lines, $x, $bounding_box->y_min, $bounding_box->y_max);
}
if ($self->horizontal_lines) {
my $y_max = $bounding_box->y_max + scaled_epsilon;
for (my $y = $bounding_box->y_min; $y <= $y_max; $y += $self->_line_spacing) {
push @lines, Slic3r::Polyline->new(
[$bounding_box->x_min, $y],
[$bounding_box->x_max, $y],
);
}
}
# clip paths against a slightly larger expolygon, so that the first and last paths
@ -54,7 +65,7 @@ sub fill_surface {
# the minimum offset for preventing edge lines from being clipped is scaled_epsilon;
# however we use a larger offset to support expolygons with slightly skewed sides and
# not perfectly straight
my @polylines = @{intersection_pl(\@vertical_lines, $expolygon->offset(+scale 0.02))};
my @polylines = @{intersection_pl(\@lines, $expolygon->offset(+scale 0.02))};
my $extra = $self->_min_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING;
foreach my $polyline (@polylines) {
@ -146,4 +157,12 @@ sub _can_connect {
&& $dist_Y <= $self->_diagonal_distance;
}
package Slic3r::Fill::Grid;
use Moo;
extends 'Slic3r::Fill::Rectilinear';
sub angles () { [0] }
sub horizontal_lines { 1 }
1;

View File

@ -36,7 +36,9 @@ sub write_file {
printf $fh qq{<amf unit="millimeter">\n};
printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
for my $material_id (sort @{ $model->material_names }) {
next if $material_id eq '';
my $material = $model->get_material($material_id);
# note that material-id must never be 0 since it's reserved by the AMF spec
printf $fh qq{ <material id="%s">\n}, $material_id;
for (keys %{$material->attributes}) {
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};

View File

@ -1,649 +0,0 @@
package Slic3r::GCode;
use Moo;
use List::Util qw(min max first);
use Slic3r::ExtrusionLoop ':roles';
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(epsilon scale unscale PI X Y B);
use Slic3r::Geometry::Clipper qw(union_ex);
# Origin of print coordinates expressed in unscaled G-code coordinates.
# This affects the input arguments supplied to the extrude*() and travel_to()
# methods.
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf->new });
has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new });
has 'writer' => (is => 'ro', default => sub { Slic3r::GCode::Writer->new });
has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new });
has 'ooze_prevention' => (is => 'rw', default => sub { Slic3r::GCode::OozePrevention->new });
has 'wipe' => (is => 'rw', default => sub { Slic3r::GCode::Wipe->new });
has 'avoid_crossing_perimeters' => (is => 'rw', default => sub { Slic3r::GCode::AvoidCrossingPerimeters->new });
has 'enable_loop_clipping' => (is => 'rw', default => sub {1});
has 'enable_cooling_markers' => (is =>'rw', default => sub {0});
has 'layer_count' => (is => 'ro');
has 'layer_index' => (is => 'rw', default => sub {-1}); # just a counter
has 'layer' => (is => 'rw');
has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos
has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds
has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } );
sub apply_print_config {
my ($self, $print_config) = @_;
$self->writer->apply_print_config($print_config);
$self->config->apply_print_config($print_config);
}
sub set_extruders {
my ($self, $extruder_ids) = @_;
$self->writer->set_extruders($extruder_ids);
# enable wipe path generation if any extruder has wipe enabled
$self->wipe->enable(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids);
}
sub set_origin {
my ($self, $pointf) = @_;
# if origin increases (goes towards right), last_pos decreases because it goes towards left
my @translate = (
scale ($self->origin->x - $pointf->x),
scale ($self->origin->y - $pointf->y), #-
);
$self->last_pos->translate(@translate);
$self->wipe->path->translate(@translate) if $self->wipe->path;
$self->origin($pointf);
}
sub preamble {
my ($self) = @_;
my $gcode = $self->writer->preamble;
# Perform a *silent* move to z_offset: we need this to initialize the Z
# position of our writer object so that any initial lift taking place
# before the first layer change will raise the extruder from the correct
# initial Z instead of 0.
$self->writer->travel_to_z($self->config->z_offset, '');
return $gcode;
}
sub change_layer {
my ($self, $layer) = @_;
$self->layer($layer);
$self->layer_index($self->layer_index + 1);
$self->first_layer($layer->id == 0);
# avoid computing islands and overhangs if they're not needed
if ($self->config->avoid_crossing_perimeters) {
$self->avoid_crossing_perimeters->init_layer_mp(
union_ex([ map @$_, @{$layer->slices} ], 1),
);
}
my $gcode = "";
if (defined $self->layer_count) {
$gcode .= $self->writer->update_progress($self->layer_index, $self->layer_count);
}
my $z = $layer->print_z + $self->config->z_offset; # in unscaled coordinates
if ($self->config->get_at('retract_layer_change', $self->writer->extruder->id) && $self->writer->will_move_z($z)) {
$gcode .= $self->retract;
}
$gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer_index . ')');
# forget last wiping path as wiping after raising Z is pointless
$self->wipe->path(undef);
return $gcode;
}
sub extrude {
my $self = shift;
$_[0]->isa('Slic3r::ExtrusionLoop')
? $self->extrude_loop(@_)
: $self->extrude_path(@_);
}
sub extrude_loop {
my ($self, $loop, $description, $speed) = @_;
# make a copy; don't modify the orientation of the original loop object otherwise
# next copies (if any) would not detect the correct orientation
$loop = $loop->clone;
# extrude all loops ccw
my $was_clockwise = $loop->make_counter_clockwise;
# find the point of the loop that is closest to the current extruder position
# or randomize if requested
my $last_pos = $self->last_pos;
if ($self->config->spiral_vase) {
$loop->split_at($last_pos);
} elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') {
# simplify polygon in order to skip false positives in concave/convex detection
my $polygon = $loop->polygon;
my @simplified = @{$polygon->simplify(scale $self->config->get_at('nozzle_diameter', $self->writer->extruder->id)/2)};
# concave vertices have priority
my @candidates = map @{$_->concave_points(PI*4/3)}, @simplified;
# if no concave points were found, look for convex vertices
@candidates = map @{$_->convex_points(PI*2/3)}, @simplified if !@candidates;
# retrieve the last start position for this object
my $obj_ptr = 0;
if (defined $self->layer) {
$obj_ptr = $self->layer->object->ptr;
if (defined $self->_seam_position->{$obj_ptr}) {
$last_pos = $self->_seam_position->{$obj_ptr};
}
}
my $point;
if ($self->config->seam_position eq 'nearest') {
@candidates = @$polygon if !@candidates;
$point = $last_pos->nearest_point(\@candidates);
if (!$loop->split_at_vertex($point)) {
# On 32-bit Linux, Clipper will change some point coordinates by 1 unit
# while performing simplify_polygons(), thus split_at_vertex() won't
# find them anymore.
$loop->split_at($point);
}
} elsif (@candidates) {
my @non_overhang = grep !$loop->has_overhang_point($_), @candidates;
@candidates = @non_overhang if @non_overhang;
$point = $last_pos->nearest_point(\@candidates);
if (!$loop->split_at_vertex($point)) {
$loop->split_at($point);
}
} else {
$point = $last_pos->projection_onto_polygon($polygon);
$loop->split_at($point);
}
$self->_seam_position->{$obj_ptr} = $point;
} elsif ($self->config->seam_position eq 'random') {
if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) {
my $polygon = $loop->polygon;
my $centroid = $polygon->centroid;
$last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y); #))
$last_pos->rotate(rand(2*PI), $centroid);
}
$loop->split_at($last_pos);
}
# clip the path to avoid the extruder to get exactly on the first point of the loop;
# if polyline was shorter than the clipping distance we'd get a null polyline, so
# we discard it in that case
my $clip_length = $self->enable_loop_clipping
? scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
: 0;
# get paths
my @paths = @{$loop->clip_end($clip_length)};
return '' if !@paths;
# apply the small perimeter speed
if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) {
$speed //= $self->config->get_abs_value('small_perimeter_speed');
}
# extrude along the path
my $gcode = join '', map $self->_extrude_path($_, $description, $speed), @paths;
# reset acceleration
$gcode .= $self->writer->set_acceleration($self->config->default_acceleration);
$self->wipe->path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path
# make a little move inwards before leaving loop
if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) {
my $last_path_polyline = $paths[-1]->polyline;
# detect angle between last and first segment
# the side depends on the original winding order of the polygon (left for contours, right for holes)
my @points = ($paths[0][1], $paths[-1][-2]);
@points = reverse @points if $was_clockwise;
my $angle = $paths[0]->first_point->ccw_angle(@points) / 3;
# turn left if contour, turn right if hole
$angle *= -1 if $was_clockwise;
# create the destination point along the first segment and rotate it
# we make sure we don't exceed the segment length because we don't know
# the rotation of the second segment so we might cross the object boundary
my $first_segment = Slic3r::Line->new(@{$paths[0]->polyline}[0,1]);
my $distance = min(scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)), $first_segment->length);
my $point = $first_segment->point_at($distance);
$point->rotate($angle, $first_segment->a);
# generate the travel move
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), "move inwards before travel");
}
return $gcode;
}
sub extrude_path {
my ($self, $path, $description, $speed) = @_;
my $gcode = $self->_extrude_path($path, $description, $speed);
# reset acceleration
$gcode .= $self->writer->set_acceleration($self->config->default_acceleration);
return $gcode;
}
sub _extrude_path {
my ($self, $path, $description, $speed) = @_;
$path->simplify(&Slic3r::SCALED_RESOLUTION);
# go to first point of extrusion path
my $gcode = "";
{
my $first_point = $path->first_point;
$gcode .= $self->travel_to($first_point, $path->role, "move to first $description point")
if !defined $self->last_pos || !$self->last_pos->coincides_with($first_point);
}
# compensate retraction
$gcode .= $self->unretract;
# adjust acceleration
{
my $acceleration;
if ($self->config->first_layer_acceleration && $self->first_layer) {
$acceleration = $self->config->first_layer_acceleration;
} elsif ($self->config->perimeter_acceleration && $path->is_perimeter) {
$acceleration = $self->config->perimeter_acceleration;
} elsif ($self->config->bridge_acceleration && $path->is_bridge) {
$acceleration = $self->config->bridge_acceleration;
} elsif ($self->config->infill_acceleration && $path->is_infill) {
$acceleration = $self->config->infill_acceleration;
} else {
$acceleration = $self->config->default_acceleration;
}
$gcode .= $self->writer->set_acceleration($acceleration);
}
# calculate extrusion length per distance unit
my $e_per_mm = $self->writer->extruder->e_per_mm3 * $path->mm3_per_mm;
$e_per_mm = 0 if !$self->writer->extrusion_axis;
# set speed
$speed //= -1;
if ($speed == -1) {
if ($path->role == EXTR_ROLE_PERIMETER) {
$speed = $self->config->get_abs_value('perimeter_speed');
} elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) {
$speed = $self->config->get_abs_value('external_perimeter_speed');
} elsif ($path->role == EXTR_ROLE_OVERHANG_PERIMETER || $path->role == EXTR_ROLE_BRIDGE) {
$speed = $self->config->get_abs_value('bridge_speed');
} elsif ($path->role == EXTR_ROLE_FILL) {
$speed = $self->config->get_abs_value('infill_speed');
} elsif ($path->role == EXTR_ROLE_SOLIDFILL) {
$speed = $self->config->get_abs_value('solid_infill_speed');
} elsif ($path->role == EXTR_ROLE_TOPSOLIDFILL) {
$speed = $self->config->get_abs_value('top_solid_infill_speed');
} elsif ($path->role == EXTR_ROLE_GAPFILL) {
$speed = $self->config->get_abs_value('gap_fill_speed');
} else {
die "Invalid speed";
}
}
my $F = $speed * 60; # convert mm/sec to mm/min
if ($self->first_layer) {
$F = $self->config->get_abs_value_over('first_layer_speed', $F/60) * 60;
}
# extrude arc or line
$gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge && $self->enable_cooling_markers;
my $path_length = unscale $path->length;
{
my $extruder_offset = $self->config->get_at('extruder_offset', $self->writer->extruder->id);
$gcode .= $path->gcode($self->writer->extruder, $e_per_mm, $F,
$self->origin->x - $extruder_offset->x,
$self->origin->y - $extruder_offset->y, #-
$self->writer->extrusion_axis,
$self->config->gcode_comments ? " ; $description" : "");
if ($self->wipe->enable) {
$self->wipe->path($path->polyline->clone);
$self->wipe->path->reverse;
}
}
$gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge && $self->enable_cooling_markers;
$self->last_pos($path->last_point);
if ($self->config->cooling) {
my $path_time = $path_length / $F * 60;
$self->elapsed_time($self->elapsed_time + $path_time);
}
return $gcode;
}
# This method accepts $point in print coordinates.
sub travel_to {
my ($self, $point, $role, $comment) = @_;
# Define the travel move as a line between current position and the taget point.
# This is expressed in print coordinates, so it will need to be translated by
# $self->origin in order to get G-code coordinates.
my $travel = Slic3r::Polyline->new($self->last_pos, $point);
# check whether a straight travel move would need retraction
my $needs_retraction = $self->needs_retraction($travel, $role);
# if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
# multi-hop travel path inside the configuration space
if ($needs_retraction
&& $self->config->avoid_crossing_perimeters
&& !$self->avoid_crossing_perimeters->disable_once) {
$travel = $self->avoid_crossing_perimeters->travel_to($self, $point);
# check again whether the new travel path still needs a retraction
$needs_retraction = $self->needs_retraction($travel, $role);
}
# Re-allow avoid_crossing_perimeters for the next travel moves
$self->avoid_crossing_perimeters->disable_once(0);
$self->avoid_crossing_perimeters->use_external_mp_once(0);
# generate G-code for the travel move
my $gcode = "";
$gcode .= $self->retract if $needs_retraction;
# use G1 because we rely on paths being straight (G0 may make round paths)
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($_->b), $comment)
for @{$travel->lines};
return $gcode;
}
sub needs_retraction {
my ($self, $travel, $role) = @_;
if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)) {
# skip retraction if the move is shorter than the configured threshold
return 0;
}
if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_polyline($travel)) {
# skip retraction if this is a travel move inside a support material island
return 0;
}
if ($self->config->only_retract_when_crossing_perimeters && defined $self->layer) {
if ($self->config->fill_density > 0
&& $self->layer->any_internal_region_slice_contains_polyline($travel)) {
# skip retraction if travel is contained in an internal slice *and*
# internal infill is enabled (so that stringing is entirely not visible)
return 0;
} elsif ($self->layer->any_bottom_region_slice_contains_polyline($travel)
&& defined $self->layer->upper_layer
&& $self->layer->upper_layer->slices->contains_polyline($travel)
&& ($self->config->bottom_solid_layers >= 2 || $self->config->fill_density > 0)) {
# skip retraction if travel is contained in an *infilled* bottom slice
# but only if it's also covered by an *infilled* upper layer's slice
# so that it's not visible from above (a bottom surface might not have an
# upper slice in case of a thin membrane)
return 0;
}
}
# retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
return 1;
}
sub retract {
my ($self, $toolchange) = @_;
return "" if !defined $self->writer->extruder;
my $gcode = "";
# wipe (if it's enabled for this extruder and we have a stored wipe path)
if ($self->config->get_at('wipe', $self->writer->extruder->id) && $self->wipe->path) {
$gcode .= $self->wipe->wipe($self, $toolchange);
}
# The parent class will decide whether we need to perform an actual retraction
# (the extruder might be already retracted fully or partially). We call these
# methods even if we performed wipe, since this will ensure the entire retraction
# length is honored in case wipe path was too short.p
$gcode .= $toolchange ? $self->writer->retract_for_toolchange : $self->writer->retract;
$gcode .= $self->writer->reset_e;
$gcode .= $self->writer->lift
if $self->writer->extruder->retract_length > 0 || $self->config->use_firmware_retraction;
return $gcode;
}
sub unretract {
my ($self) = @_;
my $gcode = "";
$gcode .= $self->writer->unlift;
$gcode .= $self->writer->unretract;
return $gcode;
}
# convert a model-space scaled point into G-code coordinates
sub point_to_gcode {
my ($self, $point) = @_;
my $extruder_offset = $self->config->get_at('extruder_offset', $self->writer->extruder->id);
return Slic3r::Pointf->new(
($point->x * &Slic3r::SCALING_FACTOR) + $self->origin->x - $extruder_offset->x,
($point->y * &Slic3r::SCALING_FACTOR) + $self->origin->y - $extruder_offset->y, #**
);
}
sub set_extruder {
my ($self, $extruder_id) = @_;
return "" if !$self->writer->need_toolchange($extruder_id);
# if we are running a single-extruder setup, just set the extruder and return nothing
if (!$self->writer->multiple_extruders) {
return $self->writer->toolchange($extruder_id);
}
# prepend retraction on the current extruder
my $gcode = $self->retract(1);
# append custom toolchange G-code
if (defined $self->writer->extruder && $self->config->toolchange_gcode) {
$gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->config->toolchange_gcode, {
previous_extruder => $self->writer->extruder->id,
next_extruder => $extruder_id,
});
}
# if ooze prevention is enabled, park current extruder in the nearest
# standby point and set it to the standby temperature
$gcode .= $self->ooze_prevention->pre_toolchange($self)
if $self->ooze_prevention->enable && defined $self->writer->extruder;
# append the toolchange command
$gcode .= $self->writer->toolchange($extruder_id);
# set the new extruder to the operating temperature
$gcode .= $self->ooze_prevention->post_toolchange($self)
if $self->ooze_prevention->enable;
return $gcode;
}
package Slic3r::GCode::OozePrevention;
use Moo;
use Slic3r::Geometry qw(scale);
has 'enable' => (is => 'rw', default => sub { 0 });
has 'standby_points' => (is => 'rw');
sub pre_toolchange {
my ($self, $gcodegen) = @_;
my $gcode = "";
# move to the nearest standby point
if (@{$self->standby_points}) {
# get current position in print coordinates
my $pos = Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1]);
my $standby_point = Slic3r::Pointf->new_unscale(@{$pos->nearest_point($self->standby_points)});
# We don't call $gcodegen->travel_to() because we don't need retraction (it was already
# triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
# of the destination point must not be transformed by origin nor current extruder offset.
$gcode .= $gcodegen->writer->travel_to_xy($standby_point, 'move to standby position');
}
if ($gcodegen->config->standby_temperature_delta != 0) {
my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0
? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id)
: $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id);
# we assume that heating is always slower than cooling, so no need to block
$gcode .= $gcodegen->writer->set_temperature($temp + $gcodegen->config->standby_temperature_delta, 0);
}
return $gcode;
}
sub post_toolchange {
my ($self, $gcodegen) = @_;
my $gcode = "";
if ($gcodegen->config->standby_temperature_delta != 0) {
my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0
? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id)
: $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id);
$gcode .= $gcodegen->writer->set_temperature($temp, 1);
}
return $gcode;
}
package Slic3r::GCode::Wipe;
use Moo;
use Slic3r::Geometry qw(scale);
has 'enable' => (is => 'rw', default => sub { 0 });
has 'path' => (is => 'rw');
sub wipe {
my ($self, $gcodegen, $toolchange) = @_;
my $gcode = "";
# Reduce feedrate a bit; travel speed is often too high to move on existing material.
# Too fast = ripping of existing material; too slow = short wipe path, thus more blob.
my $wipe_speed = $gcodegen->writer->config->get('travel_speed') * 0.8;
# get the retraction length
my $length = $toolchange
? $gcodegen->writer->extruder->retract_length_toolchange
: $gcodegen->writer->extruder->retract_length;
if ($length) {
# Calculate how long we need to travel in order to consume the required
# amount of retraction. In other words, how far do we move in XY at $wipe_speed
# for the time needed to consume retract_length at retract_speed?
my $wipe_dist = scale($length / $gcodegen->writer->extruder->retract_speed * $wipe_speed);
# Take the stored wipe path and replace first point with the current actual position
# (they might be different, for example, in case of loop clipping).
my $wipe_path = Slic3r::Polyline->new(
$gcodegen->last_pos,
@{$self->path}[1..$#{$self->path}],
);
#
$wipe_path->clip_end($wipe_path->length - $wipe_dist);
# subdivide the retraction in segments
my $retracted = 0;
foreach my $line (@{$wipe_path->lines}) {
my $segment_length = $line->length;
# Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
# due to rounding (TODO: test and/or better math for this)
my $dE = $length * ($segment_length / $wipe_dist) * 0.95;
$gcode .= $gcodegen->writer->set_speed($wipe_speed*60);
$gcode .= $gcodegen->writer->extrude_to_xy(
$gcodegen->point_to_gcode($line->b),
-$dE,
'wipe and retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''),
);
$retracted += $dE;
}
$gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted);
# prevent wiping again on same path
$self->path(undef);
}
return $gcode;
}
package Slic3r::GCode::AvoidCrossingPerimeters;
use Moo;
has '_external_mp' => (is => 'rw');
has '_layer_mp' => (is => 'rw');
has 'use_external_mp' => (is => 'rw', default => sub {0});
has 'use_external_mp_once' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move
# this flag disables avoid_crossing_perimeters just for the next travel move
# we enable it by default for the first travel move in print
has 'disable_once' => (is => 'rw', default => sub {1});
sub init_external_mp {
my ($self, $islands) = @_;
$self->_external_mp(Slic3r::MotionPlanner->new($islands));
}
sub init_layer_mp {
my ($self, $islands) = @_;
$self->_layer_mp(Slic3r::MotionPlanner->new($islands));
}
sub travel_to {
my ($self, $gcodegen, $point) = @_;
if ($self->use_external_mp || $self->use_external_mp_once) {
# get current origin set in $gcodegen
# (the one that will be used to translate the G-code coordinates by)
my $scaled_origin = Slic3r::Point->new_scale(@{$gcodegen->origin});
# represent last_pos in absolute G-code coordinates
my $last_pos = $gcodegen->last_pos->clone;
$last_pos->translate(@$scaled_origin);
# represent $point in absolute G-code coordinates
$point = $point->clone;
$point->translate(@$scaled_origin);
# calculate path
my $travel = $self->_external_mp->shortest_path($last_pos, $point);
# translate the path back into the shifted coordinate system that $gcodegen
# is currently using for writing coordinates
$travel->translate(@{$scaled_origin->negative});
return $travel;
} else {
return $self->_layer_mp->shortest_path($gcodegen->last_pos, $point);
}
}
1;

View File

@ -27,7 +27,7 @@ sub append {
$self->last_z->{$obj_id} = $print_z;
$self->gcode($self->gcode . $gcode);
$self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time);
$self->gcodegen->elapsed_time(0);
$self->gcodegen->set_elapsed_time(0);
return $return;
}

View File

@ -1,42 +0,0 @@
package Slic3r::GCode::PlaceholderParser;
use strict;
use warnings;
sub new {
# TODO: move this code to C++ constructor, remove this method
my ($class) = @_;
my $self = $class->_new;
$self->apply_env_variables;
return $self;
}
sub apply_env_variables {
my ($self) = @_;
$self->_single_set($_, $ENV{$_}) for grep /^SLIC3R_/, keys %ENV;
}
sub process {
my ($self, $string, $extra) = @_;
# extra variables have priority over the stored ones
if ($extra) {
my $regex = join '|', keys %$extra;
$string =~ s/\[($regex)\]/$extra->{$1}/eg;
}
{
my $regex = join '|', @{$self->_single_keys};
$string =~ s/\[($regex)\]/$self->_single_get("$1")/eg;
}
{
my $regex = join '|', @{$self->_multiple_keys};
$string =~ s/\[($regex)\]/$self->_multiple_get("$1")/egx;
# unhandled indices are populated using the first value
$string =~ s/\[($regex)_\d+\]/$self->_multiple_get("$1")/egx;
}
return $string;
}
1;

View File

@ -43,7 +43,7 @@ sub process {
my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY};
# Then calculate absolute flow rate (mm/sec of feedstock)
my $flow_rate = $rel_flow_rate * $args->{F} / 60;
my $flow_rate = $rel_flow_rate * $F / 60;
# And finally calculate advance by using the user-configured K factor.
my $new_advance = $self->config->pressure_advance * ($flow_rate**2);

View File

@ -3,8 +3,8 @@ use strict;
use warnings;
use utf8;
use Wx qw(:font :html :misc :dialog :sizer :systemsettings);
use Wx::Event qw(EVT_HTML_LINK_CLICKED);
use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id);
use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON);
use Wx::Print;
use Wx::Html;
use base 'Wx::Dialog';
@ -12,7 +12,7 @@ use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300], &Wx::wxCLOSE_BOX);
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 340], wxCAPTION);
$self->SetBackgroundColour(Wx::wxWHITE);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
@ -65,7 +65,18 @@ sub new {
$html->SetPage($text);
$vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20);
EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked);
my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE);
$self->SetEscapeId(wxID_CLOSE);
EVT_BUTTON($self, wxID_CLOSE, sub {
$self->EndModal(wxID_CLOSE);
$self->Close;
});
$vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
EVT_LEFT_DOWN($self, sub { $self->Close });
EVT_LEFT_DOWN($logo, sub { $self->Close });
return $self;
}

View File

@ -11,8 +11,8 @@ use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedi
use Wx::Event qw(EVT_CLOSE EVT_MENU);
use base 'Wx::Frame';
our $last_input_file;
our $last_output_file;
our $qs_last_input_file;
our $qs_last_output_file;
our $last_config;
sub new {
@ -197,7 +197,8 @@ sub _init_menubar {
$self->repair_stl;
}, undef, 'wrench.png');
$fileMenu->AppendSeparator();
$self->_append_menu_item($fileMenu, "Preferences…", 'Application preferences', sub {
# Cmd+, is standard on OS X - what about other operating systems?
$self->_append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub {
Slic3r::GUI::Preferences->new($self)->ShowModal;
}, wxID_PREFERENCES);
$fileMenu->AppendSeparator();
@ -321,19 +322,19 @@ sub quick_slice {
}
$input_file = Slic3r::decode_path($dialog->GetPaths);
$dialog->Destroy;
$last_input_file = $input_file unless $params{export_svg};
$qs_last_input_file = $input_file unless $params{export_svg};
} else {
if (!defined $last_input_file) {
if (!defined $qs_last_input_file) {
Wx::MessageDialog->new($self, "No previously sliced file.",
'Error', wxICON_ERROR | wxOK)->ShowModal();
return;
}
if (! -e $last_input_file) {
Wx::MessageDialog->new($self, "Previously sliced file ($last_input_file) not found.",
if (! -e $qs_last_input_file) {
Wx::MessageDialog->new($self, "Previously sliced file ($qs_last_input_file) not found.",
'File Not Found', wxICON_ERROR | wxOK)->ShowModal();
return;
}
$input_file = $last_input_file;
$input_file = $qs_last_input_file;
}
my $input_file_basename = basename($input_file);
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
@ -368,19 +369,19 @@ sub quick_slice {
# select output file
my $output_file;
if ($params{reslice}) {
$output_file = $last_output_file if defined $last_output_file;
$output_file = $qs_last_output_file if defined $qs_last_output_file;
} elsif ($params{save_as}) {
$output_file = $sprint->expanded_output_filepath;
$output_file =~ s/\.gcode$/.svg/i if $params{export_svg};
my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:',
wxTheApp->output_path(dirname($output_file)),
basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return;
}
$output_file = Slic3r::decode_path($dlg->GetPath);
$last_output_file = $output_file unless $params{export_svg};
$qs_last_output_file = $output_file unless $params{export_svg};
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file);
wxTheApp->save_settings;
$dlg->Destroy;

View File

@ -137,6 +137,7 @@ sub BUILD {
$self->wxWindow($field);
EVT_SPINCTRL($self->parent, $field, sub {
$self->tmp_value(undef);
$self->_on_change($self->option->opt_id);
});
EVT_TEXT($self->parent, $field, sub {
@ -147,11 +148,14 @@ sub BUILD {
# gets the old one, and on_kill_focus resets the control to the old value.
# As a workaround, we get the new value from $event->GetString and store
# here temporarily so that we can return it from $self->get_value
$self->tmp_value($event->GetString);
$self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/;
$self->_on_change($self->option->opt_id);
$self->tmp_value(undef);
# We don't reset tmp_value here because _on_change might put callbacks
# in the CallAfter queue, and we want the tmp value to be available from
# them as well.
});
EVT_KILL_FOCUS($field, sub {
$self->tmp_value(undef);
$self->_on_kill_focus($self->option->opt_id, @_);
});
}

View File

@ -337,7 +337,13 @@ sub new {
$text->SetFont($Slic3r::GUI::small_font);
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
$self->{preset_choosers}{$group} = [$choice];
EVT_COMBOBOX($choice, $choice, sub { $self->_on_select_preset($group, @_) });
# setup the listener
EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_;
wxTheApp->CallAfter(sub {
$self->_on_select_preset($group, $choice);
});
});
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0);
}
@ -431,7 +437,9 @@ sub _on_select_preset {
wxTheApp->save_settings;
return;
}
$self->{on_select_preset}->($group, $choice->GetSelection)
# call GetSelection() in scalar context as it's context-aware
$self->{on_select_preset}->($group, scalar $choice->GetSelection)
if $self->{on_select_preset};
# get new config and generate on_config_change() event for updating plater and other things
@ -445,10 +453,16 @@ sub GetFrame {
sub update_presets {
my $self = shift;
my ($group, $presets, $selected) = @_;
my ($group, $presets, $selected, $is_dirty) = @_;
foreach my $choice (@{ $self->{preset_choosers}{$group} }) {
my $sel = $choice->GetSelection;
my @choosers = @{ $self->{preset_choosers}{$group} };
foreach my $choice (@choosers) {
if ($group eq 'filament' && @choosers > 1) {
# if we have more than one filament chooser, keep our selection
# instead of importing the one from the tab
$selected = $choice->GetSelection;
$is_dirty = 0;
}
$choice->Clear;
foreach my $preset (@$presets) {
my $bitmap;
@ -471,15 +485,23 @@ sub update_presets {
}
$choice->AppendString($preset->name, $bitmap);
}
$choice->SetSelection($sel) if $sel <= $#$presets;
if ($selected <= $#$presets) {
if ($is_dirty) {
$choice->SetString($selected, $choice->GetString($selected) . " (modified)");
}
# call SetSelection() only after SetString() otherwise the new string
# won't be picked up as the visible string
$choice->SetSelection($selected);
}
}
$self->{preset_choosers}{$group}[0]->SetSelection($selected);
}
sub filament_presets {
my $self = shift;
return map $_->GetSelection, @{ $self->{preset_choosers}{filament} };
# force scalar context for GetSelection() as it's context-aware
return map scalar($_->GetSelection), @{ $self->{preset_choosers}{filament} };
}
sub add {
@ -1083,7 +1105,7 @@ sub export_gcode {
} else {
my $default_output_file = $self->{print}->expanded_output_filepath($main::opt{output});
my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)),
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return;
@ -1091,7 +1113,7 @@ sub export_gcode {
my $path = Slic3r::decode_path($dlg->GetPath);
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path);
wxTheApp->save_settings;
$self->{export_gcode_output_file} = $Slic3r::GUI::MainFrame::last_output_file = $path;
$self->{export_gcode_output_file} = $path;
$dlg->Destroy;
}
@ -1239,13 +1261,15 @@ sub send_gcode {
my $ua = LWP::UserAgent->new;
$ua->timeout(180);
my $path = Slic3r::encode_path($self->{send_gcode_file});
my $res = $ua->post(
"http://" . $self->{config}->octoprint_host . "/api/files/local",
Content_Type => 'form-data',
'X-Api-Key' => $self->{config}->octoprint_apikey,
Content => [
# OctoPrint doesn't like Windows paths
file => [ $self->{send_gcode_file}, basename($self->{send_gcode_file}) ],
# OctoPrint doesn't like Windows paths so we use basename()
# Also, since we need to read from filesystem we process it through encode_path()
file => [ $path, basename($path) ],
],
);
@ -1315,7 +1339,7 @@ sub _get_export_file {
$dlg->Destroy;
return undef;
}
$output_file = $Slic3r::GUI::MainFrame::last_output_file = Slic3r::decode_path($dlg->GetPath);
$output_file = Slic3r::decode_path($dlg->GetPath);
$dlg->Destroy;
}
return $output_file;
@ -1383,14 +1407,34 @@ sub on_extruders_change {
my $choices = $self->{preset_choosers}{filament};
while (@$choices < $num_extruders) {
# copy strings from first choice
my @presets = $choices->[0]->GetStrings;
push @$choices, Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
# initialize new choice
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
push @$choices, $choice;
# copy icons from first choice
$choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
# insert new choice into sizer
$self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0);
$self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
EVT_COMBOBOX($choices->[-1], $choices->[-1], sub { $self->_on_select_preset('filament', @_) });
my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
$choices->[-1]->SetSelection($i || 0);
$self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
# setup the listener
EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_;
wxTheApp->CallAfter(sub {
$self->_on_select_preset('filament', $choice);
});
});
# initialize selection
my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
$choice->SetSelection($i || 0);
}
# remove unused choices if any
while (@$choices > $num_extruders) {
$self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label
$self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice

View File

@ -164,6 +164,8 @@ sub new {
EVT_MOUSEWHEEL($self, sub {
my ($self, $e) = @_;
return if !$self->GetParent->enabled;
my $old_zoom = $self->_zoom;
# Calculate the zoom delta and apply it to the current zoom factor
@ -205,6 +207,8 @@ sub new {
sub mouse_event {
my ($self, $e) = @_;
return if !$self->GetParent->enabled;
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
if ($e->Entering && &Wx::wxMSW) {
# wxMSW needs focus in order to catch mouse wheel events
@ -262,8 +266,9 @@ sub set_z {
}
}
# reverse layers so that we draw the lowermost (i.e. current) on top
$self->z($z);
$self->layers([ @layers ]);
$self->layers([ reverse @layers ]);
$self->Refresh;
}

View File

@ -107,12 +107,17 @@ sub new {
$grid->SetCellValue($event->GetRow, $event->GetCol, $value);
# if there's no empty row, let's append one
for my $i (0 .. $grid->GetNumberRows-1) {
for my $i (0 .. $grid->GetNumberRows) {
if ($i == $grid->GetNumberRows) {
# if we're here then we found no empty row
$grid->AppendRows(1);
last;
}
if (!grep $grid->GetCellValue($i, $_), 0..2) {
return;
# exit loop if this row is empty
last;
}
}
$grid->AppendRows(1);
$self->{layers_changed} = 1;
});

View File

@ -65,7 +65,9 @@ sub new {
$self->{treectrl}->AddRoot("root");
$self->{pages} = [];
$self->{treectrl}->SetIndent(0);
$self->{disable_tree_sel_changed_event} = 0;
EVT_TREE_SEL_CHANGED($parent, $self->{treectrl}, sub {
return if $self->{disable_tree_sel_changed_event};
my $page = first { $_->{title} eq $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection) } @{$self->{pages}}
or return;
$_->Hide for @{$self->{pages}};
@ -106,6 +108,7 @@ sub new {
$self->{config} = Slic3r::Config->new;
$self->build;
$self->update_tree;
$self->_update;
if ($self->hidden_options) {
$self->{config}->apply(Slic3r::Config->new_from_defaults($self->hidden_options));
@ -177,8 +180,11 @@ sub _update {}
sub _on_presets_changed {
my $self = shift;
$self->{on_presets_changed}->($self->{presets}, $self->{presets_choice}->GetSelection)
if $self->{on_presets_changed};
$self->{on_presets_changed}->(
$self->{presets},
scalar($self->{presets_choice}->GetSelection),
$self->is_dirty,
) if $self->{on_presets_changed};
}
sub on_preset_loaded {}
@ -198,6 +204,8 @@ sub select_preset {
sub select_preset_by_name {
my ($self, $name) = @_;
$name = Unicode::Normalize::NFC($name);
$self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}});
}
@ -223,6 +231,10 @@ sub on_select_preset {
'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
if ($confirm->ShowModal == wxID_NO) {
$self->{presets_choice}->SetSelection($self->current_preset);
# trigger the on_presets_changed event so that we also restore the previous value
# in the plater selector
$self->_on_presets_changed;
return;
}
}
@ -282,7 +294,6 @@ sub add_options_page {
$page->Hide;
$self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5);
push @{$self->{pages}}, $page;
$self->update_tree;
return $page;
}
@ -303,12 +314,15 @@ sub update_tree {
foreach my $page (@{$self->{pages}}) {
my $itemId = $self->{treectrl}->AppendItem($rootItem, $page->{title}, $page->{iconID});
if ($page->{title} eq $selected) {
$self->{disable_tree_sel_changed_event} = 1;
$self->{treectrl}->SelectItem($itemId);
$self->{disable_tree_sel_changed_event} = 0;
$have_selection = 1;
}
}
if (!$have_selection) {
# this is triggered on first load, so we don't disable the sel change event
$self->{treectrl}->SelectItem($self->{treectrl}->GetFirstChild($rootItem));
}
}
@ -431,6 +445,7 @@ sub set_value {
package Slic3r::GUI::Tab::Print;
use base 'Slic3r::GUI::Tab';
use List::Util qw(first);
use Wx qw(:icon :dialog :id);
sub name { 'print' }
@ -449,6 +464,7 @@ sub build {
infill_every_layers infill_only_where_needed
solid_infill_every_layers fill_angle solid_infill_below_area
only_retract_when_crossing_perimeters infill_first
max_print_speed max_volumetric_speed
perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed
solid_infill_speed top_solid_infill_speed support_material_speed
support_material_interface_speed bridge_speed gap_fill_speed
@ -606,6 +622,11 @@ sub build {
$optgroup->append_single_option_line('first_layer_acceleration');
$optgroup->append_single_option_line('default_acceleration');
}
{
my $optgroup = $page->new_optgroup('Autospeed (advanced)');
$optgroup->append_single_option_line('max_print_speed');
$optgroup->append_single_option_line('max_volumetric_speed');
}
}
{
@ -738,6 +759,22 @@ sub _update {
}
}
if ($config->fill_density == 100
&& !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{external_fill_pattern}{values}}) {
my $dialog = Wx::MessageDialog->new($self,
"The " . $config->fill_pattern . " infill pattern is not supposed to work at 100% density.\n"
. "\nShall I switch to rectilinear fill pattern?",
'Infill', wxICON_WARNING | wxYES | wxNO);
my $new_conf = Slic3r::Config->new;
if ($dialog->ShowModal() == wxID_YES) {
$new_conf->set("fill_pattern", 1);
} else {
$new_conf->set("fill_density", 40);
}
$self->load_config($new_conf);
}
my $have_perimeters = $config->perimeters > 0;
$self->get_field($_)->toggle($have_perimeters)
for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first
@ -1016,8 +1053,10 @@ sub build {
$optgroup->on_change(sub {
my ($opt_id) = @_;
if ($opt_id eq 'extruders_count') {
wxTheApp->CallAfter(sub {
$self->_extruders_count_changed($optgroup->get_value('extruders_count'));
});
$self->update_dirty;
$self->_extruders_count_changed($optgroup->get_value('extruders_count'));
}
});
}
@ -1231,8 +1270,6 @@ sub _build_extruder_pages {
$optgroup->append_single_option_line($_, $extruder_idx)
for qw(retract_length_toolchange retract_restart_extra_toolchange);
}
$self->{extruder_pages}[$extruder_idx]{disabled} = 0;
}
# remove extra pages
@ -1325,7 +1362,7 @@ sub load_config_file {
}
package Slic3r::GUI::Tab::Page;
use Wx qw(:misc :panel :sizer);
use Wx qw(wxTheApp :misc :panel :sizer);
use base 'Wx::ScrolledWindow';
sub new {
@ -1353,8 +1390,11 @@ sub new_optgroup {
config => $self->GetParent->{config},
label_width => $params{label_width} // 200,
on_change => sub {
$self->GetParent->update_dirty;
$self->GetParent->_on_value_change(@_);
my ($opt_key, $value) = @_;
wxTheApp->CallAfter(sub {
$self->GetParent->update_dirty;
$self->GetParent->_on_value_change($opt_key, $value);
});
},
);
@ -1454,7 +1494,7 @@ sub config {
if ($self->default) {
return Slic3r::Config->new_from_defaults(@$keys);
} else {
if (!-e $self->file) {
if (!-e Slic3r::encode_path($self->file)) {
Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ").");
return undef;
}

View File

@ -41,24 +41,25 @@ sub make_perimeters {
for my $region_id (0..$#{$self->regions}) {
next if $done{$region_id};
my $layerm = $self->regions->[$region_id];
my $layerm = $self->get_region($region_id);
my $config = $layerm->region->config;
$done{$region_id} = 1;
# find compatible regions
my @layerms = ($layerm);
for my $i (($region_id+1)..$#{$self->regions}) {
my $config = $self->regions->[$i]->config;
my $layerm_config = $layerm->config;
my $other_layerm = $self->get_region($i);
my $other_config = $other_layerm->region->config;
if ($config->perimeter_extruder == $layerm_config->perimeter_extruder
&& $config->perimeters == $layerm_config->perimeters
&& $config->perimeter_speed == $layerm_config->perimeter_speed
&& $config->gap_fill_speed == $layerm_config->gap_fill_speed
&& $config->overhangs == $layerm_config->overhangs
&& $config->perimeter_extrusion_width == $layerm_config->perimeter_extrusion_width
&& $config->thin_walls == $layerm_config->thin_walls
&& $config->external_perimeters_first == $layerm_config->external_perimeters_first) {
push @layerms, $self->regions->[$i];
if ($config->perimeter_extruder == $other_config->perimeter_extruder
&& $config->perimeters == $other_config->perimeters
&& $config->perimeter_speed == $other_config->perimeter_speed
&& $config->gap_fill_speed == $other_config->gap_fill_speed
&& $config->overhangs == $other_config->overhangs
&& $config->perimeter_extrusion_width == $other_config->perimeter_extrusion_width
&& $config->thin_walls == $other_config->thin_walls
&& $config->external_perimeters_first == $other_config->external_perimeters_first) {
push @layerms, $other_layerm;
$done{$i} = 1;
}
}

View File

@ -1,532 +0,0 @@
package Slic3r::Layer::PerimeterGenerator;
use Moo;
use Slic3r::ExtrusionLoop ':roles';
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale chained_path);
use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset offset2
offset_ex offset2_ex intersection_ppl diff_ppl);
use Slic3r::Surface ':types';
has 'slices' => (is => 'ro', required => 1); # SurfaceCollection
has 'lower_slices' => (is => 'ro', required => 0);
has 'layer_height' => (is => 'ro', required => 1);
has 'layer_id' => (is => 'ro', required => 0, default => sub { -1 });
has 'perimeter_flow' => (is => 'ro', required => 1);
has 'ext_perimeter_flow' => (is => 'ro', required => 1);
has 'overhang_flow' => (is => 'ro', required => 1);
has 'solid_infill_flow' => (is => 'ro', required => 1);
has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new });
has 'object_config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new });
has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new });
has '_lower_slices_p' => (is => 'rw', default => sub { [] });
has '_holes_pt' => (is => 'rw');
has '_ext_mm3_per_mm' => (is => 'rw');
has '_mm3_per_mm' => (is => 'rw');
has '_mm3_per_mm_overhang' => (is => 'rw');
has '_thin_wall_polylines' => (is => 'rw', default => sub { [] });
# generated loops will be put here
has 'loops' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new });
# generated gap fills will be put here
has 'gap_fill' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new });
# generated fill surfaces will be put here
has 'fill_surfaces' => (is => 'ro', default => sub { Slic3r::Surface::Collection->new });
sub BUILDARGS {
my ($class, %args) = @_;
if (my $flow = delete $args{flow}) {
$args{perimeter_flow} //= $flow;
$args{ext_perimeter_flow} //= $flow;
$args{overhang_flow} //= $flow;
$args{solid_infill_flow} //= $flow;
}
return { %args };
}
sub process {
my ($self) = @_;
# other perimeters
$self->_mm3_per_mm($self->perimeter_flow->mm3_per_mm);
my $pwidth = $self->perimeter_flow->scaled_width;
my $pspacing = $self->perimeter_flow->scaled_spacing;
# external perimeters
$self->_ext_mm3_per_mm($self->ext_perimeter_flow->mm3_per_mm);
my $ext_pwidth = $self->ext_perimeter_flow->scaled_width;
my $ext_pspacing = scale($self->ext_perimeter_flow->spacing_to($self->perimeter_flow));
# overhang perimeters
$self->_mm3_per_mm_overhang($self->overhang_flow->mm3_per_mm);
# solid infill
my $ispacing = $self->solid_infill_flow->scaled_spacing;
my $gap_area_threshold = $pwidth ** 2;
# Calculate the minimum required spacing between two adjacent traces.
# This should be equal to the nominal flow spacing but we experiment
# with some tolerance in order to avoid triggering medial axis when
# some squishing might work. Loops are still spaced by the entire
# flow spacing; this only applies to collapsing parts.
my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
# prepare grown lower layer slices for overhang detection
if ($self->lower_slices && $self->config->overhangs) {
# We consider overhang any part where the entire nozzle diameter is not supported by the
# lower layer, so we take lower slices and offset them by half the nozzle diameter used
# in the current layer
my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->config->perimeter_extruder-1);
$self->_lower_slices_p(
offset([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2)
);
}
# we need to process each island separately because we might have different
# extra perimeters for each one
foreach my $surface (@{$self->slices}) {
# detect how many perimeters must be generated for this island
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
$loop_number--; # 0-indexed loops
my @gaps = (); # Polygons
my @last = @{$surface->expolygon->simplify_p(&Slic3r::SCALED_RESOLUTION)};
if ($loop_number >= 0) { # no loops = -1
my @contours = (); # depth => [ Polygon, Polygon ... ]
my @holes = (); # depth => [ Polygon, Polygon ... ]
my @thin_walls = (); # Polylines
# we loop one time more than needed in order to find gaps after the last perimeter was applied
for my $i (0..($loop_number+1)) { # outer loop is 0
my @offsets = ();
if ($i == 0) {
# the minimum thickness of a single loop is:
# ext_width/2 + ext_spacing/2 + spacing/2 + width/2
if ($self->config->thin_walls) {
@offsets = @{offset2(
\@last,
-(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1),
+(0.5*$ext_min_spacing - 1),
)};
} else {
@offsets = @{offset(
\@last,
-0.5*$ext_pwidth,
)};
}
# look for thin walls
if ($self->config->thin_walls) {
my $diff = diff(
\@last,
offset(\@offsets, +0.5*$ext_pwidth),
1, # medial axis requires non-overlapping geometry
);
# the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
# (actually, something larger than that still may exist due to mitering or other causes)
my $min_width = $ext_pwidth / 4;
@thin_walls = @{offset2_ex($diff, -$min_width/2, +$min_width/2)};
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
@thin_walls = grep $_->length > $ext_pwidth*2,
map @{$_->medial_axis($ext_pwidth + $ext_pspacing, $min_width)}, @thin_walls;
Slic3r::debugf " %d thin walls detected\n", scalar(@thin_walls) if $Slic3r::debug;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"medial_axis.svg",
no_arrows => 1,
expolygons => union_ex($diff),
green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
red_polylines => $self->_thin_wall_polylines,
);
}
}
} else {
my $distance = ($i == 1) ? $ext_pspacing : $pspacing;
if ($self->config->thin_walls) {
@offsets = @{offset2(
\@last,
-($distance + 0.5*$min_spacing - 1),
+(0.5*$min_spacing - 1),
)};
} else {
@offsets = @{offset(
\@last,
-$distance,
)};
}
# look for gaps
if ($self->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
# not using safety offset here would "detect" very narrow gaps
# (but still long enough to escape the area threshold) that gap fill
# won't be able to fill but we'd still remove from infill area
my $diff = diff_ex(
offset(\@last, -0.5*$distance),
offset(\@offsets, +0.5*$distance + 10), # safety offset
);
push @gaps, map $_->clone, map @$_, grep abs($_->area) >= $gap_area_threshold, @$diff;
}
}
last if !@offsets;
last if $i > $loop_number; # we were only looking for gaps this time
@last = @offsets;
$contours[$i] = [];
$holes[$i] = [];
foreach my $polygon (@offsets) {
my $loop = Slic3r::Layer::PerimeterGenerator::Loop->new(
polygon => $polygon,
is_contour => $polygon->is_counter_clockwise,
depth => $i,
);
if ($loop->is_contour) {
push @{$contours[$i]}, $loop;
} else {
push @{$holes[$i]}, $loop;
}
}
}
# nest loops: holes first
for my $d (0..$loop_number) {
# loop through all holes having depth $d
LOOP: for (my $i = 0; $i <= $#{$holes[$d]}; ++$i) {
my $loop = $holes[$d][$i];
# find the hole loop that contains this one, if any
for my $t (($d+1)..$loop_number) {
for (my $j = 0; $j <= $#{$holes[$t]}; ++$j) {
my $candidate_parent = $holes[$t][$j];
if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) {
$candidate_parent->add_child($loop);
splice @{$holes[$d]}, $i, 1;
--$i;
next LOOP;
}
}
}
# if no hole contains this hole, find the contour loop that contains it
for my $t (reverse 0..$loop_number) {
for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) {
my $candidate_parent = $contours[$t][$j];
if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) {
$candidate_parent->add_child($loop);
splice @{$holes[$d]}, $i, 1;
--$i;
next LOOP;
}
}
}
}
}
# nest contour loops
for my $d (reverse 1..$loop_number) {
# loop through all contours having depth $d
LOOP: for (my $i = 0; $i <= $#{$contours[$d]}; ++$i) {
my $loop = $contours[$d][$i];
# find the contour loop that contains it
for my $t (reverse 0..($d-1)) {
for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) {
my $candidate_parent = $contours[$t][$j];
if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) {
$candidate_parent->add_child($loop);
splice @{$contours[$d]}, $i, 1;
--$i;
next LOOP;
}
}
}
}
}
# at this point, all loops should be in $contours[0]
my @entities = $self->_traverse_loops($contours[0], \@thin_walls);
# if brim will be printed, reverse the order of perimeters so that
# we continue inwards after having finished the brim
# TODO: add test for perimeter order
@entities = reverse @entities
if $self->config->external_perimeters_first
|| ($self->layer_id == 0 && $self->print_config->brim_width > 0);
# append perimeters for this slice as a collection
$self->loops->append(Slic3r::ExtrusionPath::Collection->new(@entities))
if @entities;
}
# fill gaps
if (@gaps) {
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"gaps.svg",
expolygons => union_ex(\@gaps),
);
}
# where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth
# where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth
my @gap_sizes = (
[ $pwidth, 2*$pspacing, unscale 2*$pwidth ],
[ 0.1*$pwidth, $pwidth, unscale 1*$pwidth ],
);
foreach my $gap_size (@gap_sizes) {
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
$self->gap_fill->append($_) for @gap_fill;
# Make sure we don't infill narrow parts that are already gap-filled
# (we only consider this surface's gaps to reduce the diff() complexity).
# Growing actual extrusions ensures that gaps not filled by medial axis
# are not subtracted from fill surfaces (they might be too short gaps
# that medial axis skips but infill might join with other infill regions
# and use zigzag).
my $w = $gap_size->[2];
my @filled = map {
@{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline)
->grow(scale $w/2)};
} @gap_fill;
@last = @{diff(\@last, \@filled)};
@gaps = @{diff(\@gaps, \@filled)}; # prevent more gap fill here
}
}
# create one more offset to be used as boundary for fill
# we offset by half the perimeter spacing (to get to the actual infill boundary)
# and then we offset back and forth by half the infill spacing to only consider the
# non-collapsing regions
my $inset = 0;
if ($loop_number == 0) {
# one loop
$inset += $ext_pspacing/2;
} elsif ($loop_number > 0) {
# two or more loops
$inset += $pspacing/2;
}
$inset -= $self->config->get_abs_value_over('infill_overlap', $pwidth);
my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
$self->fill_surfaces->append($_)
for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type
@{offset2_ex(
[ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
-$inset -$min_perimeter_infill_spacing/2,
+$min_perimeter_infill_spacing/2,
)};
}
}
sub _traverse_loops {
my ($self, $loops, $thin_walls) = @_;
# loops is an arrayref of ::Loop objects
# turn each one into an ExtrusionLoop object
my $coll = Slic3r::ExtrusionPath::Collection->new;
foreach my $loop (@$loops) {
my $is_external = $loop->is_external;
my ($role, $loop_role);
if ($is_external) {
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
} else {
$role = EXTR_ROLE_PERIMETER;
}
if ($loop->is_internal_contour) {
# Note that we set loop role to ContourInternalPerimeter
# also when loop is both internal and external (i.e.
# there's only one contour loop).
$loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
} else {
$loop_role = EXTR_ROLE_PERIMETER;
}
# detect overhanging/bridging perimeters
my @paths = ();
if ($self->config->overhangs && $self->layer_id > 0
&& !($self->object_config->support_material && $self->object_config->support_material_contact_distance == 0)) {
# get non-overhang paths by intersecting this loop with the grown lower slices
foreach my $polyline (@{ intersection_ppl([ $loop->polygon ], $self->_lower_slices_p) }) {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => $role,
mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm),
width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width),
height => $self->layer_height,
);
}
# get overhang paths by checking what parts of this loop fall
# outside the grown lower slices (thus where the distance between
# the loop centerline and original lower slices is >= half nozzle diameter
foreach my $polyline (@{ diff_ppl([ $loop->polygon ], $self->_lower_slices_p) }) {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => EXTR_ROLE_OVERHANG_PERIMETER,
mm3_per_mm => $self->_mm3_per_mm_overhang,
width => $self->overhang_flow->width,
height => $self->overhang_flow->height,
);
}
# reapply the nearest point search for starting point
# (clone because the collection gets DESTROY'ed)
# We allow polyline reversal because Clipper may have randomly
# reversed polylines during clipping.
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection
@paths = map $_->clone, @{$collection->chained_path(0)};
} else {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $loop->polygon->split_at_first_point,
role => $role,
mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm),
width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width),
height => $self->layer_height,
);
}
my $eloop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
$eloop->role($loop_role);
$coll->append($eloop);
}
# append thin walls to the nearest-neighbor search (only for first iteration)
if (@$thin_walls) {
foreach my $polyline (@$thin_walls) {
$coll->append(Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => EXTR_ROLE_EXTERNAL_PERIMETER,
mm3_per_mm => $self->_mm3_per_mm,
width => $self->perimeter_flow->width,
height => $self->layer_height,
));
}
@$thin_walls = ();
}
# sort entities
my $sorted_coll = $coll->chained_path_indices(0);
my @indices = @{$sorted_coll->orig_indices};
# traverse children
my @entities = ();
for my $i (0..$#indices) {
my $idx = $indices[$i];
if ($idx > $#$loops) {
# this is a thin wall
# let's get it from the sorted collection as it might have been reversed
push @entities, $sorted_coll->[$i]->clone;
} else {
my $loop = $loops->[$idx];
my $eloop = $coll->[$idx]->clone;
my @children = $self->_traverse_loops($loop->children, $thin_walls);
if ($loop->is_contour) {
$eloop->make_counter_clockwise;
push @entities, @children, $eloop;
} else {
$eloop->make_clockwise;
push @entities, $eloop, @children;
}
}
}
return @entities;
}
sub _fill_gaps {
my ($self, $min, $max, $w, $gaps) = @_;
$min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
my $this = diff_ex(
offset2($gaps, -$min/2, +$min/2),
offset2($gaps, -$max/2, +$max/2),
1,
);
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
return if !@polylines;
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w
if @$this;
#my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
my $flow = Slic3r::Flow->new(
width => $w,
height => $self->layer_height,
nozzle_diameter => $self->solid_infill_flow->nozzle_diameter,
);
my %path_args = (
role => EXTR_ROLE_GAPFILL,
mm3_per_mm => $flow->mm3_per_mm,
width => $flow->width,
height => $self->layer_height,
);
my @entities = ();
foreach my $polyline (@polylines) {
#if ($polylines[$i]->isa('Slic3r::Polygon')) {
# my $loop = Slic3r::ExtrusionLoop->new;
# $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
# $polylines[$i] = $loop;
if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) {
# since medial_axis() now returns only Polyline objects, detect loops here
push @entities, my $loop = Slic3r::ExtrusionLoop->new;
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args));
} else {
push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args);
}
}
return @entities;
}
package Slic3r::Layer::PerimeterGenerator::Loop;
use Moo;
has 'polygon' => (is => 'ro', required => 1);
has 'is_contour' => (is => 'ro', required => 1);
has 'depth' => (is => 'ro', required => 1);
has 'children' => (is => 'ro', default => sub { [] });
use List::Util qw(first);
sub add_child {
my ($self, $child) = @_;
push @{$self->children}, $child;
}
sub is_external {
my ($self) = @_;
return $self->depth == 0;
}
sub is_internal_contour {
my ($self) = @_;
if ($self->is_contour) {
# an internal contour is a contour containing no other contours
return !defined first { $_->is_contour } @{$self->children};
}
return 0;
}
1;

View File

@ -1,131 +0,0 @@
package Slic3r::Layer::Region;
use strict;
use warnings;
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(scale);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex
);
use Slic3r::Surface ':types';
# TODO: lazy
sub infill_area_threshold {
my $self = shift;
return $self->flow(FLOW_ROLE_SOLID_INFILL)->scaled_spacing ** 2;
}
sub id { return $_[0]->layer->id; }
sub slice_z { return $_[0]->layer->slice_z; }
sub print_z { return $_[0]->layer->print_z; }
sub height { return $_[0]->layer->height; }
sub object { return $_[0]->layer->object; }
sub print { return $_[0]->layer->print; }
sub config { return $_[0]->region->config; }
sub make_perimeters {
my ($self, $slices, $fill_surfaces) = @_;
$self->perimeters->clear;
$self->thin_fills->clear;
my $generator = Slic3r::Layer::PerimeterGenerator->new(
# input:
config => $self->config,
object_config => $self->layer->object->config,
print_config => $self->layer->print->config,
layer_height => $self->height,
layer_id => $self->layer->id,
slices => $slices,
lower_slices => defined($self->layer->lower_layer) ? $self->layer->lower_layer->slices : undef,
perimeter_flow => $self->flow(FLOW_ROLE_PERIMETER),
ext_perimeter_flow => $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER),
overhang_flow => $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object),
solid_infill_flow => $self->flow(FLOW_ROLE_SOLID_INFILL),
# output:
loops => $self->perimeters,
gap_fill => $self->thin_fills,
fill_surfaces => $fill_surfaces,
);
$generator->process;
}
sub process_external_surfaces {
my ($self, $lower_layer) = @_;
my @surfaces = @{$self->fill_surfaces};
my $margin = scale &Slic3r::EXTERNAL_INFILL_MARGIN;
my @bottom = ();
foreach my $surface (grep $_->is_bottom, @surfaces) {
my $grown = $surface->expolygon->offset_ex(+$margin);
# detect bridge direction before merging grown surfaces otherwise adjacent bridges
# would get merged into a single one while they need different directions
# also, supply the original expolygon instead of the grown one, because in case
# of very thin (but still working) anchors, the grown expolygon would go beyond them
my $angle;
if ($lower_layer) {
my $bridge_detector = Slic3r::BridgeDetector->new(
$surface->expolygon,
$lower_layer->slices,
$self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width,
);
Slic3r::debugf "Processing bridge at layer %d:\n", $self->id;
$bridge_detector->detect_angle;
$angle = $bridge_detector->angle;
if (defined $angle && $self->object->config->support_material) {
$self->bridged->append(Slic3r::ExPolygon->new($_))
for @{ $bridge_detector->coverage_by_angle($angle) };
$self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges };
}
}
push @bottom, map $surface->clone(expolygon => $_, bridge_angle => $angle), @$grown;
}
my @top = ();
foreach my $surface (grep $_->surface_type == S_TYPE_TOP, @surfaces) {
# give priority to bottom surfaces
my $grown = diff_ex(
$surface->expolygon->offset(+$margin),
[ map $_->p, @bottom ],
);
push @top, map $surface->clone(expolygon => $_), @$grown;
}
# if we're slicing with no infill, we can't extend external surfaces
# over non-existent infill
my @fill_boundaries = $self->config->fill_density > 0
? @surfaces
: grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
# intersect the grown surfaces with the actual fill boundaries
my @new_surfaces = ();
foreach my $group (@{Slic3r::Surface::Collection->new(@top, @bottom)->group}) {
push @new_surfaces,
map $group->[0]->clone(expolygon => $_),
@{intersection_ex(
[ map $_->p, @$group ],
[ map $_->p, @fill_boundaries ],
1, # to ensure adjacent expolygons are unified
)};
}
# subtract the new top surfaces from the other non-top surfaces and re-add them
my @other = grep $_->surface_type != S_TYPE_TOP && !$_->is_bottom, @surfaces;
foreach my $group (@{Slic3r::Surface::Collection->new(@other)->group}) {
push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex(
[ map $_->p, @$group ],
[ map $_->p, @new_surfaces ],
)};
}
$self->fill_surfaces->clear;
$self->fill_surfaces->append($_) for @new_surfaces;
}
1;

View File

@ -14,16 +14,6 @@ use Slic3r::Print::State ':steps';
our $status_cb;
sub new {
# TODO: port PlaceholderParser methods to C++, then its own constructor
# can call them and no need for this new() method at all
my ($class) = @_;
my $self = $class->_new;
$self->placeholder_parser->apply_env_variables;
$self->placeholder_parser->update_timestamp;
return $self;
}
sub set_status_cb {
my ($class, $cb) = @_;
$status_cb = $cb;

View File

@ -15,7 +15,8 @@ has '_brim_done' => (is => 'rw');
has '_second_layer_things_done' => (is => 'rw');
has '_last_obj_copy' => (is => 'rw');
use List::Util qw(first sum);
use List::Util qw(first sum min max);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull);
use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset);
@ -35,14 +36,65 @@ sub BUILD {
}
# set up our helper object
my $gcodegen = Slic3r::GCode->new(
placeholder_parser => $self->placeholder_parser,
layer_count => $layer_count,
enable_cooling_markers => 1,
);
my $gcodegen = Slic3r::GCode->new;
$self->_gcodegen($gcodegen);
$gcodegen->set_placeholder_parser($self->placeholder_parser);
$gcodegen->set_layer_count($layer_count);
$gcodegen->set_enable_cooling_markers(1);
$gcodegen->apply_print_config($self->config);
$gcodegen->set_extruders($self->print->extruders);
$self->_gcodegen($gcodegen);
# initialize autospeed
{
# get the minimum cross-section used in the print
my @mm3_per_mm = ();
foreach my $object (@{$self->print->objects}) {
foreach my $region_id (0..$#{$self->print->regions}) {
my $region = $self->print->get_region($region_id);
foreach my $layer (@{$object->layers}) {
my $layerm = $layer->get_region($region_id);
if ($region->config->get_abs_value('perimeter_speed') == 0
|| $region->config->get_abs_value('small_perimeter_speed') == 0
|| $region->config->get_abs_value('external_perimeter_speed') == 0
|| $region->config->get_abs_value('bridge_speed') == 0) {
push @mm3_per_mm, $layerm->perimeters->min_mm3_per_mm;
}
if ($region->config->get_abs_value('infill_speed') == 0
|| $region->config->get_abs_value('solid_infill_speed') == 0
|| $region->config->get_abs_value('top_solid_infill_speed') == 0
|| $region->config->get_abs_value('bridge_speed') == 0) {
push @mm3_per_mm, $layerm->fills->min_mm3_per_mm;
}
}
}
if ($object->config->get_abs_value('support_material_speed') == 0
|| $object->config->get_abs_value('support_material_interface_speed') == 0) {
foreach my $layer (@{$object->support_layers}) {
push @mm3_per_mm, $layer->support_fills->min_mm3_per_mm;
push @mm3_per_mm, $layer->support_interface_fills->min_mm3_per_mm;
}
}
}
@mm3_per_mm = grep $_ != 0, @mm3_per_mm;
if (@mm3_per_mm) {
my $min_mm3_per_mm = min(@mm3_per_mm);
# In order to honor max_print_speed we need to find a target volumetric
# speed that we can use throughout the print. So we define this target
# volumetric speed as the volumetric speed produced by printing the
# smallest cross-section at the maximum speed: any larger cross-section
# will need slower feedrates.
my $volumetric_speed = $min_mm3_per_mm * $self->config->max_print_speed;
# limit such volumetric speed with max_volumetric_speed if set
if ($self->config->max_volumetric_speed > 0) {
$volumetric_speed = min(
$volumetric_speed,
$self->config->max_volumetric_speed,
);
}
$gcodegen->set_volumetric_speed($volumetric_speed);
}
}
}
$self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new(
@ -160,8 +212,8 @@ sub export {
}
my $convex_hull = convex_hull([ map @$_, @skirts ]);
$gcodegen->ooze_prevention->enable(1);
$gcodegen->ooze_prevention->standby_points(
$gcodegen->ooze_prevention->set_enable(1);
$gcodegen->ooze_prevention->set_standby_points(
[ map @{$_->equally_spaced_points(scale 10)}, @{offset([$convex_hull], scale 3)} ]
);
@ -195,18 +247,18 @@ sub export {
# no collision happens hopefully.
if ($finished_objects > 0) {
$gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
$gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
$gcodegen->avoid_crossing_perimeters->use_external_mp_once(1);
$gcodegen->set_enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
$gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1);
print $fh $gcodegen->retract;
print $fh $gcodegen->travel_to(
Slic3r::Point->new(0,0),
undef,
EXTR_ROLE_NONE,
'move to origin position for next object',
);
$gcodegen->enable_cooling_markers(1);
$gcodegen->set_enable_cooling_markers(1);
# disable motion planner when traveling to first object point
$gcodegen->avoid_crossing_perimeters->disable_once(1);
$gcodegen->avoid_crossing_perimeters->set_disable_once(1);
}
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
@ -308,14 +360,14 @@ sub process_layer {
$self->_spiral_vase->enable(
($layer->id > 0 || $self->print->config->brim_width == 0)
&& ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
&& !defined(first { $_->region->config->bottom_solid_layers > $layer->id } @{$layer->regions})
&& !defined(first { $_->perimeters->items_count > 1 } @{$layer->regions})
&& !defined(first { $_->fills->items_count > 0 } @{$layer->regions})
);
}
# if we're going to apply spiralvase to this layer, disable loop clipping
$self->_gcodegen->enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable);
$self->_gcodegen->set_enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable);
if (!$self->_second_layer_things_done && $layer->id == 1) {
for my $extruder (@{$self->_gcodegen->writer->extruders}) {
@ -329,15 +381,19 @@ sub process_layer {
}
# set new layer - this will change Z and force a retraction if retract_layer_change is enabled
$gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->before_layer_gcode, {
layer_num => $self->_gcodegen->layer_index + 1,
layer_z => $layer->print_z,
}) . "\n" if $self->print->config->before_layer_gcode;
$gcode .= $self->_gcodegen->change_layer($layer); # this will increase $self->_gcodegen->layer_index
$gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, {
layer_num => $self->_gcodegen->layer_index,
layer_z => $layer->print_z,
}) . "\n" if $self->print->config->layer_gcode;
if ($self->print->config->before_layer_gcode) {
my $pp = $self->_gcodegen->placeholder_parser->clone;
$pp->set('layer_num' => $self->_gcodegen->layer_index + 1);
$pp->set('layer_z' => $layer->print_z);
$gcode .= $pp->process($self->print->config->before_layer_gcode) . "\n";
}
$gcode .= $self->_gcodegen->change_layer($layer->as_layer); # this will increase $self->_gcodegen->layer_index
if ($self->print->config->layer_gcode) {
my $pp = $self->_gcodegen->placeholder_parser->clone;
$pp->set('layer_num' => $self->_gcodegen->layer_index);
$pp->set('layer_z' => $layer->print_z);
$gcode .= $pp->process($self->print->config->layer_gcode) . "\n";
}
# extrude skirt along raft layers and normal object layers
# (not along interlaced support material layers)
@ -345,7 +401,7 @@ sub process_layer {
&& !$self->_skirt_done->{$layer->print_z}
&& (!$layer->isa('Slic3r::Layer::Support') || $layer->id < $object->config->raft_layers)) {
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1);
my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders};
$gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]);
# skip skirt if we have a large brim
@ -378,12 +434,12 @@ sub process_layer {
}
}
$self->_skirt_done->{$layer->print_z} = 1;
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0);
# allow a straight travel move to the first object point if this is the first layer
# (but don't in next layers)
if ($layer->id == 0) {
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
$self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1);
}
}
@ -391,19 +447,19 @@ sub process_layer {
if (!$self->_brim_done) {
$gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1);
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1);
$gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed)
for @{$self->print->brim};
$self->_brim_done(1);
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0);
# allow a straight travel move to the first object point
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
$self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1);
}
for my $copy (@$object_copies) {
# when starting a new object, use the external motion planner for the first travel move
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy";
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy";
$self->_last_obj_copy("$copy");
$self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
@ -539,7 +595,7 @@ sub _extrude_perimeters {
my $gcode = "";
foreach my $region_id (sort keys %$entities_by_region) {
$self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config);
$gcode .= $self->_gcodegen->extrude($_, 'perimeter')
$gcode .= $self->_gcodegen->extrude($_, 'perimeter', -1)
for @{ $entities_by_region->{$region_id} };
}
return $gcode;
@ -555,10 +611,10 @@ sub _extrude_infill {
my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} });
for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) {
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
$gcode .= $self->_gcodegen->extrude($_, 'infill')
$gcode .= $self->_gcodegen->extrude($_, 'infill', -1)
for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)};
} else {
$gcode .= $self->_gcodegen->extrude($fill, 'infill') ;
$gcode .= $self->_gcodegen->extrude($fill, 'infill', -1) ;
}
}
}

View File

@ -45,8 +45,9 @@ sub slice {
$self->clear_layers;
# make layers taking custom heights into account
my $print_z = my $slice_z = my $height = my $id = 0;
my $first_object_layer_height = -1;
my $id = 0;
my $print_z = 0;
my $first_object_layer_height = -1;
my $first_object_layer_distance = -1;
# add raft layers
@ -63,8 +64,8 @@ sub slice {
{
my @nozzle_diameters = (
map $self->print->config->get_at('nozzle_diameter', $_),
$self->config->support_material_extruder,
$self->config->support_material_interface_extruder,
$self->config->support_material_extruder-1,
$self->config->support_material_interface_extruder-1,
);
$support_material_layer_height = 0.75 * min(@nozzle_diameters);
}
@ -78,20 +79,17 @@ sub slice {
);
$nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
}
my $distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter);
$first_object_layer_distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter);
# force first layer print_z according to the contact distance
# (the loop below will raise print_z by such height)
if ($self->config->support_material_contact_distance == 0) {
$first_object_layer_height = $distance;
} else {
$first_object_layer_height = $nozzle_diameter;
}
$first_object_layer_distance = $distance;
$first_object_layer_height = $first_object_layer_distance - $self->config->support_material_contact_distance;
}
# loop until we have at least one layer and the max slice_z reaches the object height
my $max_z = unscale($self->size->z);
my $slice_z = 0;
my $height = 0;
my $max_z = unscale($self->size->z);
while (($slice_z - $height) <= $max_z) {
# assign the default height to the layer according to the general settings
$height = ($id == 0)
@ -439,7 +437,7 @@ sub make_perimeters {
$slice->extra_perimeters($slice->extra_perimeters + 1);
}
Slic3r::debugf " adding %d more perimeter(s) at layer %d\n",
$slice->extra_perimeters, $layerm->id
$slice->extra_perimeters, $layerm->layer->id
if $slice->extra_perimeters > 0;
}
}
@ -832,17 +830,6 @@ sub clip_fill_surfaces {
}
}
sub process_external_surfaces {
my ($self) = @_;
for my $region_id (0 .. ($self->print->region_count-1)) {
$self->get_layer(0)->regions->[$region_id]->process_external_surfaces(undef);
for my $i (1 .. ($self->layer_count - 1)) {
$self->get_layer($i)->regions->[$region_id]->process_external_surfaces($self->get_layer($i-1));
}
}
}
sub discover_horizontal_shells {
my $self = shift;
@ -852,8 +839,8 @@ sub discover_horizontal_shells {
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layerm = $self->get_layer($i)->regions->[$region_id];
if ($layerm->config->solid_infill_every_layers && $layerm->config->fill_density > 0
&& ($i % $layerm->config->solid_infill_every_layers) == 0) {
if ($layerm->region->config->solid_infill_every_layers && $layerm->region->config->fill_density > 0
&& ($i % $layerm->region->config->solid_infill_every_layers) == 0) {
$_->surface_type(S_TYPE_INTERNALSOLID) for @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)};
}
@ -875,8 +862,8 @@ sub discover_horizontal_shells {
Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom';
my $solid_layers = ($type == S_TYPE_TOP)
? $layerm->config->top_solid_layers
: $layerm->config->bottom_solid_layers;
? $layerm->region->config->top_solid_layers
: $layerm->region->config->bottom_solid_layers;
NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1;
abs($n - $i) <= $solid_layers-1;
($type == S_TYPE_TOP) ? $n-- : $n++) {
@ -904,7 +891,7 @@ sub discover_horizontal_shells {
);
next EXTERNAL if !@$new_internal_solid;
if ($layerm->config->fill_density == 0) {
if ($layerm->region->config->fill_density == 0) {
# if we're printing a hollow object we discard any solid shell thinner
# than a perimeter width, since it's probably just crossing a sloping wall
# and it's not wanted in a hollow print even if it would make sense when
@ -944,7 +931,12 @@ sub discover_horizontal_shells {
# make sure our grown surfaces don't exceed the fill area
my @grown = @{intersection(
offset($too_narrow, +$margin),
[ map $_->p, @neighbor_fill_surfaces ],
# Discard bridges as they are grown for anchoring and we can't
# remove such anchors. (This may happen when a bridge is being
# anchored onto a wall where little space remains after the bridge
# is grown, and that little space is an internal solid shell so
# it triggers this too_narrow logic.)
[ map $_->p, grep { $_->is_internal && !$_->is_bridge } @neighbor_fill_surfaces ],
)};
$new_internal_solid = $solid = [ @grown, @$new_internal_solid ];
}
@ -1080,7 +1072,7 @@ sub combine_infill {
+ $layerms[-1]->flow(FLOW_ROLE_PERIMETER)->scaled_width / 2
# Because fill areas for rectilinear and honeycomb are grown
# later to overlap perimeters, we need to counteract that too.
+ (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|honeycomb)/)
+ (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|grid|line|honeycomb)/)
? $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width
: 0)
)}, @$intersection;
@ -1097,12 +1089,12 @@ sub combine_infill {
)};
# apply surfaces back with adjusted depth to the uppermost layer
if ($layerm->id == $self->get_layer($layer_idx)->id) {
if ($layerm->layer->id == $self->get_layer($layer_idx)->id) {
push @new_this_type,
map Slic3r::Surface->new(
expolygon => $_,
surface_type => $type,
thickness => sum(map $_->height, @layerms),
thickness => sum(map $_->layer->height, @layerms),
thickness_layers => scalar(@layerms),
),
@$intersection;

View File

@ -4,7 +4,7 @@ use Moo;
use List::Util qw(sum min max);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(scale scaled_epsilon PI rad2deg deg2rad convex_hull);
use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull);
use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2
intersection_pl offset2_ex diff_pl);
use Slic3r::Surface ':types';
@ -235,7 +235,7 @@ sub contact_area {
# just remove bridged areas
$diff = diff(
$diff,
[ map @$_, @{$layerm->bridged} ],
$layerm->bridged,
1,
);
}
@ -267,13 +267,13 @@ sub contact_area {
# get the average nozzle diameter used on this layer
my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_),
map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1, $_->config->solid_infill_extruder-1 }
@{$layer->regions};
map $_->region, @{$layer->regions};
my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
my $contact_z = $layer->print_z - $self->contact_distance($layer->height, $nozzle_diameter);
# ignore this contact area if it's too low
next if $contact_z < $self->object_config->get_value('first_layer_height');
next if $contact_z < $self->object_config->get_value('first_layer_height') - epsilon;
$contact{$contact_z} = [ @contact ];
$overhang{$contact_z} = [ @overhang ];
@ -356,13 +356,16 @@ sub support_layers_z {
if ($self->object_config->raft_layers > 1 && @z >= 2) {
# $z[1] is last raft layer (contact layer for the first layer object)
my $height = ($z[1] - $z[0]) / ($self->object_config->raft_layers - 1);
# since we already have two raft layers ($z[0] and $z[1]) we need to insert
# raft_layers-2 more
splice @z, 1, 0,
map { sprintf "%.2f", $_ }
map { $z[0] + $height * $_ }
0..($self->object_config->raft_layers - 1);
1..($self->object_config->raft_layers - 2);
}
for (my $i = $#z; $i >= 0; $i--) {
# create other layers (skip raft layers as they're already done and use thicker layers)
for (my $i = $#z; $i >= $self->object_config->raft_layers; $i--) {
my $target_height = $support_material_height;
if ($i > 0 && $top{ $z[$i-1] }) {
$target_height = $nozzle_diameter;

View File

@ -34,6 +34,13 @@ sub mesh {
$facets = [
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[0,2,8],[10,8,9],[0,8,6],[0,6,4],[4,7,9],[7,10,9],[2,3,9],[9,3,11],[12,1,5],[13,3,12],[14,12,5],[3,1,12],[11,3,13],[11,15,5],[11,13,15],[15,14,5],[5,4,9],[11,5,9],[8,13,12],[6,8,12],[10,15,13],[8,10,13],[15,10,14],[14,10,7],[14,7,12],[12,7,6]
],
} elsif ($name eq 'cube_with_concave_hole') {
$vertices = [
[-10,-10,-5],[-10,-10,5],[-10,10,-5],[-10,10,5],[10,-10,-5],[10,-10,5],[-5,-5,-5],[5,-5,-5],[5,5,-5],[5,10,-5],[-5,5,-5],[3.06161699911402e-16,5,-5],[5,0,-5],[0,0,-5],[10,5,-5],[5,10,5],[-5,-5,5],[5,0,5],[5,-5,5],[-5,5,5],[10,5,5],[5,5,5],[3.06161699911402e-16,5,5],[0,0,5]
];
$facets = [
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[10,2,11],[11,12,13],[0,2,10],[0,10,6],[0,6,4],[11,2,8],[4,7,12],[4,12,8],[12,11,8],[14,4,8],[2,3,9],[9,3,15],[16,1,5],[17,18,5],[19,3,16],[20,21,5],[18,16,5],[3,1,16],[22,3,19],[21,3,22],[21,17,5],[21,22,17],[21,15,3],[23,17,22],[5,4,14],[20,5,14],[20,14,21],[21,14,8],[9,15,21],[8,9,21],[10,19,16],[6,10,16],[11,22,19],[10,11,19],[13,23,11],[11,23,22],[23,13,12],[17,23,12],[17,12,18],[18,12,7],[18,7,16],[16,7,6]
],
} elsif ($name eq 'V') {
$vertices = [
[-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20]

View File

@ -82,7 +82,11 @@ foreach my $c (@external_configs, Slic3r::Config->new_from_cli(%cli_options)) {
# save configuration
if ($opt{save}) {
$cli_config->save($opt{save});
if (@{$cli_config->get_keys} > 0) {
$cli_config->save($opt{save});
} else {
Slic3r::Config->new_from_defaults->save($opt{save});
}
}
# apply command line config on top of default config

View File

@ -1,4 +1,4 @@
use Test::More tests => 2;
use Test::More tests => 1;
use strict;
use warnings;
@ -10,13 +10,6 @@ BEGIN {
use Slic3r;
use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('layer_height', 0.123);
$config->setenv;
is $ENV{SLIC3R_LAYER_HEIGHT}, '0.123', 'setenv';
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('perimeter_extrusion_width', '250%');

View File

@ -18,9 +18,12 @@ sub buffer {
my $print_config = Slic3r::Config::Print->new;
$print_config->apply_dynamic($config);
my $gcodegen = Slic3r::GCode->new;
$gcodegen->apply_print_config($print_config);
$gcodegen->set_layer_count(10);
my $buffer = Slic3r::GCode::CoolingBuffer->new(
config => $print_config,
gcodegen => Slic3r::GCode->new(print_config => $print_config, layer_count => 10, extruders => []),
gcodegen => $gcodegen,
);
return $buffer;
}
@ -30,14 +33,14 @@ $config->set('disable_fan_first_layers', 0);
{
my $buffer = buffer($config);
$buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time + 1);
$buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time + 1);
my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush;
like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold';
}
{
my $buffer = buffer($config);
$buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1);
$buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time - 1);
my $gcode = $buffer->append("G1 X50 F2500\nG1 X100 E1 F3000\nG1 E4 F400", 0, 0, 0.4) . $buffer->flush;
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';
@ -46,7 +49,7 @@ $config->set('disable_fan_first_layers', 0);
{
my $buffer = buffer($config);
$buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time + 1);
$buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time + 1);
my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush;
unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold';
}
@ -56,7 +59,7 @@ $config->set('disable_fan_first_layers', 0);
my $gcode = "";
for my $obj_id (0 .. 1) {
# use an elapsed time which is < the slowdown threshold but greater than it when summed twice
$buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1);
$buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time - 1);
$gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4);
}
$gcode .= $buffer->flush;
@ -69,7 +72,7 @@ $config->set('disable_fan_first_layers', 0);
for my $layer_id (0 .. 1) {
for my $obj_id (0 .. 1) {
# use an elapsed time which is < the threshold but greater than it when summed twice
$buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time - 1);
$buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time - 1);
$gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights
}
}
@ -83,7 +86,7 @@ $config->set('disable_fan_first_layers', 0);
for my $layer_id (0 .. 1) {
for my $obj_id (0 .. 1) {
# use an elapsed time which is < the threshold even when summed twice
$buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time/2 - 1);
$buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time/2 - 1);
$gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights
}
}

View File

@ -47,7 +47,8 @@ use Slic3r::Test;
{
my $parser = Slic3r::GCode::PlaceholderParser->new;
$parser->apply_config(my $config = Slic3r::Config->new_from_defaults);
is $parser->process('[temperature_[foo]]', { foo => '1' }),
$parser->set('foo' => '0');
is $parser->process('[temperature_[foo]]'),
$config->temperature->[0],
"nested config options";
}

View File

@ -13,10 +13,8 @@ use Slic3r::Geometry qw(scale convex_hull);
use Slic3r::Test;
{
my $gcodegen = Slic3r::GCode->new(
layer_count => 1,
extruders => [],
);
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';
}

View File

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 38;
plan tests => 42;
BEGIN {
use FindBin;
@ -190,6 +190,21 @@ my $polygons = [
#==========================================================
{
my $square = Slic3r::Polygon->new_scale(
[100,100],
[200,100],
[200,200],
[100,200],
);
is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in ccw square';
is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in ccw square';
$square->make_clockwise;
is scalar(@{$square->concave_points(PI*4/3)}), 4, 'fuor concave vertices detected in cw square';
is scalar(@{$square->convex_points(PI*2/3)}), 0, 'no convex vertices detected in cw square';
}
{
my $square = Slic3r::Polygon->new_scale(
[150,100],

View File

@ -1,4 +1,4 @@
use Test::More tests => 29;
use Test::More tests => 33;
use strict;
use warnings;
@ -17,6 +17,101 @@ use Slic3r::Geometry::Clipper qw(union_ex diff union offset);
use Slic3r::Surface ':types';
use Slic3r::Test;
{
my $flow = Slic3r::Flow->new(
width => 1,
height => 1,
nozzle_diameter => 1,
);
my $config = Slic3r::Config->new;
my $test = sub {
my ($expolygons, %expected) = @_;
my $slices = Slic3r::Surface::Collection->new;
$slices->append(Slic3r::Surface->new(
surface_type => S_TYPE_INTERNAL,
expolygon => $_,
)) for @$expolygons;
my ($region_config, $object_config, $print_config, $loops, $gap_fill, $fill_surfaces);
my $g = Slic3r::Layer::PerimeterGenerator->new(
# input:
$slices,
1, # layer height
$flow,
($region_config = Slic3r::Config::PrintRegion->new),
($object_config = Slic3r::Config::PrintObject->new),
($print_config = Slic3r::Config::Print->new),
# output:
($loops = Slic3r::ExtrusionPath::Collection->new),
($gap_fill = Slic3r::ExtrusionPath::Collection->new),
($fill_surfaces = Slic3r::Surface::Collection->new),
);
$g->config->apply_dynamic($config);
$g->process;
is scalar(@$loops),
scalar(@$expolygons), 'expected number of collections';
ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @$loops),
'everything is returned as collections';
my $flattened_loops = $loops->flatten;
my @loops = @$flattened_loops;
is scalar(@loops),
$expected{total}, 'expected number of loops';
is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops),
$expected{external}, 'expected number of external loops';
is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops),
$expected{cinternal}, 'expected number of internal contour loops';
is scalar(grep $_->polygon->is_counter_clockwise, @loops),
$expected{ccw}, 'expected number of ccw loops';
};
$config->set('perimeters', 3);
$test->(
[
Slic3r::ExPolygon->new(
Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
),
],
total => 3,
external => 1,
cinternal => 1,
ccw => 3,
);
$test->(
[
Slic3r::ExPolygon->new(
Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]),
),
],
total => 6,
external => 2,
cinternal => 1,
ccw => 3,
);
$test->(
[
Slic3r::ExPolygon->new(
Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]),
Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]),
),
# nested:
Slic3r::ExPolygon->new(
Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]),
Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]),
),
],
total => 4*3,
external => 4,
cinternal => 2,
ccw => 2*3,
);
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
@ -47,14 +142,14 @@ use Slic3r::Test;
ok !$has_cw_loops, 'all perimeters extruded ccw';
}
{
foreach my $model (qw(cube_with_hole cube_with_concave_hole)) {
$config->set('external_perimeter_speed', 68);
my $print = Slic3r::Test::init_print(
'cube_with_hole',
$model,
config => $config,
duplicate => 2, # we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong inwards moves)
);
my $has_cw_loops = my $has_outwards_move = 0;
my $has_cw_loops = my $has_outwards_move = my $starts_on_convex_point = 0;
my $cur_loop;
my %external_loops = (); # print_z => count of external loops
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
@ -74,10 +169,22 @@ use Slic3r::Test;
if defined($external_loops{$self->Z}) && $external_loops{$self->Z} == 2;
$external_loops{$self->Z}++;
my $loop_contains_point = Slic3r::Polygon->new_scale(@$cur_loop)->contains_point($move_dest);
my $is_contour = $external_loops{$self->Z} == 2;
my $is_hole = $external_loops{$self->Z} == 1;
my $loop = Slic3r::Polygon->new_scale(@$cur_loop);
my $loop_contains_point = $loop->contains_point($move_dest);
$has_outwards_move = 1
if (!$loop_contains_point && $external_loops{$self->Z} == 2) # contour should include destination
|| ($loop_contains_point && $external_loops{$self->Z} == 1); # hole should not
if (!$loop_contains_point && $is_contour) # contour should include destination
|| ($loop_contains_point && $is_hole); # hole should not
if ($model eq 'cube_with_concave_hole') {
# check that loop starts at a concave vertex
my $ccw_angle = $loop->first_point->ccw_angle(@$loop[-2,1]);
my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex
$starts_on_convex_point = 1
if ($convex && $is_contour) || (!$convex && $is_hole);
}
}
$cur_loop = undef;
}
@ -85,6 +192,7 @@ use Slic3r::Test;
});
ok !$has_cw_loops, 'all perimeters extruded ccw';
ok !$has_outwards_move, 'move inwards after completing external loop';
ok !$starts_on_convex_point, 'loops start on concave point if any';
}
{
@ -210,9 +318,12 @@ use Slic3r::Test;
expolygons => [ map $_->expolygon, @{$layerm->slices} ],
red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]),
green_expolygons => union_ex($non_covered),
no_arrows => 1,
polylines => [
map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters},
],
);
}
ok !(defined first { $_->area > ($iflow->scaled_width**2) } @$non_covered), 'no gap between perimeters and infill';
}
@ -286,89 +397,4 @@ use Slic3r::Test;
$test->('small_dorito');
}
{
my $flow = Slic3r::Flow->new(
width => 1,
height => 1,
nozzle_diameter => 1,
);
my $config = Slic3r::Config->new;
my $test = sub {
my ($expolygons, %expected) = @_;
my $slices = Slic3r::Surface::Collection->new;
$slices->append(Slic3r::Surface->new(
surface_type => S_TYPE_INTERNAL,
expolygon => $_,
)) for @$expolygons;
my $g = Slic3r::Layer::PerimeterGenerator->new(
# input:
layer_height => 1,
slices => $slices,
flow => $flow,
);
$g->config->apply_dynamic($config);
$g->process;
is scalar(@{$g->loops}),
scalar(@$expolygons), 'expected number of collections';
ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @{$g->loops}),
'everything is returned as collections';
is scalar(map @$_, @{$g->loops}),
$expected{total}, 'expected number of loops';
is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, map @$_, @{$g->loops}),
$expected{external}, 'expected number of external loops';
is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, map @$_, @{$g->loops}),
$expected{cinternal}, 'expected number of internal contour loops';
is scalar(grep $_->polygon->is_counter_clockwise, map @$_, @{$g->loops}),
$expected{ccw}, 'expected number of ccw loops';
return $g;
};
$config->set('perimeters', 3);
$test->(
[
Slic3r::ExPolygon->new(
Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
),
],
total => 3,
external => 1,
cinternal => 1,
ccw => 3,
);
$test->(
[
Slic3r::ExPolygon->new(
Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]),
),
],
total => 6,
external => 2,
cinternal => 1,
ccw => 3,
);
$test->(
[
Slic3r::ExPolygon->new(
Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]),
Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]),
),
# nested:
Slic3r::ExPolygon->new(
Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]),
Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]),
),
],
total => 4*3,
external => 4,
cinternal => 2,
ccw => 2*3,
);
}
__END__

View File

@ -192,7 +192,7 @@ use Slic3r::Test;
push @z_steps, $info->{dist_Z}
if $started_extruding && $info->{dist_Z} > 0;
$travel_moves_after_first_extrusion++
if $info->{travel} && $started_extruding && !exists $args->{Z};
if $info->{travel} && $info->{dist_XY} > 0 && $started_extruding && !exists $args->{Z};
} elsif ($cmd eq 'M104') {
$first_layer_temperature_set = 1 if $args->{S} == 205;
$temperature_set = 1 if $args->{S} == 200;
@ -271,7 +271,7 @@ use Slic3r::Test;
foreach my $segment (@this_layer) {
# check that segment's dist_Z is proportioned to its dist_XY
$all_layer_segments_have_same_slope = 1
if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.1;
if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.2;
}
@this_layer = ();

View File

@ -6,17 +6,28 @@
use strict;
use warnings;
use constant PI => 3.141592653589793238;
my @filament_diameter = split /,/, $ENV{SLIC3R_FILAMENT_DIAMETER};
my $E = 0;
my ($X, $Y);
my $T = 0;
my ($X, $Y, $F);
while (<>) {
if (/^G1.*? F([0-9.]+)/) {
$F = $1;
}
if (/^G1 X([0-9.]+) Y([0-9.]+).*? E([0-9.]+)/) {
my ($x, $y, $e) = ($1, $2, $3);
my $e_length = $e - $E;
if ($e_length > 0 && defined $X && defined $Y) {
my $dist = sqrt( (($x-$X)**2) + (($y-$Y)**2) );
if ($dist > 0) {
my $flowrate = sprintf '%.2f', $e_length / $dist;
s/(\R+)/ ; XY dist = $dist ; E dist = $e_length ; E\/XY = $flowrate mm\/mm$1/;
my $mm_per_mm = $e_length / $dist; # dE/dXY
my $mm3_per_mm = ($filament_diameter[$T] ** 2) * PI/4 * $mm_per_mm;
my $vol_speed = $F/60 * $mm3_per_mm;
my $comment = sprintf ' ; dXY = %.3fmm ; dE = %.5fmm ; dE/XY = %.5fmm/mm; volspeed = %.5fmm^3/sec',
$dist, $e_length, $mm_per_mm, $vol_speed;
s/(\R+)/$comment$1/;
}
}
$E = $e;
@ -33,6 +44,9 @@ while (<>) {
if (/^G92 E0/) {
$E = 0;
}
if (/^T(\d+)/) {
$T = $1;
}
print;
}

View File

@ -1670,6 +1670,7 @@ src/libslic3r/ExtrusionEntityCollection.cpp
src/libslic3r/ExtrusionEntityCollection.hpp
src/libslic3r/Flow.cpp
src/libslic3r/Flow.hpp
src/libslic3r/GCode.cpp
src/libslic3r/GCode.hpp
src/libslic3r/GCodeSender.cpp
src/libslic3r/GCodeSender.hpp
@ -1691,6 +1692,8 @@ src/libslic3r/MotionPlanner.cpp
src/libslic3r/MotionPlanner.hpp
src/libslic3r/MultiPoint.cpp
src/libslic3r/MultiPoint.hpp
src/libslic3r/PerimeterGenerator.cpp
src/libslic3r/PerimeterGenerator.hpp
src/libslic3r/PlaceholderParser.cpp
src/libslic3r/PlaceholderParser.hpp
src/libslic3r/Point.cpp
@ -1753,6 +1756,7 @@ t/17_boundingbox.t
t/18_motionplanner.t
t/19_model.t
t/20_print.t
t/21_gcode.t
xsp/BoundingBox.xsp
xsp/BridgeDetector.xsp
xsp/Clipper.xsp
@ -1765,6 +1769,7 @@ xsp/ExtrusionLoop.xsp
xsp/ExtrusionPath.xsp
xsp/Flow.xsp
xsp/GCodeSender.xsp
xsp/GCode.xsp
xsp/GCodeWriter.xsp
xsp/Geometry.xsp
xsp/GUI_3DScene.xsp
@ -1774,6 +1779,7 @@ xsp/Model.xsp
xsp/MotionPlanner.xsp
xsp/my.map
xsp/mytype.map
xsp/PerimeterGenerator.xsp
xsp/PlaceholderParser.xsp
xsp/Point.xsp
xsp/Polygon.xsp

View File

@ -215,7 +215,12 @@ for my $class (qw(
Slic3r::ExtrusionPath
Slic3r::ExtrusionPath::Collection
Slic3r::Flow
Slic3r::GCode
Slic3r::GCode::AvoidCrossingPerimeters
Slic3r::GCode::OozePrevention
Slic3r::GCode::PlaceholderParser
Slic3r::GCode::Wipe
Slic3r::GCode::Writer
Slic3r::Geometry::BoundingBox
Slic3r::Geometry::BoundingBoxf
Slic3r::Geometry::BoundingBoxf3

View File

@ -269,7 +269,7 @@ stl_write_binary(stl_file *stl, const char *file, const char *label) {
if (stl->error) return;
/* Open the file */
fp = fopen(file, "w");
fp = fopen(file, "wb");
if(fp == NULL) {
error_msg = (char*)
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */

View File

@ -152,18 +152,19 @@ stl_count_facets(stl_file *stl, char *file) {
}
/* Find the number of facets */
j = 0;
for(i = 0; i < file_size ; i++) {
j++;
if(getc(stl->fp) == '\n') {
if(j > 4) { /* don't count short lines */
num_lines++;
}
j = 0;
}
char linebuf[100];
while (fgets(linebuf, 100, stl->fp) != NULL) {
/* don't count short lines */
if (strlen(linebuf) <= 4) continue;
/* skip solid/endsolid lines as broken STL file generators may put several of them */
if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue;
++num_lines;
}
rewind(stl->fp);
/* Get the header */
for(i = 0;
(i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++);
@ -269,8 +270,6 @@ stl_read(stl_file *stl, int first_facet, int first) {
fseek(stl->fp, HEADER_SIZE, SEEK_SET);
} else {
rewind(stl->fp);
/* Skip the first line of the file */
while(getc(stl->fp) != '\n');
}
for(i = first_facet; i < stl->stats.number_of_facets; i++) {
@ -288,13 +287,18 @@ stl_read(stl_file *stl, int first_facet, int first) {
} else
/* Read a single facet from an ASCII .STL file */
{
if((fscanf(stl->fp, "%*s %*s %f %f %f\n", &facet.normal.x, &facet.normal.y, &facet.normal.z) + \
fscanf(stl->fp, "%*s %*s") + \
fscanf(stl->fp, "%*s %f %f %f\n", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z) + \
fscanf(stl->fp, "%*s %f %f %f\n", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z) + \
fscanf(stl->fp, "%*s %f %f %f\n", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z) + \
fscanf(stl->fp, "%*s") + \
fscanf(stl->fp, "%*s")) != 12) {
// skip solid/endsolid
// (in this order, otherwise it won't work when they are paired in the middle of a file)
fscanf(stl->fp, "endsolid\n");
fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid")
if((fscanf(stl->fp, " facet normal %f %f %f\n", &facet.normal.x, &facet.normal.y, &facet.normal.z) + \
fscanf(stl->fp, " outer loop\n") + \
fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z) + \
fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z) + \
fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z) + \
fscanf(stl->fp, " endloop\n") + \
fscanf(stl->fp, " endfacet\n")) != 12) {
perror("Something is syntactically very wrong with this ASCII STL!");
stl->error = 1;
return;

View File

@ -185,16 +185,12 @@ BridgeDetector::detect_angle()
return true;
}
void
BridgeDetector::coverage(Polygons* coverage) const
{
if (this->angle == -1) return;
return this->coverage(angle, coverage);
}
void
BridgeDetector::coverage(double angle, Polygons* coverage) const
{
if (angle == -1) angle = this->angle;
if (angle == -1) return;
// Clone our expolygon and rotate it so that we work with vertical lines.
ExPolygon expolygon = this->expolygon;
expolygon.rotate(PI/2.0 - angle, Point(0,0));
@ -263,19 +259,23 @@ BridgeDetector::coverage(double angle, Polygons* coverage) const
*/
}
Polygons
BridgeDetector::coverage(double angle) const
{
Polygons pp;
this->coverage(angle, &pp);
return pp;
}
/* This method returns the bridge edges (as polylines) that are not supported
but would allow the entire bridge area to be bridged with detected angle
if supported too */
void
BridgeDetector::unsupported_edges(Polylines* unsupported) const
{
if (this->angle == -1) return;
return this->unsupported_edges(this->angle, unsupported);
}
void
BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const
{
if (angle == -1) angle = this->angle;
if (angle == -1) return;
// get bridge edges (both contour and holes)
Polylines bridge_edges;
{
@ -319,6 +319,14 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const
*/
}
Polylines
BridgeDetector::unsupported_edges(double angle) const
{
Polylines pp;
this->unsupported_edges(angle, &pp);
return pp;
}
#ifdef SLIC3RXS
REGISTER_CLASS(BridgeDetector, "BridgeDetector");
#endif

View File

@ -18,10 +18,10 @@ class BridgeDetector {
BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
bool detect_angle();
void coverage(Polygons* coverage) const;
void coverage(double angle, Polygons* coverage) const;
void unsupported_edges(Polylines* unsupported) const;
Polygons coverage(double angle = -1) const;
void unsupported_edges(double angle, Polylines* unsupported) const;
Polylines unsupported_edges(double angle = -1) const;
private:
Polylines _edges; // representing the supporting edges

View File

@ -136,6 +136,15 @@ offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float d
ClipperPaths_to_Slic3rMultiPoints(output, retval);
}
Slic3r::Polygons
offset(const Slic3r::Polygons &polygons, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
{
Slic3r::Polygons pp;
offset(polygons, &pp, delta, scale, joinType, miterLimit);
return pp;
}
void
offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
@ -203,6 +212,15 @@ offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float
ClipperPaths_to_Slic3rExPolygons(output, retval);
}
Slic3r::ExPolygons
offset_ex(const Slic3r::Polygons &polygons, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
{
Slic3r::ExPolygons expp;
offset(polygons, &expp, delta, scale, joinType, miterLimit);
return expp;
}
void
offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
@ -248,6 +266,15 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float
ClipperPaths_to_Slic3rMultiPoints(output, retval);
}
Slic3r::Polygons
offset2(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
{
Slic3r::Polygons pp;
offset2(polygons, &pp, delta1, delta2, scale, joinType, miterLimit);
return pp;
}
void
offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
@ -260,6 +287,15 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const floa
ClipperPaths_to_Slic3rExPolygons(output, retval);
}
Slic3r::ExPolygons
offset2_ex(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
{
Slic3r::ExPolygons expp;
offset2(polygons, &expp, delta1, delta2, scale, joinType, miterLimit);
return expp;
}
template <class T>
void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
const Slic3r::Polygons &clip, T* retval, const ClipperLib::PolyFillType fillType, const bool safety_offset_)
@ -437,6 +473,25 @@ void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType
}
template void diff<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_);
Slic3r::Polygons
diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_)
{
Slic3r::Polygons pp;
diff(subject, clip, &pp, safety_offset_);
return pp;
}
template <class SubjectType, class ClipType>
Slic3r::ExPolygons
diff_ex(const SubjectType &subject, const ClipType &clip, bool safety_offset_)
{
Slic3r::ExPolygons expp;
diff(subject, clip, &expp, safety_offset_);
return expp;
}
template Slic3r::ExPolygons diff_ex<Slic3r::Polygons, Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_);
template Slic3r::ExPolygons diff_ex<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_);
template <class SubjectType, class ResultType>
void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_)
{
@ -448,6 +503,30 @@ template void intersection<Slic3r::Polygons, Slic3r::Polylines>(const Slic3r::Po
template void intersection<Slic3r::Polylines, Slic3r::Polylines>(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_);
template void intersection<Slic3r::Lines, Slic3r::Lines>(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_);
Slic3r::Polygons
intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_)
{
Slic3r::Polygons pp;
intersection(subject, clip, &pp, safety_offset_);
return pp;
}
Slic3r::Polylines
intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_)
{
Slic3r::Polylines pp;
intersection(subject, clip, &pp, safety_offset_);
return pp;
}
Slic3r::ExPolygons
intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_)
{
Slic3r::ExPolygons expp;
intersection(subject, clip, &expp, safety_offset_);
return expp;
}
template <class SubjectType>
bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_)
{
@ -474,6 +553,22 @@ void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_)
template void union_<Slic3r::ExPolygons>(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool safety_offset_);
template void union_<Slic3r::Polygons>(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_);
Slic3r::Polygons
union_(const Slic3r::Polygons &subject, bool safety_offset)
{
Polygons pp;
union_(subject, &pp, safety_offset);
return pp;
}
Slic3r::ExPolygons
union_ex(const Slic3r::Polygons &subject, bool safety_offset)
{
ExPolygons expp;
union_(subject, &expp, safety_offset);
return expp;
}
void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset)
{
Polygons pp = subject1;

View File

@ -40,6 +40,9 @@ void offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const f
void offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta,
double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta,
double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
// offset Polylines
void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta,
@ -55,6 +58,9 @@ void offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const floa
void offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta,
double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta,
double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1,
const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
@ -62,9 +68,15 @@ void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const
void offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1,
const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1,
const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
template <class T>
void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
@ -86,9 +98,18 @@ void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType*
template <class SubjectType, class ResultType>
void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false);
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
template <class SubjectType, class ClipType>
Slic3r::ExPolygons diff_ex(const SubjectType &subject, const ClipType &clip, bool safety_offset_ = false);
template <class SubjectType, class ResultType>
void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false);
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
Slic3r::Polylines intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
template <class SubjectType>
bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
@ -98,6 +119,9 @@ void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r:
template <class T>
void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_ = false);
Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset = false);
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset = false);
void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset = false);
void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_ = false);

View File

@ -1,4 +1,9 @@
#include "Config.hpp"
#include <stdlib.h> // for setenv()
#ifdef _WIN32
#define setenv(k, v, o) _putenv_s(k, v)
#endif
namespace Slic3r {
@ -10,8 +15,7 @@ ConfigBase::has(const t_config_option_key opt_key) {
void
ConfigBase::apply(const ConfigBase &other, bool ignore_nonexistent) {
// get list of option keys to apply
t_config_option_keys opt_keys;
other.keys(&opt_keys);
t_config_option_keys opt_keys = other.keys();
// loop through options and apply them
for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) {
@ -37,8 +41,7 @@ t_config_option_keys
ConfigBase::diff(ConfigBase &other) {
t_config_option_keys diff;
t_config_option_keys my_keys;
this->keys(&my_keys);
t_config_option_keys my_keys = this->keys();
for (t_config_option_keys::const_iterator opt_key = my_keys.begin(); opt_key != my_keys.end(); ++opt_key) {
if (other.has(*opt_key) && other.serialize(*opt_key) != this->serialize(*opt_key)) {
diff.push_back(*opt_key);
@ -98,14 +101,31 @@ ConfigBase::get_abs_value(const t_config_option_key opt_key, double ratio_over)
return opt->get_abs_value(ratio_over);
}
void
ConfigBase::setenv_()
{
t_config_option_keys opt_keys = this->keys();
for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) {
// prepend the SLIC3R_ prefix
std::ostringstream ss;
ss << "SLIC3R_";
ss << *it;
std::string envname = ss.str();
// capitalize environment variable name
for (size_t i = 0; i < envname.size(); ++i)
envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i];
setenv(envname.c_str(), this->serialize(*it).c_str(), 1);
}
}
#ifdef SLIC3RXS
SV*
ConfigBase::as_hash() {
HV* hv = newHV();
t_config_option_keys opt_keys;
this->keys(&opt_keys);
t_config_option_keys opt_keys = this->keys();
for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it)
(void)hv_store( hv, it->c_str(), it->length(), this->get(*it), 0 );
@ -368,10 +388,12 @@ DynamicConfig::option(const t_config_option_key opt_key) const {
return const_cast<DynamicConfig*>(this)->option(opt_key, false);
}
void
DynamicConfig::keys(t_config_option_keys *keys) const {
t_config_option_keys
DynamicConfig::keys() const {
t_config_option_keys keys;
for (t_options_map::const_iterator it = this->options.begin(); it != this->options.end(); ++it)
keys->push_back(it->first);
keys.push_back(it->first);
return keys;
}
void
@ -379,12 +401,14 @@ DynamicConfig::erase(const t_config_option_key opt_key) {
this->options.erase(opt_key);
}
void
StaticConfig::keys(t_config_option_keys *keys) const {
t_config_option_keys
StaticConfig::keys() const {
t_config_option_keys keys;
for (t_optiondef_map::const_iterator it = this->def->begin(); it != this->def->end(); ++it) {
const ConfigOption* opt = this->option(it->first);
if (opt != NULL) keys->push_back(it->first);
if (opt != NULL) keys.push_back(it->first);
}
return keys;
}
const ConfigOption*

View File

@ -515,7 +515,7 @@ class ConfigBase
bool has(const t_config_option_key opt_key);
virtual ConfigOption* option(const t_config_option_key opt_key, bool create = false) = 0;
virtual const ConfigOption* option(const t_config_option_key opt_key) const = 0;
virtual void keys(t_config_option_keys *keys) const = 0;
virtual t_config_option_keys keys() const = 0;
void apply(const ConfigBase &other, bool ignore_nonexistent = false);
bool equals(ConfigBase &other);
t_config_option_keys diff(ConfigBase &other);
@ -524,6 +524,7 @@ class ConfigBase
void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false);
double get_abs_value(const t_config_option_key opt_key);
double get_abs_value(const t_config_option_key opt_key, double ratio_over);
void setenv_();
#ifdef SLIC3RXS
SV* as_hash();
@ -545,7 +546,7 @@ class DynamicConfig : public ConfigBase
template<class T> T* opt(const t_config_option_key opt_key, bool create = false);
ConfigOption* option(const t_config_option_key opt_key, bool create = false);
const ConfigOption* option(const t_config_option_key opt_key) const;
void keys(t_config_option_keys *keys) const;
t_config_option_keys keys() const;
void erase(const t_config_option_key opt_key);
private:
@ -556,7 +557,7 @@ class DynamicConfig : public ConfigBase
class StaticConfig : public ConfigBase
{
public:
void keys(t_config_option_keys *keys) const;
t_config_option_keys keys() const;
virtual ConfigOption* option(const t_config_option_key opt_key, bool create = false) = 0;
const ConfigOption* option(const t_config_option_key opt_key) const;

View File

@ -122,6 +122,13 @@ ExPolygon::has_boundary_point(const Point &point) const
return false;
}
void
ExPolygon::simplify_p(double tolerance, Polygons* polygons) const
{
Polygons pp = this->simplify_p(tolerance);
polygons->insert(polygons->end(), pp.begin(), pp.end());
}
Polygons
ExPolygon::simplify_p(double tolerance) const
{
@ -180,16 +187,17 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines)
}
// compute the Voronoi diagram
ma.build(polylines);
Polylines pp;
ma.build(&pp);
// clip segments to our expolygon area
// (do this before extending endpoints as external segments coule be extended into
// expolygon, this leaving wrong things inside)
intersection(*polylines, *this, polylines);
pp = intersection(pp, *this);
// extend initial and final segments of each polyline (they will be clipped)
// unless they represent closed loops
for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) {
for (Polylines::iterator polyline = pp.begin(); polyline != pp.end(); ++polyline) {
if (polyline->points.front().distance_to(polyline->points.back()) < min_width) continue;
// TODO: we should *not* extend endpoints where other polylines start/end
// (such as T joints, which are returned as three polylines by MedialAxis)
@ -198,18 +206,20 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines)
}
// clip again after extending endpoints to prevent them from exceeding the expolygon boundaries
intersection(*polylines, *this, polylines);
pp = intersection(pp, *this);
// remove too short polylines
// (we can't do this check before endpoints extension and clipping because we don't
// know how long will the endpoints be extended since it depends on polygon thickness
// which is variable - extension will be <= max_width/2 on each side)
for (size_t i = 0; i < polylines->size(); ++i) {
if ((*polylines)[i].length() < max_width) {
polylines->erase(polylines->begin() + i);
for (size_t i = 0; i < pp.size(); ++i) {
if (pp[i].length() < max_width) {
pp.erase(pp.begin() + i);
--i;
}
}
polylines->insert(polylines->end(), pp.begin(), pp.end());
}
void

View File

@ -27,6 +27,7 @@ class ExPolygon
bool contains(const Point &point) const;
bool contains_b(const Point &point) const;
bool has_boundary_point(const Point &point) const;
void simplify_p(double tolerance, Polygons* polygons) const;
Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons &expolygons) const;

View File

@ -122,6 +122,12 @@ ExPolygonCollection::contours() const
return contours;
}
void
ExPolygonCollection::append(const ExPolygons &expp)
{
this->expolygons.insert(this->expolygons.end(), expp.begin(), expp.end());
}
#ifdef SLIC3RXS
REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection");
#endif

View File

@ -31,6 +31,7 @@ class ExPolygonCollection
Polygon convex_hull() const;
Lines lines() const;
Polygons contours() const;
void append(const ExPolygons &expolygons);
};
}

View File

@ -3,6 +3,7 @@
#include "ExPolygonCollection.hpp"
#include "ClipperUtils.hpp"
#include "Extruder.hpp"
#include <cmath>
#include <sstream>
namespace Slic3r {
@ -113,57 +114,6 @@ ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCo
REGISTER_CLASS(ExtrusionPath, "ExtrusionPath");
#endif
std::string
ExtrusionPath::gcode(Extruder* extruder, double e, double F,
double xofs, double yofs, std::string extrusion_axis,
std::string gcode_line_suffix) const
{
dSP;
std::stringstream stream;
stream.setf(std::ios::fixed);
double local_F = F;
Lines lines = this->polyline.lines();
for (Lines::const_iterator line_it = lines.begin();
line_it != lines.end(); ++line_it)
{
const double line_length = line_it->length() * SCALING_FACTOR;
// calculate extrusion length for this line
double E = 0;
if (e > 0) {
extruder->extrude(e * line_length);
E = extruder->E;
}
// compose G-code line
Point point = line_it->b;
const double x = point.x * SCALING_FACTOR + xofs;
const double y = point.y * SCALING_FACTOR + yofs;
stream.precision(3);
stream << "G1 X" << x << " Y" << y;
if (E != 0) {
stream.precision(5);
stream << " " << extrusion_axis << E;
}
if (local_F != 0) {
stream.precision(3);
stream << " F" << local_F;
local_F = 0;
}
stream << gcode_line_suffix;
stream << "\n";
}
return stream.str();
}
Polygons
ExtrusionPath::grow() const
{
@ -375,6 +325,20 @@ ExtrusionLoop::grow() const
return pp;
}
double
ExtrusionLoop::min_mm3_per_mm() const
{
double min_mm3_per_mm = 0;
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
if (min_mm3_per_mm == 0) {
min_mm3_per_mm = path->mm3_per_mm;
} else {
min_mm3_per_mm = fmin(min_mm3_per_mm, path->mm3_per_mm);
}
}
return min_mm3_per_mm;
}
#ifdef SLIC3RXS
REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop");
#endif

View File

@ -13,6 +13,7 @@ class Extruder;
/* Each ExtrusionRole value identifies a distinct set of { extruder, speed } */
enum ExtrusionRole {
erNone,
erPerimeter,
erExternalPerimeter,
erOverhangPerimeter,
@ -50,6 +51,8 @@ class ExtrusionEntity
virtual Point first_point() const = 0;
virtual Point last_point() const = 0;
virtual Polygons grow() const = 0;
virtual double min_mm3_per_mm() const = 0;
virtual Polyline as_polyline() const = 0;
};
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
@ -77,10 +80,13 @@ class ExtrusionPath : public ExtrusionEntity
bool is_infill() const;
bool is_solid_infill() const;
bool is_bridge() const;
std::string gcode(Extruder* extruder, double e, double F,
double xofs, double yofs, std::string extrusion_axis,
std::string gcode_line_suffix) const;
Polygons grow() const;
double min_mm3_per_mm() const {
return this->mm3_per_mm;
};
Polyline as_polyline() const {
return this->polyline;
};
private:
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
@ -95,6 +101,8 @@ class ExtrusionLoop : public ExtrusionEntity
ExtrusionLoopRole role;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {};
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault)
: paths(paths), role(role) {};
bool is_loop() const {
return true;
};
@ -117,6 +125,10 @@ class ExtrusionLoop : public ExtrusionEntity
bool is_infill() const;
bool is_solid_infill() const;
Polygons grow() const;
double min_mm3_per_mm() const;
Polyline as_polyline() const {
return this->polygon().split_at_first_point();
};
};
}

View File

@ -1,5 +1,6 @@
#include "ExtrusionEntityCollection.hpp"
#include <algorithm>
#include <cmath>
#include <map>
namespace Slic3r {
@ -7,9 +8,13 @@ namespace Slic3r {
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionEntityCollection& collection)
: no_sort(collection.no_sort), orig_indices(collection.orig_indices)
{
this->entities.reserve(collection.entities.size());
for (ExtrusionEntitiesPtr::const_iterator it = collection.entities.begin(); it != collection.entities.end(); ++it)
this->entities.push_back((*it)->clone());
this->append(collection.entities);
}
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
: no_sort(false)
{
this->append(paths);
}
ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const ExtrusionEntityCollection &other)
@ -27,10 +32,30 @@ ExtrusionEntityCollection::swap (ExtrusionEntityCollection &c)
std::swap(this->no_sort, c.no_sort);
}
ExtrusionEntityCollection::~ExtrusionEntityCollection()
{
for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it)
delete *it;
}
ExtrusionEntityCollection::operator ExtrusionPaths() const
{
ExtrusionPaths paths;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(*it))
paths.push_back(*path);
}
return paths;
}
ExtrusionEntityCollection*
ExtrusionEntityCollection::clone() const
{
return new ExtrusionEntityCollection(*this);
ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this);
for (size_t i = 0; i < coll->entities.size(); ++i) {
coll->entities[i] = this->entities[i]->clone();
}
return coll;
}
void
@ -56,6 +81,34 @@ ExtrusionEntityCollection::last_point() const
return this->entities.back()->last_point();
}
void
ExtrusionEntityCollection::append(const ExtrusionEntity &entity)
{
this->entities.push_back(entity.clone());
}
void
ExtrusionEntityCollection::append(const ExtrusionEntitiesPtr &entities)
{
for (ExtrusionEntitiesPtr::const_iterator ptr = entities.begin(); ptr != entities.end(); ++ptr)
this->append(**ptr);
}
void
ExtrusionEntityCollection::append(const ExtrusionPaths &paths)
{
for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path)
this->append(*path);
}
ExtrusionEntityCollection
ExtrusionEntityCollection::chained_path(bool no_reverse, std::vector<size_t>* orig_indices) const
{
ExtrusionEntityCollection coll;
this->chained_path(&coll, no_reverse, orig_indices);
return coll;
}
void
ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, std::vector<size_t>* orig_indices) const
{
@ -137,6 +190,43 @@ ExtrusionEntityCollection::items_count() const
return count;
}
/* Returns a single vector of pointers to all non-collection items contained in this one */
void
ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const
{
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if ((*it)->is_collection()) {
ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it);
retval->append(collection->flatten().entities);
} else {
retval->append(**it);
}
}
}
ExtrusionEntityCollection
ExtrusionEntityCollection::flatten() const
{
ExtrusionEntityCollection coll;
this->flatten(&coll);
return coll;
}
double
ExtrusionEntityCollection::min_mm3_per_mm() const
{
double min_mm3_per_mm = 0;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
double mm3_per_mm = (*it)->min_mm3_per_mm();
if (min_mm3_per_mm == 0) {
min_mm3_per_mm = mm3_per_mm;
} else {
min_mm3_per_mm = fmin(min_mm3_per_mm, mm3_per_mm);
}
}
return min_mm3_per_mm;
}
#ifdef SLIC3RXS
// there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection
REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection");

View File

@ -10,19 +10,33 @@ class ExtrusionEntityCollection : public ExtrusionEntity
{
public:
ExtrusionEntityCollection* clone() const;
ExtrusionEntitiesPtr entities;
ExtrusionEntitiesPtr entities; // we own these entities
std::vector<size_t> orig_indices; // handy for XS
bool no_sort;
ExtrusionEntityCollection(): no_sort(false) {};
ExtrusionEntityCollection(const ExtrusionEntityCollection &collection);
ExtrusionEntityCollection(const ExtrusionPaths &paths);
ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other);
~ExtrusionEntityCollection();
operator ExtrusionPaths() const;
bool is_collection() const {
return true;
};
bool can_reverse() const {
return !this->no_sort;
};
bool empty() const {
return this->entities.empty();
};
void clear() {
this->entities.clear();
};
void swap (ExtrusionEntityCollection &c);
void append(const ExtrusionEntity &entity);
void append(const ExtrusionEntitiesPtr &entities);
void append(const ExtrusionPaths &paths);
ExtrusionEntityCollection chained_path(bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
void reverse();
@ -30,6 +44,13 @@ class ExtrusionEntityCollection : public ExtrusionEntity
Point last_point() const;
Polygons grow() const;
size_t items_count() const;
void flatten(ExtrusionEntityCollection* retval) const;
ExtrusionEntityCollection flatten() const;
double min_mm3_per_mm() const;
Polyline as_polyline() const {
CONFESS("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline();
};
};
}

774
xs/src/libslic3r/GCode.cpp Normal file
View File

@ -0,0 +1,774 @@
#include "GCode.hpp"
#include "ExtrusionEntity.hpp"
#include <algorithm>
#include <cstdlib>
namespace Slic3r {
AvoidCrossingPerimeters::AvoidCrossingPerimeters()
: use_external_mp(false), use_external_mp_once(false), disable_once(true),
_external_mp(NULL), _layer_mp(NULL)
{
}
AvoidCrossingPerimeters::~AvoidCrossingPerimeters()
{
if (this->_external_mp != NULL)
delete this->_external_mp;
if (this->_layer_mp != NULL)
delete this->_layer_mp;
}
void
AvoidCrossingPerimeters::init_external_mp(const ExPolygons &islands)
{
if (this->_external_mp != NULL)
delete this->_external_mp;
this->_external_mp = new MotionPlanner(islands);
}
void
AvoidCrossingPerimeters::init_layer_mp(const ExPolygons &islands)
{
if (this->_layer_mp != NULL)
delete this->_layer_mp;
this->_layer_mp = new MotionPlanner(islands);
}
Polyline
AvoidCrossingPerimeters::travel_to(GCode &gcodegen, Point point)
{
if (this->use_external_mp || this->use_external_mp_once) {
// get current origin set in gcodegen
// (the one that will be used to translate the G-code coordinates by)
Point scaled_origin = Point::new_scale(gcodegen.origin.x, gcodegen.origin.y);
// represent last_pos in absolute G-code coordinates
Point last_pos = gcodegen.last_pos();
last_pos.translate(scaled_origin);
// represent point in absolute G-code coordinates
point.translate(scaled_origin);
// calculate path
Polyline travel = this->_external_mp->shortest_path(last_pos, point);
// translate the path back into the shifted coordinate system that gcodegen
// is currently using for writing coordinates
travel.translate(scaled_origin.negative());
return travel;
} else {
return this->_layer_mp->shortest_path(gcodegen.last_pos(), point);
}
}
#ifdef SLIC3RXS
REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters");
#endif
OozePrevention::OozePrevention()
: enable(false)
{
}
std::string
OozePrevention::pre_toolchange(GCode &gcodegen)
{
std::string gcode;
// move to the nearest standby point
if (!this->standby_points.empty()) {
// get current position in print coordinates
Pointf3 writer_pos = gcodegen.writer.get_position();
Point pos = Point::new_scale(writer_pos.x, writer_pos.y);
// find standby point
Point standby_point;
pos.nearest_point(this->standby_points, &standby_point);
/* We don't call gcodegen.travel_to() because we don't need retraction (it was already
triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
of the destination point must not be transformed by origin nor current extruder offset. */
gcode += gcodegen.writer.travel_to_xy(Pointf::new_unscale(standby_point),
"move to standby position");
}
if (gcodegen.config.standby_temperature_delta.value != 0) {
// we assume that heating is always slower than cooling, so no need to block
gcode += gcodegen.writer.set_temperature
(this->_get_temp(gcodegen) + gcodegen.config.standby_temperature_delta.value, false);
}
return gcode;
}
std::string
OozePrevention::post_toolchange(GCode &gcodegen)
{
std::string gcode;
if (gcodegen.config.standby_temperature_delta.value != 0) {
gcode += gcodegen.writer.set_temperature(this->_get_temp(gcodegen), true);
}
return gcode;
}
int
OozePrevention::_get_temp(GCode &gcodegen)
{
return (gcodegen.layer != NULL && gcodegen.layer->id() == 0)
? gcodegen.config.first_layer_temperature.get_at(gcodegen.writer.extruder()->id)
: gcodegen.config.temperature.get_at(gcodegen.writer.extruder()->id);
}
#ifdef SLIC3RXS
REGISTER_CLASS(OozePrevention, "GCode::OozePrevention");
#endif
Wipe::Wipe()
: enable(false)
{
}
bool
Wipe::has_path()
{
return !this->path.points.empty();
}
void
Wipe::reset_path()
{
this->path = Polyline();
}
std::string
Wipe::wipe(GCode &gcodegen, bool toolchange)
{
std::string gcode;
/* Reduce feedrate a bit; travel speed is often too high to move on existing material.
Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
double wipe_speed = gcodegen.writer.config.travel_speed.value * 0.8;
// get the retraction length
double length = toolchange
? gcodegen.writer.extruder()->retract_length_toolchange()
: gcodegen.writer.extruder()->retract_length();
if (length > 0) {
/* Calculate how long we need to travel in order to consume the required
amount of retraction. In other words, how far do we move in XY at wipe_speed
for the time needed to consume retract_length at retract_speed? */
double wipe_dist = scale_(length / gcodegen.writer.extruder()->retract_speed() * wipe_speed);
/* Take the stored wipe path and replace first point with the current actual position
(they might be different, for example, in case of loop clipping). */
Polyline wipe_path;
wipe_path.append(gcodegen.last_pos());
wipe_path.append(
this->path.points.begin() + 1,
this->path.points.end()
);
wipe_path.clip_end(wipe_path.length() - wipe_dist);
// subdivide the retraction in segments
double retracted = 0;
Lines lines = wipe_path.lines();
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
double segment_length = line->length();
/* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
due to rounding (TODO: test and/or better math for this) */
double dE = length * (segment_length / wipe_dist) * 0.95;
gcode += gcodegen.writer.set_speed(wipe_speed*60);
gcode += gcodegen.writer.extrude_to_xy(
gcodegen.point_to_gcode(line->b),
-dE,
(std::string)"wipe and retract" + (gcodegen.enable_cooling_markers ? ";_WIPE" : "")
);
retracted += dE;
}
gcodegen.writer.extruder()->retracted += retracted;
// prevent wiping again on same path
this->reset_path();
}
return gcode;
}
#ifdef SLIC3RXS
REGISTER_CLASS(Wipe, "GCode::Wipe");
#endif
#define EXTRUDER_CONFIG(OPT) this->config.OPT.get_at(this->writer.extruder()->id)
GCode::GCode()
: enable_loop_clipping(true), enable_cooling_markers(false), layer_count(0),
layer_index(-1), first_layer(false), elapsed_time(0), volumetric_speed(0),
_last_pos_defined(false), layer(NULL), placeholder_parser(NULL)
{
}
Point&
GCode::last_pos()
{
return this->_last_pos;
}
void
GCode::set_last_pos(const Point &pos)
{
this->_last_pos = pos;
this->_last_pos_defined = true;
}
bool
GCode::last_pos_defined() const
{
return this->_last_pos_defined;
}
void
GCode::apply_print_config(const PrintConfig &print_config)
{
this->writer.apply_print_config(print_config);
this->config.apply(print_config);
}
void
GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
{
this->writer.set_extruders(extruder_ids);
// enable wipe path generation if any extruder has wipe enabled
this->wipe.enable = false;
for (std::vector<unsigned int>::const_iterator it = extruder_ids.begin();
it != extruder_ids.end(); ++it) {
if (this->config.wipe.get_at(*it)) {
this->wipe.enable = true;
break;
}
}
}
void
GCode::set_origin(const Pointf &pointf)
{
// if origin increases (goes towards right), last_pos decreases because it goes towards left
Point translate(
scale_(this->origin.x - pointf.x),
scale_(this->origin.y - pointf.y)
);
this->_last_pos.translate(translate);
this->wipe.path.translate(translate);
this->origin = pointf;
}
std::string
GCode::preamble()
{
std::string gcode = this->writer.preamble();
/* Perform a *silent* move to z_offset: we need this to initialize the Z
position of our writer object so that any initial lift taking place
before the first layer change will raise the extruder from the correct
initial Z instead of 0. */
this->writer.travel_to_z(this->config.z_offset.value);
return gcode;
}
std::string
GCode::change_layer(const Layer &layer)
{
this->layer = &layer;
this->layer_index++;
this->first_layer = (layer.id() == 0);
// avoid computing islands and overhangs if they're not needed
if (this->config.avoid_crossing_perimeters) {
ExPolygons islands;
union_(layer.slices, &islands, true);
this->avoid_crossing_perimeters.init_layer_mp(islands);
}
std::string gcode;
if (this->layer_count > 0) {
gcode += this->writer.update_progress(this->layer_index, this->layer_count);
}
coordf_t z = layer.print_z + this->config.z_offset.value; // in unscaled coordinates
if (EXTRUDER_CONFIG(retract_layer_change) && this->writer.will_move_z(z)) {
gcode += this->retract();
}
{
std::ostringstream comment;
comment << "move to next layer (" << this->layer_index << ")";
gcode += this->writer.travel_to_z(z, comment.str());
}
// forget last wiping path as wiping after raising Z is pointless
this->wipe.reset_path();
return gcode;
}
std::string
GCode::extrude(ExtrusionLoop loop, std::string description, double speed)
{
// get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation
// extrude all loops ccw
bool was_clockwise = loop.make_counter_clockwise();
// find the point of the loop that is closest to the current extruder position
// or randomize if requested
Point last_pos = this->last_pos();
if (this->config.spiral_vase) {
loop.split_at(last_pos);
} else if (this->config.seam_position == spNearest || this->config.seam_position == spAligned) {
Polygon polygon = loop.polygon();
// simplify polygon in order to skip false positives in concave/convex detection
// (loop is always ccw as polygon.simplify() only works on ccw polygons)
Polygons simplified = polygon.simplify(scale_(EXTRUDER_CONFIG(nozzle_diameter))/2);
// restore original winding order so that concave and convex detection always happens
// on the right/outer side of the polygon
if (was_clockwise) {
for (Polygons::iterator p = simplified.begin(); p != simplified.end(); ++p)
p->reverse();
}
// concave vertices have priority
Points candidates;
for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) {
Points concave = p->concave_points(PI*4/3);
candidates.insert(candidates.end(), concave.begin(), concave.end());
}
// if no concave points were found, look for convex vertices
if (candidates.empty()) {
for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) {
Points convex = p->convex_points(PI*2/3);
candidates.insert(candidates.end(), convex.begin(), convex.end());
}
}
// retrieve the last start position for this object
if (this->layer != NULL && this->_seam_position.count(this->layer->object()) > 0) {
last_pos = this->_seam_position[this->layer->object()];
}
Point point;
if (this->config.seam_position == spNearest) {
if (candidates.empty()) candidates = polygon.points;
last_pos.nearest_point(candidates, &point);
// On 32-bit Linux, Clipper will change some point coordinates by 1 unit
// while performing simplify_polygons(), thus split_at_vertex() won't
// find them anymore.
if (!loop.split_at_vertex(point)) loop.split_at(point);
} else if (!candidates.empty()) {
Points non_overhang;
for (Points::const_iterator p = candidates.begin(); p != candidates.end(); ++p) {
if (!loop.has_overhang_point(*p))
non_overhang.push_back(*p);
}
if (!non_overhang.empty())
candidates = non_overhang;
last_pos.nearest_point(candidates, &point);
if (!loop.split_at_vertex(point)) loop.split_at(point); // see note above
} else {
point = last_pos.projection_onto(polygon);
loop.split_at(point);
}
if (this->layer != NULL)
this->_seam_position[this->layer->object()] = point;
} else if (this->config.seam_position == spRandom) {
if (loop.role == elrContourInternalPerimeter) {
Polygon polygon = loop.polygon();
Point centroid = polygon.centroid();
last_pos = Point(polygon.bounding_box().max.x, centroid.y);
last_pos.rotate(rand() % 2*PI, centroid);
}
loop.split_at(last_pos);
}
// clip the path to avoid the extruder to get exactly on the first point of the loop;
// if polyline was shorter than the clipping distance we'd get a null polyline, so
// we discard it in that case
double clip_length = this->enable_loop_clipping
? scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
: 0;
// get paths
ExtrusionPaths paths;
loop.clip_end(clip_length, &paths);
if (paths.empty()) return "";
// apply the small perimeter speed
if (paths.front().is_perimeter() && loop.length() <= SMALL_PERIMETER_LENGTH) {
if (speed == -1) speed = this->config.get_abs_value("small_perimeter_speed");
}
// extrude along the path
std::string gcode;
for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path)
gcode += this->_extrude(*path, description, speed);
// reset acceleration
gcode += this->writer.set_acceleration(this->config.default_acceleration.value);
if (this->wipe.enable)
this->wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path
// make a little move inwards before leaving loop
if (paths.back().role == erExternalPerimeter && this->layer != NULL && this->config.perimeters > 1) {
Polyline &last_path_polyline = paths.back().polyline;
// detect angle between last and first segment
// the side depends on the original winding order of the polygon (left for contours, right for holes)
Point a = paths.front().polyline.points[1]; // second point
Point b = *(paths.back().polyline.points.end()-3); // second to last point
if (was_clockwise) {
// swap points
Point c = a; a = b; b = c;
}
double angle = paths.front().first_point().ccw_angle(a, b) / 3;
// turn left if contour, turn right if hole
if (was_clockwise) angle *= -1;
// create the destination point along the first segment and rotate it
// we make sure we don't exceed the segment length because we don't know
// the rotation of the second segment so we might cross the object boundary
Line first_segment(
paths.front().polyline.points[0],
paths.front().polyline.points[1]
);
double distance = std::min(
scale_(EXTRUDER_CONFIG(nozzle_diameter)),
first_segment.length()
);
Point point = first_segment.point_at(distance);
point.rotate(angle, first_segment.a);
// generate the travel move
gcode += this->writer.travel_to_xy(this->point_to_gcode(point), "move inwards before travel");
}
return gcode;
}
std::string
GCode::extrude(const ExtrusionEntity &entity, std::string description, double speed)
{
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity)) {
return this->extrude(*path, description, speed);
} else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity)) {
return this->extrude(*loop, description, speed);
} else {
CONFESS("Invalid argument supplied to extrude()");
return "";
}
}
std::string
GCode::extrude(const ExtrusionPath &path, std::string description, double speed)
{
std::string gcode = this->_extrude(path, description, speed);
// reset acceleration
gcode += this->writer.set_acceleration(this->config.default_acceleration.value);
return gcode;
}
std::string
GCode::_extrude(ExtrusionPath path, std::string description, double speed)
{
path.simplify(SCALED_RESOLUTION);
std::string gcode;
// go to first point of extrusion path
if (!this->_last_pos_defined || !this->_last_pos.coincides_with(path.first_point())) {
gcode += this->travel_to(
path.first_point(),
path.role,
"move to first " + description + " point"
);
}
// compensate retraction
gcode += this->unretract();
// adjust acceleration
{
double acceleration;
if (this->config.first_layer_acceleration.value > 0 && this->first_layer) {
acceleration = this->config.first_layer_acceleration.value;
} else if (this->config.perimeter_acceleration.value > 0 && path.is_perimeter()) {
acceleration = this->config.perimeter_acceleration.value;
} else if (this->config.bridge_acceleration.value > 0 && path.is_bridge()) {
acceleration = this->config.bridge_acceleration.value;
} else if (this->config.infill_acceleration.value > 0 && path.is_infill()) {
acceleration = this->config.infill_acceleration.value;
} else {
acceleration = this->config.default_acceleration.value;
}
gcode += this->writer.set_acceleration(acceleration);
}
// calculate extrusion length per distance unit
double e_per_mm = this->writer.extruder()->e_per_mm3 * path.mm3_per_mm;
if (this->writer.extrusion_axis().empty()) e_per_mm = 0;
// set speed
if (speed == -1) {
if (path.role == erPerimeter) {
speed = this->config.get_abs_value("perimeter_speed");
} else if (path.role == erExternalPerimeter) {
speed = this->config.get_abs_value("external_perimeter_speed");
} else if (path.role == erOverhangPerimeter || path.role == erBridgeInfill) {
speed = this->config.get_abs_value("bridge_speed");
} else if (path.role == erInternalInfill) {
speed = this->config.get_abs_value("infill_speed");
} else if (path.role == erSolidInfill) {
speed = this->config.get_abs_value("solid_infill_speed");
} else if (path.role == erTopSolidInfill) {
speed = this->config.get_abs_value("top_solid_infill_speed");
} else if (path.role == erGapFill) {
speed = this->config.get_abs_value("gap_fill_speed");
} else {
CONFESS("Invalid speed");
}
}
if (this->first_layer) {
speed = this->config.get_abs_value("first_layer_speed", speed);
}
if (this->volumetric_speed != 0 && speed == 0) {
speed = this->volumetric_speed / path.mm3_per_mm;
}
if (this->config.max_volumetric_speed.value > 0) {
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
speed = std::min(
speed,
this->config.max_volumetric_speed.value / path.mm3_per_mm
);
}
double F = speed * 60; // convert mm/sec to mm/min
// extrude arc or line
if (path.is_bridge() && this->enable_cooling_markers)
gcode += ";_BRIDGE_FAN_START\n";
gcode += this->writer.set_speed(F);
double path_length = 0;
{
std::string comment = this->config.gcode_comments ? (" ; " + description) : "";
Lines lines = path.polyline.lines();
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
const double line_length = line->length() * SCALING_FACTOR;
path_length += line_length;
gcode += this->writer.extrude_to_xy(
this->point_to_gcode(line->b),
e_per_mm * line_length,
comment
);
}
}
if (this->wipe.enable) {
this->wipe.path = path.polyline;
this->wipe.path.reverse();
}
if (path.is_bridge() && this->enable_cooling_markers)
gcode += ";_BRIDGE_FAN_END\n";
this->set_last_pos(path.last_point());
if (this->config.cooling)
this->elapsed_time += path_length / F * 60;
return gcode;
}
// This method accepts &point in print coordinates.
std::string
GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment)
{
/* Define the travel move as a line between current position and the taget point.
This is expressed in print coordinates, so it will need to be translated by
this->origin in order to get G-code coordinates. */
Polyline travel;
travel.append(this->last_pos());
travel.append(point);
// check whether a straight travel move would need retraction
bool needs_retraction = this->needs_retraction(travel, role);
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
// multi-hop travel path inside the configuration space
if (needs_retraction
&& this->config.avoid_crossing_perimeters
&& !this->avoid_crossing_perimeters.disable_once) {
travel = this->avoid_crossing_perimeters.travel_to(*this, point);
// check again whether the new travel path still needs a retraction
needs_retraction = this->needs_retraction(travel, role);
}
// Re-allow avoid_crossing_perimeters for the next travel moves
this->avoid_crossing_perimeters.disable_once = false;
this->avoid_crossing_perimeters.use_external_mp_once = false;
// generate G-code for the travel move
std::string gcode;
if (needs_retraction) gcode += this->retract();
// use G1 because we rely on paths being straight (G0 may make round paths)
Lines lines = travel.lines();
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line)
gcode += this->writer.travel_to_xy(this->point_to_gcode(line->b), comment);
return gcode;
}
bool
GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
{
if (travel.length() < scale_(EXTRUDER_CONFIG(retract_before_travel))) {
// skip retraction if the move is shorter than the configured threshold
return false;
}
if (role == erSupportMaterial) {
const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(this->layer);
if (support_layer != NULL && support_layer->support_islands.contains(travel)) {
// skip retraction if this is a travel move inside a support material island
return false;
}
}
if (this->config.only_retract_when_crossing_perimeters && this->layer != NULL) {
if (this->config.fill_density.value > 0
&& this->layer->any_internal_region_slice_contains(travel)) {
/* skip retraction if travel is contained in an internal slice *and*
internal infill is enabled (so that stringing is entirely not visible) */
return false;
} else if (this->layer->any_bottom_region_slice_contains(travel)
&& this->layer->upper_layer != NULL
&& this->layer->upper_layer->slices.contains(travel)
&& (this->config.bottom_solid_layers.value >= 2 || this->config.fill_density.value > 0)) {
/* skip retraction if travel is contained in an *infilled* bottom slice
but only if it's also covered by an *infilled* upper layer's slice
so that it's not visible from above (a bottom surface might not have an
upper slice in case of a thin membrane) */
return false;
}
}
// retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
return true;
}
std::string
GCode::retract(bool toolchange)
{
std::string gcode;
if (this->writer.extruder() == NULL)
return gcode;
// wipe (if it's enabled for this extruder and we have a stored wipe path)
if (EXTRUDER_CONFIG(wipe) && this->wipe.has_path()) {
gcode += this->wipe.wipe(*this, toolchange);
}
/* The parent class will decide whether we need to perform an actual retraction
(the extruder might be already retracted fully or partially). We call these
methods even if we performed wipe, since this will ensure the entire retraction
length is honored in case wipe path was too short. */
gcode += toolchange ? this->writer.retract_for_toolchange() : this->writer.retract();
gcode += this->writer.reset_e();
if (this->writer.extruder()->retract_length() > 0 || this->config.use_firmware_retraction)
gcode += this->writer.lift();
return gcode;
}
std::string
GCode::unretract()
{
std::string gcode;
gcode += this->writer.unlift();
gcode += this->writer.unretract();
return gcode;
}
std::string
GCode::set_extruder(unsigned int extruder_id)
{
this->placeholder_parser->set("current_extruder", extruder_id);
if (!this->writer.need_toolchange(extruder_id))
return "";
// if we are running a single-extruder setup, just set the extruder and return nothing
if (!this->writer.multiple_extruders) {
return this->writer.toolchange(extruder_id);
}
// prepend retraction on the current extruder
std::string gcode = this->retract(true);
// append custom toolchange G-code
if (this->writer.extruder() != NULL && !this->config.toolchange_gcode.value.empty()) {
PlaceholderParser pp = *this->placeholder_parser;
pp.set("previous_extruder", this->writer.extruder()->id);
pp.set("next_extruder", extruder_id);
gcode += pp.process(this->config.toolchange_gcode.value) + '\n';
}
// if ooze prevention is enabled, park current extruder in the nearest
// standby point and set it to the standby temperature
if (this->ooze_prevention.enable && this->writer.extruder() != NULL)
gcode += this->ooze_prevention.pre_toolchange(*this);
// append the toolchange command
gcode += this->writer.toolchange(extruder_id);
// set the new extruder to the operating temperature
if (this->ooze_prevention.enable)
gcode += this->ooze_prevention.post_toolchange(*this);
return gcode;
}
// convert a model-space scaled point into G-code coordinates
Pointf
GCode::point_to_gcode(const Point &point)
{
Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset);
return Pointf(
unscale(point.x) + this->origin.x - extruder_offset.x,
unscale(point.y) + this->origin.y - extruder_offset.y
);
}
#ifdef SLIC3RXS
REGISTER_CLASS(GCode, "GCode");
#endif
}

View File

@ -2,32 +2,112 @@
#define slic3r_GCode_hpp_
#include <myinit.h>
#include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "Layer.hpp"
#include "MotionPlanner.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
#include "Print.hpp"
#include "PrintConfig.hpp"
#include <string>
namespace Slic3r {
// draft for a binary representation of a G-code line
class GCode;
enum GCodeCmdType {
gcctSyncMotion,
gcctExtrude,
gcctResetE,
gcctSetTemp,
gcctSetTempWait,
gcctToolchange,
gcctCustom
class AvoidCrossingPerimeters {
public:
// this flag triggers the use of the external configuration space
bool use_external_mp;
bool use_external_mp_once; // just for the next travel move
// this flag disables avoid_crossing_perimeters just for the next travel move
// we enable it by default for the first travel move in print
bool disable_once;
AvoidCrossingPerimeters();
~AvoidCrossingPerimeters();
void init_external_mp(const ExPolygons &islands);
void init_layer_mp(const ExPolygons &islands);
Polyline travel_to(GCode &gcodegen, Point point);
private:
MotionPlanner* _external_mp;
MotionPlanner* _layer_mp;
};
class GCodeCmd {
class OozePrevention {
public:
GCodeCmdType type;
float X, Y, Z, E, F;
unsigned short T, S;
std::string custom, comment;
float xy_dist; // cache
bool enable;
Points standby_points;
GCodeCmd(GCodeCmdType type)
: type(type), X(0), Y(0), Z(0), E(0), F(0), T(-1), S(0), xy_dist(-1) {};
OozePrevention();
std::string pre_toolchange(GCode &gcodegen);
std::string post_toolchange(GCode &gcodegen);
private:
int _get_temp(GCode &gcodegen);
};
class Wipe {
public:
bool enable;
Polyline path;
Wipe();
bool has_path();
void reset_path();
std::string wipe(GCode &gcodegen, bool toolchange = false);
};
class GCode {
public:
/* Origin of print coordinates expressed in unscaled G-code coordinates.
This affects the input arguments supplied to the extrude*() and travel_to()
methods. */
Pointf origin;
FullPrintConfig config;
GCodeWriter writer;
PlaceholderParser* placeholder_parser;
OozePrevention ooze_prevention;
Wipe wipe;
AvoidCrossingPerimeters avoid_crossing_perimeters;
bool enable_loop_clipping;
bool enable_cooling_markers;
size_t layer_count;
int layer_index; // just a counter
const Layer* layer;
std::map<const PrintObject*,Point> _seam_position;
bool first_layer; // this flag triggers first layer speeds
unsigned int elapsed_time; // seconds
double volumetric_speed;
GCode();
Point& last_pos();
void set_last_pos(const Point &pos);
bool last_pos_defined() const;
void apply_print_config(const PrintConfig &print_config);
void set_extruders(const std::vector<unsigned int> &extruder_ids);
void set_origin(const Pointf &pointf);
std::string preamble();
std::string change_layer(const Layer &layer);
std::string extrude(const ExtrusionEntity &entity, std::string description = "", double speed = -1);
std::string extrude(ExtrusionLoop loop, std::string description = "", double speed = -1);
std::string extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone);
std::string retract(bool toolchange = false);
std::string unretract();
std::string set_extruder(unsigned int extruder_id);
Pointf point_to_gcode(const Point &point);
private:
Point _last_pos;
bool _last_pos_defined;
std::string _extrude(ExtrusionPath path, std::string description = "", double speed = -1);
};
}

View File

@ -504,7 +504,7 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
//printf(" => too thin, skipping\n");
return false;
}
return true;
}

View File

@ -53,6 +53,12 @@ Layer::object()
return this->_object;
}
const PrintObject*
Layer::object() const
{
return this->_object;
}
size_t
Layer::region_count()

View File

@ -40,8 +40,7 @@ class LayerRegion
// collection of expolygons representing the bridged areas (thus not
// needing support material)
// (this could be just a Polygons object)
ExPolygonCollection bridged;
Polygons bridged;
// collection of polylines representing the unsupported bridge edges
PolylineCollection unsupported_bridge_edges;
@ -57,6 +56,9 @@ class LayerRegion
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
void merge_slices();
void prepare_fill_surfaces();
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces);
void process_external_surfaces(const Layer* lower_layer);
double infill_area_threshold() const;
private:
Layer *_layer;
@ -76,6 +78,7 @@ class Layer {
size_t id() const;
void set_id(size_t id);
PrintObject* object();
const PrintObject* object() const;
Layer *upper_layer;
Layer *lower_layer;

View File

@ -1,5 +1,7 @@
#include "Layer.hpp"
#include "BridgeDetector.hpp"
#include "ClipperUtils.hpp"
#include "PerimeterGenerator.hpp"
#include "Print.hpp"
#include "Surface.hpp"
@ -53,6 +55,178 @@ LayerRegion::merge_slices()
this->slices.surfaces.push_back(Surface(stInternal, *expoly));
}
void
LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
{
this->perimeters.clear();
this->thin_fills.clear();
PerimeterGenerator g(
// input:
&slices,
this->layer()->height,
this->flow(frPerimeter),
&this->region()->config,
&this->layer()->object()->config,
&this->layer()->object()->print()->config,
// output:
&this->perimeters,
&this->thin_fills,
fill_surfaces
);
if (this->layer()->lower_layer != NULL)
g.lower_slices = &this->layer()->lower_layer->slices;
g.layer_id = this->layer()->id();
g.ext_perimeter_flow = this->flow(frExternalPerimeter);
g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object());
g.solid_infill_flow = this->flow(frSolidInfill);
g.process();
}
void
LayerRegion::process_external_surfaces(const Layer* lower_layer)
{
const Surfaces &surfaces = this->fill_surfaces.surfaces;
const double margin = scale_(EXTERNAL_INFILL_MARGIN);
SurfaceCollection bottom;
for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) {
if (!surface->is_bottom()) continue;
ExPolygons grown = offset_ex(surface->expolygon, +margin);
/* detect bridge direction before merging grown surfaces otherwise adjacent bridges
would get merged into a single one while they need different directions
also, supply the original expolygon instead of the grown one, because in case
of very thin (but still working) anchors, the grown expolygon would go beyond them */
double angle = -1;
if (lower_layer != NULL) {
BridgeDetector bd(
surface->expolygon,
lower_layer->slices,
this->flow(frInfill, this->layer()->height, true).scaled_width()
);
#ifdef SLIC3R_DEBUG
printf("Processing bridge at layer %zu:\n", this->layer()->id();
#endif
if (bd.detect_angle() && this->layer()->object()->config.support_material) {
angle = bd.angle;
Polygons coverage = bd.coverage();
this->bridged.insert(this->bridged.end(), coverage.begin(), coverage.end());
this->unsupported_bridge_edges.append(bd.unsupported_edges());
}
}
for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) {
Surface s = *surface;
s.expolygon = *it;
s.bridge_angle = angle;
bottom.surfaces.push_back(s);
}
}
SurfaceCollection top;
for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) {
if (surface->surface_type != stTop) continue;
// give priority to bottom surfaces
ExPolygons grown = diff_ex(
offset(surface->expolygon, +margin),
(Polygons)bottom
);
for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) {
Surface s = *surface;
s.expolygon = *it;
top.surfaces.push_back(s);
}
}
/* if we're slicing with no infill, we can't extend external surfaces
over non-existent infill */
SurfaceCollection fill_boundaries;
if (this->region()->config.fill_density.value > 0) {
fill_boundaries = SurfaceCollection(surfaces);
} else {
for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) {
if (it->surface_type != stInternal)
fill_boundaries.surfaces.push_back(*it);
}
}
// intersect the grown surfaces with the actual fill boundaries
SurfaceCollection new_surfaces;
{
// merge top and bottom in a single collection
SurfaceCollection tb = top;
tb.surfaces.insert(tb.surfaces.end(), bottom.surfaces.begin(), bottom.surfaces.end());
// group surfaces
std::vector<SurfacesPtr> groups;
tb.group(&groups);
for (std::vector<SurfacesPtr>::const_iterator g = groups.begin(); g != groups.end(); ++g) {
Polygons subject;
for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) {
Polygons pp = **s;
subject.insert(subject.end(), pp.begin(), pp.end());
}
ExPolygons expp = intersection_ex(
subject,
(Polygons)fill_boundaries,
true // to ensure adjacent expolygons are unified
);
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
Surface s = *g->front();
s.expolygon = *ex;
new_surfaces.surfaces.push_back(s);
}
}
}
/* subtract the new top surfaces from the other non-top surfaces and re-add them */
{
SurfaceCollection other;
for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
if (s->surface_type != stTop && !s->is_bottom())
other.surfaces.push_back(*s);
}
// group surfaces
std::vector<SurfacesPtr> groups;
other.group(&groups);
for (std::vector<SurfacesPtr>::const_iterator g = groups.begin(); g != groups.end(); ++g) {
Polygons subject;
for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) {
Polygons pp = **s;
subject.insert(subject.end(), pp.begin(), pp.end());
}
ExPolygons expp = diff_ex(
subject,
(Polygons)new_surfaces
);
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
Surface s = *g->front();
s.expolygon = *ex;
new_surfaces.surfaces.push_back(s);
}
}
}
this->fill_surfaces = new_surfaces;
}
void
LayerRegion::prepare_fill_surfaces()
{
@ -90,6 +264,13 @@ LayerRegion::prepare_fill_surfaces()
}
}
double
LayerRegion::infill_area_threshold() const
{
double ss = this->flow(frSolidInfill).scaled_spacing();
return ss*ss;
}
#ifdef SLIC3RXS
REGISTER_CLASS(LayerRegion, "Layer::Region");
#endif

View File

@ -690,7 +690,8 @@ ModelVolume::assign_unique_material()
{
Model* model = this->get_object()->get_model();
this->_material_id = 1 + model->materials.size();
// as material-id "0" is reserved by the AMF spec we start from 1
this->_material_id = 1 + model->materials.size(); // watchout for implicit cast
return model->add_material(this->_material_id);
}

View File

@ -100,6 +100,24 @@ MultiPoint::remove_duplicate_points()
}
}
void
MultiPoint::append(const Point &point)
{
this->points.push_back(point);
}
void
MultiPoint::append(const Points &points)
{
this->append(points.begin(), points.end());
}
void
MultiPoint::append(const Points::const_iterator &begin, const Points::const_iterator &end)
{
this->points.insert(this->points.end(), begin, end);
}
Points
MultiPoint::_douglas_peucker(const Points &points, const double tolerance)
{

View File

@ -33,6 +33,9 @@ class MultiPoint
bool has_boundary_point(const Point &point) const;
BoundingBox bounding_box() const;
void remove_duplicate_points();
void append(const Point &point);
void append(const Points &points);
void append(const Points::const_iterator &begin, const Points::const_iterator &end);
static Points _douglas_peucker(const Points &points, const double tolerance);

View File

@ -0,0 +1,525 @@
#include "PerimeterGenerator.hpp"
#include "ClipperUtils.hpp"
#include "ExtrusionEntityCollection.hpp"
namespace Slic3r {
void
PerimeterGenerator::process()
{
// other perimeters
this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
coord_t pwidth = this->perimeter_flow.scaled_width();
coord_t pspacing = this->perimeter_flow.scaled_spacing();
// external perimeters
this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
coord_t ext_pwidth = this->ext_perimeter_flow.scaled_width();
coord_t ext_pspacing = scale_(this->ext_perimeter_flow.spacing(this->perimeter_flow));
// overhang perimeters
this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
// solid infill
coord_t ispacing = this->solid_infill_flow.scaled_spacing();
coord_t gap_area_threshold = pwidth * pwidth;
// Calculate the minimum required spacing between two adjacent traces.
// This should be equal to the nominal flow spacing but we experiment
// with some tolerance in order to avoid triggering medial axis when
// some squishing might work. Loops are still spaced by the entire
// flow spacing; this only applies to collapsing parts.
coord_t min_spacing = pspacing * (1 - INSET_OVERLAP_TOLERANCE);
coord_t ext_min_spacing = ext_pspacing * (1 - INSET_OVERLAP_TOLERANCE);
// prepare grown lower layer slices for overhang detection
if (this->lower_slices != NULL && this->config->overhangs) {
// We consider overhang any part where the entire nozzle diameter is not supported by the
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
// in the current layer
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1);
this->_lower_slices_p = offset(*this->lower_slices, scale_(+nozzle_diameter/2));
}
// we need to process each island separately because we might have different
// extra perimeters for each one
for (Surfaces::const_iterator surface = this->slices->surfaces.begin();
surface != this->slices->surfaces.end(); ++surface) {
// detect how many perimeters must be generated for this island
signed short loop_number = this->config->perimeters + surface->extra_perimeters;
loop_number--; // 0-indexed loops
Polygons gaps;
Polygons last = surface->expolygon.simplify_p(SCALED_RESOLUTION);
if (loop_number >= 0) { // no loops = -1
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
std::vector<PerimeterGeneratorLoops> holes(loop_number+1); // depth => loops
Polylines thin_walls;
// we loop one time more than needed in order to find gaps after the last perimeter was applied
for (signed short i = 0; i <= loop_number+1; ++i) { // outer loop is 0
Polygons offsets;
if (i == 0) {
// the minimum thickness of a single loop is:
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
if (this->config->thin_walls) {
offsets = offset2(
last,
-(0.5*ext_pwidth + 0.5*ext_min_spacing - 1),
+(0.5*ext_min_spacing - 1)
);
} else {
offsets = offset(last, -0.5*ext_pwidth);
}
// look for thin walls
if (this->config->thin_walls) {
Polygons diffpp = diff(
last,
offset(offsets, +0.5*ext_pwidth),
true // medial axis requires non-overlapping geometry
);
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes)
coord_t min_width = ext_pwidth / 2;
ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2);
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
Polylines pp;
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex)
ex->medial_axis(ext_pwidth + ext_pspacing, min_width, &pp);
double threshold = ext_pwidth * ext_pwidth;
for (Polylines::const_iterator p = pp.begin(); p != pp.end(); ++p) {
if (p->length() > threshold) {
thin_walls.push_back(*p);
}
}
#ifdef DEBUG
printf(" %zu thin walls detected\n", thin_walls.size());
#endif
/*
if (false) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"medial_axis.svg",
no_arrows => 1,
#expolygons => \@expp,
polylines => \@thin_walls,
);
}
*/
}
} else {
coord_t distance = (i == 1) ? ext_pspacing : pspacing;
if (this->config->thin_walls) {
offsets = offset2(
last,
-(distance + 0.5*min_spacing - 1),
+(0.5*min_spacing - 1)
);
} else {
offsets = offset(
last,
-distance
);
}
// look for gaps
if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0) {
// not using safety offset here would "detect" very narrow gaps
// (but still long enough to escape the area threshold) that gap fill
// won't be able to fill but we'd still remove from infill area
ExPolygons diff_expp = diff_ex(
offset(last, -0.5*distance),
offset(offsets, +0.5*distance + 10) // safety offset
);
for (ExPolygons::const_iterator ex = diff_expp.begin(); ex != diff_expp.end(); ++ex) {
if (fabs(ex->area()) >= gap_area_threshold) {
Polygons pp = *ex;
gaps.insert(gaps.end(), pp.begin(), pp.end());
}
}
}
}
if (offsets.empty()) break;
if (i > loop_number) break; // we were only looking for gaps this time
last = offsets;
for (Polygons::const_iterator polygon = offsets.begin(); polygon != offsets.end(); ++polygon) {
PerimeterGeneratorLoop loop(*polygon, i);
loop.is_contour = polygon->is_counter_clockwise();
if (loop.is_contour) {
contours[i].push_back(loop);
} else {
holes[i].push_back(loop);
}
}
}
// nest loops: holes first
for (signed short d = 0; d <= loop_number; ++d) {
PerimeterGeneratorLoops &holes_d = holes[d];
// loop through all holes having depth == d
for (signed short i = 0; i < holes_d.size(); ++i) {
const PerimeterGeneratorLoop &loop = holes_d[i];
// find the hole loop that contains this one, if any
for (signed short t = d+1; t <= loop_number; ++t) {
for (signed short j = 0; j < holes[t].size(); ++j) {
PerimeterGeneratorLoop &candidate_parent = holes[t][j];
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
candidate_parent.children.push_back(loop);
holes_d.erase(holes_d.begin() + i);
--i;
goto NEXT_LOOP;
}
}
}
// if no hole contains this hole, find the contour loop that contains it
for (signed short t = loop_number; t >= 0; --t) {
for (signed short j = 0; j < contours[t].size(); ++j) {
PerimeterGeneratorLoop &candidate_parent = contours[t][j];
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
candidate_parent.children.push_back(loop);
holes_d.erase(holes_d.begin() + i);
--i;
goto NEXT_LOOP;
}
}
}
NEXT_LOOP: ;
}
}
// nest contour loops
for (signed short d = loop_number; d >= 1; --d) {
PerimeterGeneratorLoops &contours_d = contours[d];
// loop through all contours having depth == d
for (signed short i = 0; i < contours_d.size(); ++i) {
const PerimeterGeneratorLoop &loop = contours_d[i];
// find the contour loop that contains it
for (signed short t = d-1; t >= 0; --t) {
for (signed short j = 0; j < contours[t].size(); ++j) {
PerimeterGeneratorLoop &candidate_parent = contours[t][j];
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
candidate_parent.children.push_back(loop);
contours_d.erase(contours_d.begin() + i);
--i;
goto NEXT_CONTOUR;
}
}
}
NEXT_CONTOUR: ;
}
}
// at this point, all loops should be in contours[0]
ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls);
// if brim will be printed, reverse the order of perimeters so that
// we continue inwards after having finished the brim
// TODO: add test for perimeter order
if (this->config->external_perimeters_first
|| (this->layer_id == 0 && this->print_config->brim_width.value > 0))
entities.reverse();
// append perimeters for this slice as a collection
if (!entities.empty())
this->loops->append(entities);
}
// fill gaps
if (!gaps.empty()) {
/*
if (false) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"gaps.svg",
expolygons => union_ex(\@gaps),
);
}
*/
// where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth
// where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth
std::vector<PerimeterGeneratorGapSize> gap_sizes;
gap_sizes.push_back(PerimeterGeneratorGapSize(pwidth, 2*pspacing, unscale(2*pwidth)));
gap_sizes.push_back(PerimeterGeneratorGapSize(0.1*pwidth, pwidth, unscale(1*pwidth)));
for (std::vector<PerimeterGeneratorGapSize>::const_iterator gap_size = gap_sizes.begin();
gap_size != gap_sizes.end(); ++gap_size) {
ExtrusionEntityCollection gap_fill = this->_fill_gaps(gap_size->min,
gap_size->max, gap_size->width, gaps);
this->gap_fill->append(gap_fill.entities);
// Make sure we don't infill narrow parts that are already gap-filled
// (we only consider this surface's gaps to reduce the diff() complexity).
// Growing actual extrusions ensures that gaps not filled by medial axis
// are not subtracted from fill surfaces (they might be too short gaps
// that medial axis skips but infill might join with other infill regions
// and use zigzag).
double dist = scale_(gap_size->width/2);
Polygons filled;
for (ExtrusionEntitiesPtr::const_iterator it = gap_fill.entities.begin();
it != gap_fill.entities.end(); ++it)
offset((*it)->as_polyline(), &filled, dist);
last = diff(last, filled);
gaps = diff(gaps, filled); // prevent more gap fill here
}
}
// create one more offset to be used as boundary for fill
// we offset by half the perimeter spacing (to get to the actual infill boundary)
// and then we offset back and forth by half the infill spacing to only consider the
// non-collapsing regions
coord_t inset = 0;
if (loop_number == 0) {
// one loop
inset += ext_pspacing/2;
} else if (loop_number > 0) {
// two or more loops
inset += pspacing/2;
}
// only apply infill overlap if we actually have one perimeter
if (inset > 0)
inset -= this->config->get_abs_value("infill_overlap", inset + ispacing/2);
{
ExPolygons expp = union_ex(last);
// simplify infill contours according to resolution
Polygons pp;
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex)
ex->simplify_p(SCALED_RESOLUTION, &pp);
// collapse too narrow infill areas
coord_t min_perimeter_infill_spacing = ispacing * (1 - INSET_OVERLAP_TOLERANCE);
expp = offset2_ex(
pp,
-inset -min_perimeter_infill_spacing/2,
+min_perimeter_infill_spacing/2
);
// append infill areas to fill_surfaces
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex)
this->fill_surfaces->surfaces.push_back(Surface(stInternal, *ex)); // use a bogus surface type
}
}
}
ExtrusionEntityCollection
PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops,
Polylines &thin_walls) const
{
// loops is an arrayref of ::Loop objects
// turn each one into an ExtrusionLoop object
ExtrusionEntityCollection coll;
for (PerimeterGeneratorLoops::const_iterator loop = loops.begin();
loop != loops.end(); ++loop) {
bool is_external = loop->is_external();
ExtrusionRole role;
ExtrusionLoopRole loop_role;
role = is_external ? erExternalPerimeter : erPerimeter;
if (loop->is_internal_contour()) {
// Note that we set loop role to ContourInternalPerimeter
// also when loop is both internal and external (i.e.
// there's only one contour loop).
loop_role = elrContourInternalPerimeter;
} else {
loop_role = elrDefault;
}
// detect overhanging/bridging perimeters
ExtrusionPaths paths;
if (this->config->overhangs && this->layer_id > 0
&& !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) {
// get non-overhang paths by intersecting this loop with the grown lower slices
{
Polylines polylines;
intersection((Polygons)loop->polygon, this->_lower_slices_p, &polylines);
for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) {
ExtrusionPath path(role);
path.polyline = *polyline;
path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm;
path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width;
path.height = this->layer_height;
paths.push_back(path);
}
}
// get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between
// the loop centerline and original lower slices is >= half nozzle diameter
{
Polylines polylines;
diff((Polygons)loop->polygon, this->_lower_slices_p, &polylines);
for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) {
ExtrusionPath path(erOverhangPerimeter);
path.polyline = *polyline;
path.mm3_per_mm = this->_mm3_per_mm_overhang;
path.width = this->overhang_flow.width;
path.height = this->overhang_flow.height;
paths.push_back(path);
}
}
// reapply the nearest point search for starting point
// We allow polyline reversal because Clipper may have randomly
// reversed polylines during clipping.
paths = ExtrusionEntityCollection(paths).chained_path();
} else {
ExtrusionPath path(role);
path.polyline = loop->polygon.split_at_first_point();
path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm;
path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width;
path.height = this->layer_height;
paths.push_back(path);
}
coll.append(ExtrusionLoop(paths, loop_role));
}
// append thin walls to the nearest-neighbor search (only for first iteration)
if (!thin_walls.empty()) {
for (Polylines::const_iterator polyline = thin_walls.begin(); polyline != thin_walls.end(); ++polyline) {
ExtrusionPath path(erExternalPerimeter);
path.polyline = *polyline;
path.mm3_per_mm = this->_mm3_per_mm;
path.width = this->perimeter_flow.width;
path.height = this->layer_height;
coll.append(path);
}
thin_walls.clear();
}
// sort entities
ExtrusionEntityCollection sorted_coll;
coll.chained_path(&sorted_coll, false, &sorted_coll.orig_indices);
// traverse children
ExtrusionEntityCollection entities;
for (unsigned short i = 0; i < sorted_coll.orig_indices.size(); ++i) {
size_t idx = sorted_coll.orig_indices[i];
if (idx >= loops.size()) {
// this is a thin wall
// let's get it from the sorted collection as it might have been reversed
entities.append(*sorted_coll.entities[i]);
} else {
const PerimeterGeneratorLoop &loop = loops[i];
ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[idx]);
ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls);
if (loop.is_contour) {
eloop.make_counter_clockwise();
entities.append(children.entities);
entities.append(eloop);
} else {
eloop.make_clockwise();
entities.append(eloop);
entities.append(children.entities);
}
}
}
return entities;
}
ExtrusionEntityCollection
PerimeterGenerator::_fill_gaps(double min, double max, double w,
const Polygons &gaps) const
{
ExtrusionEntityCollection coll;
min *= (1 - INSET_OVERLAP_TOLERANCE);
ExPolygons curr = diff_ex(
offset2(gaps, -min/2, +min/2),
offset2(gaps, -max/2, +max/2),
true
);
Polylines polylines;
for (ExPolygons::const_iterator ex = curr.begin(); ex != curr.end(); ++ex)
ex->medial_axis(max, min/2, &polylines);
if (polylines.empty())
return coll;
#ifdef SLIC3R_DEBUG
if (!curr.empty())
printf(" %zu gaps filled with extrusion width = %f\n", curr.size(), w);
#endif
//my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
Flow flow(
w, this->layer_height, this->solid_infill_flow.nozzle_diameter
);
double mm3_per_mm = flow.mm3_per_mm();
for (Polylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) {
ExtrusionPath path(erGapFill);
path.polyline = *p;
path.mm3_per_mm = mm3_per_mm;
path.width = flow.width;
path.height = this->layer_height;
if (p->is_valid() && p->first_point().coincides_with(p->last_point())) {
// since medial_axis() now returns only Polyline objects, detect loops here
ExtrusionLoop loop;
loop.paths.push_back(path);
coll.append(loop);
} else {
coll.append(path);
}
}
return coll;
}
#ifdef SLIC3RXS
REGISTER_CLASS(PerimeterGenerator, "Layer::PerimeterGenerator");
#endif
bool
PerimeterGeneratorLoop::is_external() const
{
return this->depth == 0;
}
bool
PerimeterGeneratorLoop::is_internal_contour() const
{
if (this->is_contour) {
// an internal contour is a contour containing no other contours
for (std::vector<PerimeterGeneratorLoop>::const_iterator loop = this->children.begin();
loop != this->children.end(); ++loop) {
if (loop->is_contour) {
return false;
}
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,84 @@
#ifndef slic3r_PerimeterGenerator_hpp_
#define slic3r_PerimeterGenerator_hpp_
#include <myinit.h>
#include <vector>
#include "ExPolygonCollection.hpp"
#include "Flow.hpp"
#include "Polygon.hpp"
#include "PrintConfig.hpp"
#include "SurfaceCollection.hpp"
namespace Slic3r {
class PerimeterGeneratorLoop;
typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops;
class PerimeterGeneratorLoop {
public:
Polygon polygon;
bool is_contour;
unsigned short depth;
std::vector<PerimeterGeneratorLoop> children;
PerimeterGeneratorLoop(Polygon polygon, unsigned short depth)
: polygon(polygon), depth(depth), is_contour(false)
{};
bool is_external() const;
bool is_internal_contour() const;
};
class PerimeterGenerator {
public:
const SurfaceCollection* slices;
const ExPolygonCollection* lower_slices;
double layer_height;
int layer_id;
Flow perimeter_flow;
Flow ext_perimeter_flow;
Flow overhang_flow;
Flow solid_infill_flow;
PrintRegionConfig* config;
PrintObjectConfig* object_config;
PrintConfig* print_config;
ExtrusionEntityCollection* loops;
ExtrusionEntityCollection* gap_fill;
SurfaceCollection* fill_surfaces;
PerimeterGenerator(const SurfaceCollection* slices, double layer_height, Flow flow,
PrintRegionConfig* config, PrintObjectConfig* object_config,
PrintConfig* print_config, ExtrusionEntityCollection* loops,
ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces)
: slices(slices), lower_slices(NULL), layer_height(layer_height),
perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow),
solid_infill_flow(flow), layer_id(-1),
config(config), object_config(object_config), print_config(print_config),
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces),
_ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1)
{};
void process();
private:
double _ext_mm3_per_mm;
double _mm3_per_mm;
double _mm3_per_mm_overhang;
Polygons _lower_slices_p;
ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops,
Polylines &thin_walls) const;
ExtrusionEntityCollection _fill_gaps(double min, double max, double w,
const Polygons &gaps) const;
};
class PerimeterGeneratorGapSize {
public:
coord_t min;
coord_t max;
coord_t width;
PerimeterGeneratorGapSize(coord_t min, coord_t max, coord_t width)
: min(min), max(max), width(width) {};
};
}
#endif

View File

@ -2,14 +2,16 @@
#include <ctime>
#include <iomanip>
#include <sstream>
#include <unistd.h> // provides **environ
extern char **environ;
namespace Slic3r {
PlaceholderParser::PlaceholderParser()
{
this->_single["version"] = SLIC3R_VERSION;
// TODO: port these methods to C++, then call them here
// this->apply_env_variables();
this->set("version", SLIC3R_VERSION);
this->apply_env_variables();
this->update_timestamp();
}
@ -62,16 +64,32 @@ void PlaceholderParser::apply_config(DynamicPrintConfig &config)
if (const ConfigOptionVectorBase* optv = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
// set placeholders for options with multiple values
// TODO: treat [bed_shape] as single, not multiple
this->set(key, optv->vserialize());
} else if (const ConfigOptionPoint* optp = dynamic_cast<const ConfigOptionPoint*>(opt)) {
this->_single[key] = optp->serialize();
this->set(key, optp->serialize());
Pointf val = *optp;
this->_multiple[key + "_X"] = val.x;
this->_multiple[key + "_Y"] = val.y;
this->set(key + "_X", val.x);
this->set(key + "_Y", val.y);
} else {
// set single-value placeholders
this->_single[key] = opt->serialize();
this->set(key, opt->serialize());
}
}
}
void
PlaceholderParser::apply_env_variables()
{
for (char** env = environ; *env; env++) {
if (strncmp(*env, "SLIC3R_", 7) == 0) {
std::stringstream ss(*env);
std::string key, value;
std::getline(ss, key, '=');
ss >> value;
this->set(key, value);
}
}
}
@ -92,18 +110,57 @@ PlaceholderParser::set(const std::string &key, int value)
}
void
PlaceholderParser::set(const std::string &key, const std::vector<std::string> &values)
PlaceholderParser::set(const std::string &key, std::vector<std::string> values)
{
for (std::vector<std::string>::const_iterator v = values.begin(); v != values.end(); ++v) {
if (values.empty()) {
this->_multiple.erase(key);
this->_single.erase(key);
} else {
this->_multiple[key] = values;
this->_single[key] = values.front();
}
}
std::string
PlaceholderParser::process(std::string str) const
{
// replace single options, like [foo]
for (t_strstr_map::const_iterator it = this->_single.begin(); it != this->_single.end(); ++it) {
std::stringstream ss;
ss << key << "_" << (v - values.begin());
this->_multiple[ ss.str() ] = *v;
if (v == values.begin()) {
this->_multiple[key] = *v;
ss << '[' << it->first << ']';
this->find_and_replace(str, ss.str(), it->second);
}
// replace multiple options like [foo_0] by looping until we have enough values
// or until a previous match was found (this handles non-existing indices reasonably
// without a regex)
for (t_strstrs_map::const_iterator it = this->_multiple.begin(); it != this->_multiple.end(); ++it) {
const std::vector<std::string> &values = it->second;
bool found = false;
for (size_t i = 0; (i < values.size()) || found; ++i) {
std::stringstream ss;
ss << '[' << it->first << '_' << i << ']';
if (i < values.size()) {
found = this->find_and_replace(str, ss.str(), values[i]);
} else {
found = this->find_and_replace(str, ss.str(), values.front());
}
}
}
this->_single.erase(key);
return str;
}
bool
PlaceholderParser::find_and_replace(std::string &source, std::string const &find, std::string const &replace) const
{
bool found = false;
for (std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos; ) {
source.replace(i, find.length(), replace);
i += replace.length();
found = true;
}
return found;
}
#ifdef SLIC3RXS

View File

@ -11,18 +11,26 @@
namespace Slic3r {
typedef std::map<std::string, std::string> t_strstr_map;
typedef std::map<std::string, std::vector<std::string> > t_strstrs_map;
class PlaceholderParser
{
public:
std::map<std::string, std::string> _single;
std::map<std::string, std::string> _multiple;
t_strstr_map _single;
t_strstrs_map _multiple;
PlaceholderParser();
void update_timestamp();
void apply_config(DynamicPrintConfig &config);
void apply_env_variables();
void set(const std::string &key, const std::string &value);
void set(const std::string &key, int value);
void set(const std::string &key, const std::vector<std::string> &values);
void set(const std::string &key, std::vector<std::string> values);
std::string process(std::string str) const;
private:
bool find_and_replace(std::string &source, std::string const &find, std::string const &replace) const;
};
}

View File

@ -218,7 +218,8 @@ Polygon::wkt() const
return wkt.str();
}
// find all concave vertices (i.e. having an internal angle greater than the supplied angle) */
// find all concave vertices (i.e. having an internal angle greater than the supplied angle)
// (external = right side, thus we consider ccw orientation)
Points
Polygon::concave_points(double angle) const
{
@ -241,7 +242,8 @@ Polygon::concave_points(double angle) const
return points;
}
// find all convex vertices (i.e. having an internal angle smaller than the supplied angle) */
// find all convex vertices (i.e. having an internal angle smaller than the supplied angle)
// (external = right side, thus we consider ccw orientation)
Points
Polygon::convex_points(double angle) const
{

View File

@ -50,6 +50,12 @@ PolylineCollection::leftmost_point() const
return p;
}
void
PolylineCollection::append(const Polylines &pp)
{
this->polylines.insert(this->polylines.end(), pp.begin(), pp.end());
}
#ifdef SLIC3RXS
REGISTER_CLASS(PolylineCollection, "Polyline::Collection");
#endif

View File

@ -13,6 +13,7 @@ class PolylineCollection
void chained_path(PolylineCollection* retval, bool no_reverse = false) const;
void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const;
Point leftmost_point() const;
void append(const Polylines &polylines);
};
}

View File

@ -61,7 +61,7 @@ class PrintRegion
private:
Print* _print;
PrintRegion(Print* print);
~PrintRegion();
};
@ -135,6 +135,7 @@ class PrintObject
bool invalidate_all_steps();
bool has_support_material() const;
void process_external_surfaces();
void bridge_over_infill();
private:

View File

@ -151,10 +151,11 @@ PrintConfigDef::build_def() {
Options["external_perimeter_speed"].type = coFloatOrPercent;
Options["external_perimeter_speed"].label = "External perimeters";
Options["external_perimeter_speed"].category = "Speed";
Options["external_perimeter_speed"].tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above.";
Options["external_perimeter_speed"].tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto.";
Options["external_perimeter_speed"].sidetext = "mm/s or %";
Options["external_perimeter_speed"].cli = "external-perimeter-speed=s";
Options["external_perimeter_speed"].ratio_over = "perimeter_speed";
Options["external_perimeter_speed"].min = 0;
Options["external_perimeters_first"].type = coBool;
Options["external_perimeters_first"].label = "External perimeters first";
@ -300,6 +301,7 @@ PrintConfigDef::build_def() {
Options["fill_pattern"].cli = "fill-pattern=s";
Options["fill_pattern"].enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
Options["fill_pattern"].enum_values.push_back("rectilinear");
Options["fill_pattern"].enum_values.push_back("grid");
Options["fill_pattern"].enum_values.push_back("line");
Options["fill_pattern"].enum_values.push_back("concentric");
Options["fill_pattern"].enum_values.push_back("honeycomb");
@ -308,6 +310,7 @@ PrintConfigDef::build_def() {
Options["fill_pattern"].enum_values.push_back("archimedeanchords");
Options["fill_pattern"].enum_values.push_back("octagramspiral");
Options["fill_pattern"].enum_labels.push_back("Rectilinear");
Options["fill_pattern"].enum_labels.push_back("Grid");
Options["fill_pattern"].enum_labels.push_back("Line");
Options["fill_pattern"].enum_labels.push_back("Concentric");
Options["fill_pattern"].enum_labels.push_back("Honeycomb");
@ -350,6 +353,7 @@ PrintConfigDef::build_def() {
Options["first_layer_speed"].tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds.";
Options["first_layer_speed"].sidetext = "mm/s or %";
Options["first_layer_speed"].cli = "first-layer-speed=s";
Options["first_layer_speed"].min = 0;
Options["first_layer_temperature"].type = coInts;
Options["first_layer_temperature"].label = "First layer";
@ -448,7 +452,7 @@ PrintConfigDef::build_def() {
Options["infill_speed"].type = coFloat;
Options["infill_speed"].label = "Infill";
Options["infill_speed"].category = "Speed";
Options["infill_speed"].tooltip = "Speed for printing the internal fill.";
Options["infill_speed"].tooltip = "Speed for printing the internal fill. Set to zero for auto.";
Options["infill_speed"].sidetext = "mm/s";
Options["infill_speed"].cli = "infill-speed=f";
Options["infill_speed"].aliases.push_back("print_feed_rate");
@ -499,7 +503,20 @@ PrintConfigDef::build_def() {
Options["min_print_speed"].sidetext = "mm/s";
Options["min_print_speed"].cli = "min-print-speed=f";
Options["min_print_speed"].min = 0;
Options["min_print_speed"].max = 1000;
Options["max_print_speed"].type = coFloat;
Options["max_print_speed"].label = "Max print speed";
Options["max_print_speed"].tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the highest print speed you want to allow.";
Options["max_print_speed"].sidetext = "mm/s";
Options["max_print_speed"].cli = "max-print-speed=f";
Options["max_print_speed"].min = 1;
Options["max_volumetric_speed"].type = coFloat;
Options["max_volumetric_speed"].label = "Max volumetric speed";
Options["max_volumetric_speed"].tooltip = "This experimental setting is used to set the maximum volumetric speed your extruder supports.";
Options["max_volumetric_speed"].sidetext = "mm³/s";
Options["max_volumetric_speed"].cli = "max-volumetric-speed=f";
Options["max_volumetric_speed"].min = 0;
Options["min_skirt_length"].type = coFloat;
Options["min_skirt_length"].label = "Minimum extrusion length";
@ -579,7 +596,7 @@ PrintConfigDef::build_def() {
Options["perimeter_speed"].type = coFloat;
Options["perimeter_speed"].label = "Perimeters";
Options["perimeter_speed"].category = "Speed";
Options["perimeter_speed"].tooltip = "Speed for perimeters (contours, aka vertical shells).";
Options["perimeter_speed"].tooltip = "Speed for perimeters (contours, aka vertical shells). Set to zero for auto.";
Options["perimeter_speed"].sidetext = "mm/s";
Options["perimeter_speed"].cli = "perimeter-speed=f";
Options["perimeter_speed"].aliases.push_back("perimeter_feed_rate");
@ -637,12 +654,14 @@ PrintConfigDef::build_def() {
Options["retract_length"].type = coFloats;
Options["retract_length"].label = "Length";
Options["retract_length"].full_label = "Retraction Length";
Options["retract_length"].tooltip = "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder).";
Options["retract_length"].sidetext = "mm (zero to disable)";
Options["retract_length"].cli = "retract-length=f@";
Options["retract_length_toolchange"].type = coFloats;
Options["retract_length_toolchange"].label = "Length";
Options["retract_length_toolchange"].full_label = "Retraction Length (Toolchange)";
Options["retract_length_toolchange"].tooltip = "When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder).";
Options["retract_length_toolchange"].sidetext = "mm (zero to disable)";
Options["retract_length_toolchange"].cli = "retract-length-toolchange=f@";
@ -667,10 +686,10 @@ PrintConfigDef::build_def() {
Options["retract_speed"].type = coInts;
Options["retract_speed"].label = "Speed";
Options["retract_speed"].full_label = "Retraction Speed";
Options["retract_speed"].tooltip = "The speed for retractions (it only applies to the extruder motor).";
Options["retract_speed"].sidetext = "mm/s";
Options["retract_speed"].cli = "retract-speed=f@";
Options["retract_speed"].max = 1000;
Options["seam_position"].type = coEnum;
Options["seam_position"].label = "Seam position";
@ -719,6 +738,7 @@ PrintConfigDef::build_def() {
Options["skirts"].type = coInt;
Options["skirts"].label = "Loops (minimum)";
Options["skirts"].full_label = "Skirt Loops";
Options["skirts"].tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely.";
Options["skirts"].cli = "skirts=i";
Options["skirts"].min = 0;
@ -735,10 +755,11 @@ PrintConfigDef::build_def() {
Options["small_perimeter_speed"].type = coFloatOrPercent;
Options["small_perimeter_speed"].label = "Small perimeters";
Options["small_perimeter_speed"].category = "Speed";
Options["small_perimeter_speed"].tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above.";
Options["small_perimeter_speed"].tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto.";
Options["small_perimeter_speed"].sidetext = "mm/s or %";
Options["small_perimeter_speed"].cli = "small-perimeter-speed=s";
Options["small_perimeter_speed"].ratio_over = "perimeter_speed";
Options["small_perimeter_speed"].min = 0;
Options["solid_infill_below_area"].type = coFloat;
Options["solid_infill_below_area"].label = "Solid infill threshold area";
@ -773,11 +794,12 @@ PrintConfigDef::build_def() {
Options["solid_infill_speed"].type = coFloatOrPercent;
Options["solid_infill_speed"].label = "Solid infill";
Options["solid_infill_speed"].category = "Speed";
Options["solid_infill_speed"].tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above.";
Options["solid_infill_speed"].tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above. Set to zero for auto.";
Options["solid_infill_speed"].sidetext = "mm/s or %";
Options["solid_infill_speed"].cli = "solid-infill-speed=s";
Options["solid_infill_speed"].ratio_over = "infill_speed";
Options["solid_infill_speed"].aliases.push_back("solid_infill_feed_rate");
Options["solid_infill_speed"].min = 0;
Options["solid_layers"].type = coInt;
Options["solid_layers"].label = "Solid layers";
@ -889,6 +911,7 @@ PrintConfigDef::build_def() {
Options["support_material_interface_speed"].sidetext = "mm/s or %";
Options["support_material_interface_speed"].cli = "support-material-interface-speed=s";
Options["support_material_interface_speed"].ratio_over = "support_material_speed";
Options["support_material_interface_speed"].min = 0;
Options["support_material_pattern"].type = coEnum;
Options["support_material_pattern"].label = "Pattern";
@ -970,10 +993,11 @@ PrintConfigDef::build_def() {
Options["top_solid_infill_speed"].type = coFloatOrPercent;
Options["top_solid_infill_speed"].label = "Top solid infill";
Options["top_solid_infill_speed"].category = "Speed";
Options["top_solid_infill_speed"].tooltip = "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above.";
Options["top_solid_infill_speed"].tooltip = "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above. Set to zero for auto.";
Options["top_solid_infill_speed"].sidetext = "mm/s or %";
Options["top_solid_infill_speed"].cli = "top-solid-infill-speed=s";
Options["top_solid_infill_speed"].ratio_over = "solid_infill_speed";
Options["top_solid_infill_speed"].min = 0;
Options["top_solid_layers"].type = coInt;
Options["top_solid_layers"].label = "Top";
@ -989,7 +1013,7 @@ PrintConfigDef::build_def() {
Options["travel_speed"].sidetext = "mm/s";
Options["travel_speed"].cli = "travel-speed=f";
Options["travel_speed"].aliases.push_back("travel_feed_rate");
Options["travel_speed"].min = 0;
Options["travel_speed"].min = 1;
Options["use_firmware_retraction"].type = coBool;
Options["use_firmware_retraction"].label = "Use firmware retraction";

View File

@ -3,6 +3,8 @@
#include "Config.hpp"
#define OPT_PTR(KEY) if (opt_key == #KEY) return &this->KEY
namespace Slic3r {
enum GCodeFlavor {
@ -10,7 +12,7 @@ enum GCodeFlavor {
};
enum InfillPattern {
ipRectilinear, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipRectilinear, ipGrid, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
};
@ -37,6 +39,7 @@ template<> inline t_config_enum_values ConfigOptionEnum<GCodeFlavor>::get_enum_v
template<> inline t_config_enum_values ConfigOptionEnum<InfillPattern>::get_enum_values() {
t_config_enum_values keys_map;
keys_map["rectilinear"] = ipRectilinear;
keys_map["grid"] = ipGrid;
keys_map["line"] = ipLine;
keys_map["concentric"] = ipConcentric;
keys_map["honeycomb"] = ipHoneycomb;
@ -148,29 +151,29 @@ class PrintObjectConfig : public virtual StaticPrintConfig
};
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
if (opt_key == "dont_support_bridges") return &this->dont_support_bridges;
if (opt_key == "extrusion_width") return &this->extrusion_width;
if (opt_key == "first_layer_height") return &this->first_layer_height;
if (opt_key == "infill_only_where_needed") return &this->infill_only_where_needed;
if (opt_key == "interface_shells") return &this->interface_shells;
if (opt_key == "layer_height") return &this->layer_height;
if (opt_key == "raft_layers") return &this->raft_layers;
if (opt_key == "seam_position") return &this->seam_position;
if (opt_key == "support_material") return &this->support_material;
if (opt_key == "support_material_angle") return &this->support_material_angle;
if (opt_key == "support_material_contact_distance") return &this->support_material_contact_distance;
if (opt_key == "support_material_enforce_layers") return &this->support_material_enforce_layers;
if (opt_key == "support_material_extruder") return &this->support_material_extruder;
if (opt_key == "support_material_extrusion_width") return &this->support_material_extrusion_width;
if (opt_key == "support_material_interface_extruder") return &this->support_material_interface_extruder;
if (opt_key == "support_material_interface_layers") return &this->support_material_interface_layers;
if (opt_key == "support_material_interface_spacing") return &this->support_material_interface_spacing;
if (opt_key == "support_material_interface_speed") return &this->support_material_interface_speed;
if (opt_key == "support_material_pattern") return &this->support_material_pattern;
if (opt_key == "support_material_spacing") return &this->support_material_spacing;
if (opt_key == "support_material_speed") return &this->support_material_speed;
if (opt_key == "support_material_threshold") return &this->support_material_threshold;
if (opt_key == "xy_size_compensation") return &this->xy_size_compensation;
OPT_PTR(dont_support_bridges);
OPT_PTR(extrusion_width);
OPT_PTR(first_layer_height);
OPT_PTR(infill_only_where_needed);
OPT_PTR(interface_shells);
OPT_PTR(layer_height);
OPT_PTR(raft_layers);
OPT_PTR(seam_position);
OPT_PTR(support_material);
OPT_PTR(support_material_angle);
OPT_PTR(support_material_contact_distance);
OPT_PTR(support_material_enforce_layers);
OPT_PTR(support_material_extruder);
OPT_PTR(support_material_extrusion_width);
OPT_PTR(support_material_interface_extruder);
OPT_PTR(support_material_interface_layers);
OPT_PTR(support_material_interface_spacing);
OPT_PTR(support_material_interface_speed);
OPT_PTR(support_material_pattern);
OPT_PTR(support_material_spacing);
OPT_PTR(support_material_speed);
OPT_PTR(support_material_threshold);
OPT_PTR(xy_size_compensation);
return NULL;
};
@ -258,38 +261,38 @@ class PrintRegionConfig : public virtual StaticPrintConfig
};
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
if (opt_key == "bottom_solid_layers") return &this->bottom_solid_layers;
if (opt_key == "bridge_flow_ratio") return &this->bridge_flow_ratio;
if (opt_key == "bridge_speed") return &this->bridge_speed;
if (opt_key == "external_fill_pattern") return &this->external_fill_pattern;
if (opt_key == "external_perimeter_extrusion_width") return &this->external_perimeter_extrusion_width;
if (opt_key == "external_perimeter_speed") return &this->external_perimeter_speed;
if (opt_key == "external_perimeters_first") return &this->external_perimeters_first;
if (opt_key == "extra_perimeters") return &this->extra_perimeters;
if (opt_key == "fill_angle") return &this->fill_angle;
if (opt_key == "fill_density") return &this->fill_density;
if (opt_key == "fill_pattern") return &this->fill_pattern;
if (opt_key == "gap_fill_speed") return &this->gap_fill_speed;
if (opt_key == "infill_extruder") return &this->infill_extruder;
if (opt_key == "infill_extrusion_width") return &this->infill_extrusion_width;
if (opt_key == "infill_every_layers") return &this->infill_every_layers;
if (opt_key == "infill_overlap") return &this->infill_overlap;
if (opt_key == "infill_speed") return &this->infill_speed;
if (opt_key == "overhangs") return &this->overhangs;
if (opt_key == "perimeter_extruder") return &this->perimeter_extruder;
if (opt_key == "perimeter_extrusion_width") return &this->perimeter_extrusion_width;
if (opt_key == "perimeter_speed") return &this->perimeter_speed;
if (opt_key == "perimeters") return &this->perimeters;
if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed;
if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area;
if (opt_key == "solid_infill_extruder") return &this->solid_infill_extruder;
if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width;
if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers;
if (opt_key == "solid_infill_speed") return &this->solid_infill_speed;
if (opt_key == "thin_walls") return &this->thin_walls;
if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width;
if (opt_key == "top_solid_infill_speed") return &this->top_solid_infill_speed;
if (opt_key == "top_solid_layers") return &this->top_solid_layers;
OPT_PTR(bottom_solid_layers);
OPT_PTR(bridge_flow_ratio);
OPT_PTR(bridge_speed);
OPT_PTR(external_fill_pattern);
OPT_PTR(external_perimeter_extrusion_width);
OPT_PTR(external_perimeter_speed);
OPT_PTR(external_perimeters_first);
OPT_PTR(extra_perimeters);
OPT_PTR(fill_angle);
OPT_PTR(fill_density);
OPT_PTR(fill_pattern);
OPT_PTR(gap_fill_speed);
OPT_PTR(infill_extruder);
OPT_PTR(infill_extrusion_width);
OPT_PTR(infill_every_layers);
OPT_PTR(infill_overlap);
OPT_PTR(infill_speed);
OPT_PTR(overhangs);
OPT_PTR(perimeter_extruder);
OPT_PTR(perimeter_extrusion_width);
OPT_PTR(perimeter_speed);
OPT_PTR(perimeters);
OPT_PTR(small_perimeter_speed);
OPT_PTR(solid_infill_below_area);
OPT_PTR(solid_infill_extruder);
OPT_PTR(solid_infill_extrusion_width);
OPT_PTR(solid_infill_every_layers);
OPT_PTR(solid_infill_speed);
OPT_PTR(thin_walls);
OPT_PTR(top_infill_extrusion_width);
OPT_PTR(top_solid_infill_speed);
OPT_PTR(top_solid_layers);
return NULL;
};
@ -306,6 +309,8 @@ class GCodeConfig : public virtual StaticPrintConfig
ConfigOptionBool gcode_comments;
ConfigOptionEnum<GCodeFlavor> gcode_flavor;
ConfigOptionString layer_gcode;
ConfigOptionFloat max_print_speed;
ConfigOptionFloat max_volumetric_speed;
ConfigOptionFloat pressure_advance;
ConfigOptionFloats retract_length;
ConfigOptionFloats retract_length_toolchange;
@ -331,6 +336,8 @@ class GCodeConfig : public virtual StaticPrintConfig
this->gcode_comments.value = false;
this->gcode_flavor.value = gcfRepRap;
this->layer_gcode.value = "";
this->max_print_speed.value = 80;
this->max_volumetric_speed.value = 0;
this->pressure_advance.value = 0;
this->retract_length.values.resize(1);
this->retract_length.values[0] = 2;
@ -353,27 +360,29 @@ class GCodeConfig : public virtual StaticPrintConfig
};
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
if (opt_key == "before_layer_gcode") return &this->before_layer_gcode;
if (opt_key == "end_gcode") return &this->end_gcode;
if (opt_key == "extrusion_axis") return &this->extrusion_axis;
if (opt_key == "extrusion_multiplier") return &this->extrusion_multiplier;
if (opt_key == "filament_diameter") return &this->filament_diameter;
if (opt_key == "gcode_comments") return &this->gcode_comments;
if (opt_key == "gcode_flavor") return &this->gcode_flavor;
if (opt_key == "layer_gcode") return &this->layer_gcode;
if (opt_key == "pressure_advance") return &this->pressure_advance;
if (opt_key == "retract_length") return &this->retract_length;
if (opt_key == "retract_length_toolchange") return &this->retract_length_toolchange;
if (opt_key == "retract_lift") return &this->retract_lift;
if (opt_key == "retract_restart_extra") return &this->retract_restart_extra;
if (opt_key == "retract_restart_extra_toolchange") return &this->retract_restart_extra_toolchange;
if (opt_key == "retract_speed") return &this->retract_speed;
if (opt_key == "start_gcode") return &this->start_gcode;
if (opt_key == "toolchange_gcode") return &this->toolchange_gcode;
if (opt_key == "travel_speed") return &this->travel_speed;
if (opt_key == "use_firmware_retraction") return &this->use_firmware_retraction;
if (opt_key == "use_relative_e_distances") return &this->use_relative_e_distances;
if (opt_key == "use_volumetric_e") return &this->use_volumetric_e;
OPT_PTR(before_layer_gcode);
OPT_PTR(end_gcode);
OPT_PTR(extrusion_axis);
OPT_PTR(extrusion_multiplier);
OPT_PTR(filament_diameter);
OPT_PTR(gcode_comments);
OPT_PTR(gcode_flavor);
OPT_PTR(layer_gcode);
OPT_PTR(max_print_speed);
OPT_PTR(max_volumetric_speed);
OPT_PTR(pressure_advance);
OPT_PTR(retract_length);
OPT_PTR(retract_length_toolchange);
OPT_PTR(retract_lift);
OPT_PTR(retract_restart_extra);
OPT_PTR(retract_restart_extra_toolchange);
OPT_PTR(retract_speed);
OPT_PTR(start_gcode);
OPT_PTR(toolchange_gcode);
OPT_PTR(travel_speed);
OPT_PTR(use_firmware_retraction);
OPT_PTR(use_relative_e_distances);
OPT_PTR(use_volumetric_e);
return NULL;
};
@ -510,56 +519,56 @@ class PrintConfig : public GCodeConfig
};
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
if (opt_key == "avoid_crossing_perimeters") return &this->avoid_crossing_perimeters;
if (opt_key == "bed_shape") return &this->bed_shape;
if (opt_key == "bed_temperature") return &this->bed_temperature;
if (opt_key == "bridge_acceleration") return &this->bridge_acceleration;
if (opt_key == "bridge_fan_speed") return &this->bridge_fan_speed;
if (opt_key == "brim_width") return &this->brim_width;
if (opt_key == "complete_objects") return &this->complete_objects;
if (opt_key == "cooling") return &this->cooling;
if (opt_key == "default_acceleration") return &this->default_acceleration;
if (opt_key == "disable_fan_first_layers") return &this->disable_fan_first_layers;
if (opt_key == "duplicate_distance") return &this->duplicate_distance;
if (opt_key == "extruder_clearance_height") return &this->extruder_clearance_height;
if (opt_key == "extruder_clearance_radius") return &this->extruder_clearance_radius;
if (opt_key == "extruder_offset") return &this->extruder_offset;
if (opt_key == "fan_always_on") return &this->fan_always_on;
if (opt_key == "fan_below_layer_time") return &this->fan_below_layer_time;
if (opt_key == "filament_colour") return &this->filament_colour;
if (opt_key == "first_layer_acceleration") return &this->first_layer_acceleration;
if (opt_key == "first_layer_bed_temperature") return &this->first_layer_bed_temperature;
if (opt_key == "first_layer_extrusion_width") return &this->first_layer_extrusion_width;
if (opt_key == "first_layer_speed") return &this->first_layer_speed;
if (opt_key == "first_layer_temperature") return &this->first_layer_temperature;
if (opt_key == "gcode_arcs") return &this->gcode_arcs;
if (opt_key == "infill_acceleration") return &this->infill_acceleration;
if (opt_key == "infill_first") return &this->infill_first;
if (opt_key == "max_fan_speed") return &this->max_fan_speed;
if (opt_key == "min_fan_speed") return &this->min_fan_speed;
if (opt_key == "min_print_speed") return &this->min_print_speed;
if (opt_key == "min_skirt_length") return &this->min_skirt_length;
if (opt_key == "notes") return &this->notes;
if (opt_key == "nozzle_diameter") return &this->nozzle_diameter;
if (opt_key == "only_retract_when_crossing_perimeters") return &this->only_retract_when_crossing_perimeters;
if (opt_key == "ooze_prevention") return &this->ooze_prevention;
if (opt_key == "output_filename_format") return &this->output_filename_format;
if (opt_key == "perimeter_acceleration") return &this->perimeter_acceleration;
if (opt_key == "post_process") return &this->post_process;
if (opt_key == "resolution") return &this->resolution;
if (opt_key == "retract_before_travel") return &this->retract_before_travel;
if (opt_key == "retract_layer_change") return &this->retract_layer_change;
if (opt_key == "skirt_distance") return &this->skirt_distance;
if (opt_key == "skirt_height") return &this->skirt_height;
if (opt_key == "skirts") return &this->skirts;
if (opt_key == "slowdown_below_layer_time") return &this->slowdown_below_layer_time;
if (opt_key == "spiral_vase") return &this->spiral_vase;
if (opt_key == "standby_temperature_delta") return &this->standby_temperature_delta;
if (opt_key == "temperature") return &this->temperature;
if (opt_key == "threads") return &this->threads;
if (opt_key == "vibration_limit") return &this->vibration_limit;
if (opt_key == "wipe") return &this->wipe;
if (opt_key == "z_offset") return &this->z_offset;
OPT_PTR(avoid_crossing_perimeters);
OPT_PTR(bed_shape);
OPT_PTR(bed_temperature);
OPT_PTR(bridge_acceleration);
OPT_PTR(bridge_fan_speed);
OPT_PTR(brim_width);
OPT_PTR(complete_objects);
OPT_PTR(cooling);
OPT_PTR(default_acceleration);
OPT_PTR(disable_fan_first_layers);
OPT_PTR(duplicate_distance);
OPT_PTR(extruder_clearance_height);
OPT_PTR(extruder_clearance_radius);
OPT_PTR(extruder_offset);
OPT_PTR(fan_always_on);
OPT_PTR(fan_below_layer_time);
OPT_PTR(filament_colour);
OPT_PTR(first_layer_acceleration);
OPT_PTR(first_layer_bed_temperature);
OPT_PTR(first_layer_extrusion_width);
OPT_PTR(first_layer_speed);
OPT_PTR(first_layer_temperature);
OPT_PTR(gcode_arcs);
OPT_PTR(infill_acceleration);
OPT_PTR(infill_first);
OPT_PTR(max_fan_speed);
OPT_PTR(min_fan_speed);
OPT_PTR(min_print_speed);
OPT_PTR(min_skirt_length);
OPT_PTR(notes);
OPT_PTR(nozzle_diameter);
OPT_PTR(only_retract_when_crossing_perimeters);
OPT_PTR(ooze_prevention);
OPT_PTR(output_filename_format);
OPT_PTR(perimeter_acceleration);
OPT_PTR(post_process);
OPT_PTR(resolution);
OPT_PTR(retract_before_travel);
OPT_PTR(retract_layer_change);
OPT_PTR(skirt_distance);
OPT_PTR(skirt_height);
OPT_PTR(skirts);
OPT_PTR(slowdown_below_layer_time);
OPT_PTR(spiral_vase);
OPT_PTR(standby_temperature_delta);
OPT_PTR(temperature);
OPT_PTR(threads);
OPT_PTR(vibration_limit);
OPT_PTR(wipe);
OPT_PTR(z_offset);
// look in parent class
ConfigOption* opt;
@ -585,10 +594,10 @@ class HostConfig : public virtual StaticPrintConfig
};
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
if (opt_key == "octoprint_host") return &this->octoprint_host;
if (opt_key == "octoprint_apikey") return &this->octoprint_apikey;
if (opt_key == "serial_port") return &this->serial_port;
if (opt_key == "serial_speed") return &this->serial_speed;
OPT_PTR(octoprint_host);
OPT_PTR(octoprint_apikey);
OPT_PTR(serial_port);
OPT_PTR(serial_speed);
return NULL;
};

View File

@ -340,39 +340,97 @@ PrintObject::has_support_material() const
|| this->config.support_material_enforce_layers > 0;
}
void
PrintObject::process_external_surfaces()
{
FOREACH_REGION(this->_print, region) {
size_t region_id = region - this->_print->regions.begin();
FOREACH_LAYER(this, layer_it) {
const Layer* lower_layer = (layer_it == this->layers.begin())
? NULL
: *(layer_it-1);
(*layer_it)->get_region(region_id)->process_external_surfaces(lower_layer);
}
}
}
/* This method applies bridge flow to the first internal solid layer above
sparse infill */
void
PrintObject::bridge_over_infill()
{
FOREACH_REGION(this->_print, region) {
size_t region_id = region - this->_print->regions.begin();
double fill_density = (*region)->config.fill_density.value;
if (fill_density == 100) continue;
// skip bridging in case there are no voids
if ((*region)->config.fill_density.value == 100) continue;
// get bridge flow
Flow bridge_flow = (*region)->flow(
frSolidInfill,
-1, // layer height, not relevant for bridge flow
true, // bridge
false, // first layer
-1, // custom width, not relevant for bridge flow
*this
);
FOREACH_LAYER(this, layer_it) {
// skip first layer
if (layer_it == this->layers.begin()) continue;
Layer* layer = *layer_it;
Layer* lower_layer = *(layer_it - 1);
LayerRegion* layerm = layer->get_region(region_id);
// compute the areas needing bridge math
Polygons internal_solid, lower_internal;
// extract the stInternalSolid surfaces that might be transformed into bridges
Polygons internal_solid;
layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid);
FOREACH_LAYERREGION(lower_layer, lower_layerm_it)
(*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal);
// check whether the lower area is deep enough for absorbing the extra flow
// (for obvious physical reasons but also for preventing the bridge extrudates
// from overflowing in 3D preview)
ExPolygons to_bridge;
intersection(internal_solid, lower_internal, &to_bridge);
if (to_bridge.empty()) continue;
ExPolygons not_to_bridge;
diff(internal_solid, to_bridge, &not_to_bridge, true);
{
Polygons to_bridge_pp = internal_solid;
// iterate through lower layers spanned by bridge_flow
double bottom_z = layer->print_z - bridge_flow.height;
for (int i = (layer_it - this->layers.begin()) - 1; i >= 0; --i) {
const Layer* lower_layer = this->layers[i];
// stop iterating if layer is lower than bottom_z
if (lower_layer->print_z < bottom_z) break;
// iterate through regions and collect internal surfaces
Polygons lower_internal;
FOREACH_LAYERREGION(lower_layer, lower_layerm_it)
(*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal);
// intersect such lower internal surfaces with the candidate solid surfaces
to_bridge_pp = intersection(to_bridge_pp, lower_internal);
}
// there's no point in bridging too thin/short regions
{
double min_width = bridge_flow.scaled_width() * 3;
to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width);
}
if (to_bridge_pp.empty()) continue;
// convert into ExPolygons
to_bridge = union_ex(to_bridge_pp);
}
#ifdef SLIC3R_DEBUG
printf("Bridging %zu internal areas at layer %d\n", to_bridge.size(), layer->id());
printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id());
#endif
// compute the remaning internal solid surfaces as difference
ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, true);
// build the new collection of fill_surfaces
{
Surfaces new_surfaces;

View File

@ -2,6 +2,11 @@
namespace Slic3r {
Surface::operator Polygons() const
{
return this->expolygon;
}
double
Surface::area() const
{

View File

@ -21,6 +21,7 @@ class Surface
: surface_type(_surface_type), expolygon(_expolygon),
thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
{};
operator Polygons() const;
double area() const;
bool is_solid() const;
bool is_external() const;

View File

@ -111,6 +111,12 @@ SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons)
}
}
void
SurfaceCollection::append(const SurfaceCollection &coll)
{
this->surfaces.insert(this->surfaces.end(), coll.surfaces.begin(), coll.surfaces.end());
}
#ifdef SLIC3RXS
REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
#endif

View File

@ -11,6 +11,9 @@ class SurfaceCollection
public:
Surfaces surfaces;
SurfaceCollection() {};
SurfaceCollection(const Surfaces &_surfaces)
: surfaces(_surfaces) {};
operator Polygons() const;
operator ExPolygons() const;
void simplify(double tolerance);
@ -19,6 +22,7 @@ class SurfaceCollection
template <class T> bool any_bottom_contains(const T &item) const;
SurfacesPtr filter_by_type(SurfaceType type);
void filter_by_type(SurfaceType type, Polygons* polygons);
void append(const SurfaceCollection &coll);
};
}

View File

@ -6,11 +6,17 @@
#include <iostream>
#include <sstream>
#define SLIC3R_VERSION "1.2.8-dev"
#define SLIC3R_VERSION "1.2.10-dev"
#define EPSILON 1e-4
#define SCALING_FACTOR 0.000001
#define RESOLUTION 0.0125
#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR)
#define PI 3.141592653589793238
#define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15
#define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI
#define INSET_OVERLAP_TOLERANCE 0.4
#define EXTERNAL_INFILL_MARGIN 3
#define scale_(val) (val / SCALING_FACTOR)
#define unscale(val) (val * SCALING_FACTOR)
#define SCALED_EPSILON scale_(EPSILON)

View File

@ -44,6 +44,7 @@ class Ref {
public:
Ref() : val(NULL) {}
Ref(T* t) : val(t) {}
Ref(const T* t) : val(const_cast<T*>(t)) {}
operator T*() const { return val; }
static const char* CLASS() { return ClassTraits<T>::name_ref; }
};

View File

@ -7,7 +7,7 @@ use Slic3r::XS;
use Test::More tests => 5;
{
my $print = Slic3r::Print->_new;
my $print = Slic3r::Print->new;
isa_ok $print, 'Slic3r::Print';
isa_ok $print->config, 'Slic3r::Config::Print::Ref';
isa_ok $print->default_object_config, 'Slic3r::Config::PrintObject::Ref';

17
xs/t/21_gcode.t Normal file
View File

@ -0,0 +1,17 @@
#!/usr/bin/perl
use strict;
use warnings;
use Slic3r::XS;
use Test::More tests => 2;
{
my $gcodegen = Slic3r::GCode->new;
$gcodegen->set_origin(Slic3r::Pointf->new(10,0));
is_deeply $gcodegen->origin->pp, [10,0], 'set_origin';
$gcodegen->origin->translate(5,5);
is_deeply $gcodegen->origin->pp, [15,5], 'origin returns reference to point';
}
__END__

View File

@ -9,14 +9,10 @@
~BridgeDetector();
bool detect_angle();
Polygons coverage()
%code{% THIS->coverage(&RETVAL); %};
Polygons coverage_by_angle(double angle)
%code{% THIS->coverage(angle, &RETVAL); %};
Polylines unsupported_edges()
%code{% THIS->unsupported_edges(&RETVAL); %};
Polylines unsupported_edges_by_angle(double angle)
%code{% THIS->unsupported_edges(angle, &RETVAL); %};
Polygons coverage();
%name{coverage_by_angle} Polygons coverage(double angle);
Polylines unsupported_edges();
%name{unsupported_edges_by_angle} Polylines unsupported_edges(double angle);
double angle()
%code{% RETVAL = THIS->angle; %};
double resolution()

View File

@ -27,10 +27,10 @@
%code{% RETVAL = THIS->equals(*other); %};
void apply_static(FullPrintConfig* other)
%code{% THIS->apply(*other, true); %};
std::vector<std::string> get_keys()
%code{% THIS->keys(&RETVAL); %};
%name{get_keys} std::vector<std::string> keys();
void erase(t_config_option_key opt_key);
void normalize();
%name{setenv} void setenv_();
};
%name{Slic3r::Config::GCode} class GCodeConfig {
@ -51,9 +51,9 @@
%code{% THIS->apply(*other, true); %};
void apply_dynamic(DynamicPrintConfig* other)
%code{% THIS->apply(*other, true); %};
std::vector<std::string> get_keys()
%code{% THIS->keys(&RETVAL); %};
%name{get_keys} std::vector<std::string> keys();
std::string get_extrusion_axis();
%name{setenv} void setenv_();
};
%name{Slic3r::Config::Print} class PrintConfig {
@ -72,9 +72,9 @@
double get_abs_value(t_config_option_key opt_key, double ratio_over);
void apply_dynamic(DynamicPrintConfig* other)
%code{% THIS->apply(*other, true); %};
std::vector<std::string> get_keys()
%code{% THIS->keys(&RETVAL); %};
%name{get_keys} std::vector<std::string> keys();
std::string get_extrusion_axis();
%name{setenv} void setenv_();
};
%name{Slic3r::Config::PrintRegion} class PrintRegionConfig {
@ -95,8 +95,8 @@
%code{% THIS->apply(*other, true); %};
void apply_dynamic(DynamicPrintConfig* other)
%code{% THIS->apply(*other, true); %};
std::vector<std::string> get_keys()
%code{% THIS->keys(&RETVAL); %};
%name{get_keys} std::vector<std::string> keys();
%name{setenv} void setenv_();
};
%name{Slic3r::Config::PrintObject} class PrintObjectConfig {
@ -117,8 +117,8 @@
%code{% THIS->apply(*other, true); %};
void apply_dynamic(DynamicPrintConfig* other)
%code{% THIS->apply(*other, true); %};
std::vector<std::string> get_keys()
%code{% THIS->keys(&RETVAL); %};
%name{get_keys} std::vector<std::string> keys();
%name{setenv} void setenv_();
};
%name{Slic3r::Config::Full} class FullPrintConfig {
@ -143,9 +143,9 @@
%code{% THIS->apply(*other, true); %};
void apply_dynamic(DynamicPrintConfig* other)
%code{% THIS->apply(*other, true); %};
std::vector<std::string> get_keys()
%code{% THIS->keys(&RETVAL); %};
%name{get_keys} std::vector<std::string> keys();
std::string get_extrusion_axis();
%name{setenv} void setenv_();
};
%package{Slic3r::Config};

View File

@ -7,6 +7,7 @@
%name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection {
%name{_new} ExtrusionEntityCollection();
~ExtrusionEntityCollection();
Clone<ExtrusionEntityCollection> clone()
%code{% RETVAL = THIS->clone(); %};
void reverse();
@ -28,6 +29,12 @@
%code{% RETVAL = THIS->entities.size(); %};
int items_count()
%code{% RETVAL = THIS->items_count(); %};
ExtrusionEntityCollection* flatten()
%code{%
RETVAL = new ExtrusionEntityCollection();
THIS->flatten(RETVAL);
%};
double min_mm3_per_mm();
bool empty()
%code{% RETVAL = THIS->entities.empty(); %};
std::vector<size_t> orig_indices()
@ -35,14 +42,6 @@
Polygons grow();
%{
void
ExtrusionEntityCollection::DESTROY()
CODE:
for (ExtrusionEntitiesPtr::iterator it = THIS->entities.begin(); it != THIS->entities.end(); ++it) {
delete *it;
}
delete THIS;
SV*
ExtrusionEntityCollection::arrayref()
CODE:

View File

@ -38,9 +38,8 @@ ExtrusionLoop::arrayref()
CODE:
AV* av = newAV();
av_fill(av, THIS->paths.size()-1);
int i = 0;
for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) {
av_store(av, i++, perl_to_SV_ref(*it));
av_store(av, it - THIS->paths.begin(), perl_to_SV_ref(*it));
}
RETVAL = newRV_noinc((SV*)av);
OUTPUT:

View File

@ -26,9 +26,6 @@
bool is_infill();
bool is_solid_infill();
bool is_bridge();
std::string gcode(Extruder* extruder, double e, double F,
double xofs, double yofs, std::string extrusion_axis,
std::string gcode_line_suffix);
Polygons grow();
%{
@ -133,6 +130,7 @@ ExtrusionPath::subtract_expolygons(ExPolygonCollection* collection)
IV
_constant()
ALIAS:
EXTR_ROLE_NONE = erNone
EXTR_ROLE_PERIMETER = erPerimeter
EXTR_ROLE_EXTERNAL_PERIMETER = erExternalPerimeter
EXTR_ROLE_OVERHANG_PERIMETER = erOverhangPerimeter

183
xs/xsp/GCode.xsp Normal file
View File

@ -0,0 +1,183 @@
%module{Slic3r::XS};
%{
#include <myinit.h>
#include "libslic3r/GCode.hpp"
%}
%name{Slic3r::GCode::AvoidCrossingPerimeters} class AvoidCrossingPerimeters {
AvoidCrossingPerimeters();
~AvoidCrossingPerimeters();
void init_external_mp(ExPolygons islands);
void init_layer_mp(ExPolygons islands);
Clone<Polyline> travel_to(GCode* gcode, Point* point)
%code{% RETVAL = THIS->travel_to(*gcode, *point); %};
bool use_external_mp()
%code{% RETVAL = THIS->use_external_mp; %};
void set_use_external_mp(bool value)
%code{% THIS->use_external_mp = value; %};
bool use_external_mp_once()
%code{% RETVAL = THIS->use_external_mp_once; %};
void set_use_external_mp_once(bool value)
%code{% THIS->use_external_mp_once = value; %};
bool disable_once()
%code{% RETVAL = THIS->disable_once; %};
void set_disable_once(bool value)
%code{% THIS->disable_once = value; %};
};
%name{Slic3r::GCode::OozePrevention} class OozePrevention {
OozePrevention();
~OozePrevention();
bool enable()
%code{% RETVAL = THIS->enable; %};
void set_enable(bool value)
%code{% THIS->enable = value; %};
Points standby_points()
%code{% RETVAL = THIS->standby_points; %};
void set_standby_points(Points points)
%code{% THIS->standby_points = points; %};
std::string pre_toolchange(GCode* gcodegen)
%code{% RETVAL = THIS->pre_toolchange(*gcodegen); %};
std::string post_toolchange(GCode* gcodegen)
%code{% RETVAL = THIS->post_toolchange(*gcodegen); %};
};
%name{Slic3r::GCode::Wipe} class Wipe {
Wipe();
~Wipe();
bool has_path();
void reset_path();
std::string wipe(GCode* gcodegen, bool toolchange = false)
%code{% RETVAL = THIS->wipe(*gcodegen, toolchange); %};
bool enable()
%code{% RETVAL = THIS->enable; %};
void set_enable(bool value)
%code{% THIS->enable = value; %};
Ref<Polyline> path()
%code{% RETVAL = &(THIS->path); %};
void set_path(Polyline* value)
%code{% THIS->path = *value; %};
};
%name{Slic3r::GCode} class GCode {
GCode();
~GCode();
Ref<Pointf> origin()
%code{% RETVAL = &(THIS->origin); %};
Ref<FullPrintConfig> config()
%code{% RETVAL = &(THIS->config); %};
Ref<GCodeWriter> writer()
%code{% RETVAL = &(THIS->writer); %};
Ref<PlaceholderParser> placeholder_parser()
%code{% RETVAL = THIS->placeholder_parser; %};
void set_placeholder_parser(PlaceholderParser* ptr)
%code{% THIS->placeholder_parser = ptr; %};
Ref<OozePrevention> ooze_prevention()
%code{% RETVAL = &(THIS->ooze_prevention); %};
Ref<Wipe> wipe()
%code{% RETVAL = &(THIS->wipe); %};
Ref<AvoidCrossingPerimeters> avoid_crossing_perimeters()
%code{% RETVAL = &(THIS->avoid_crossing_perimeters); %};
bool enable_loop_clipping()
%code{% RETVAL = THIS->enable_loop_clipping; %};
void set_enable_loop_clipping(bool value)
%code{% THIS->enable_loop_clipping = value; %};
bool enable_cooling_markers()
%code{% RETVAL = THIS->enable_cooling_markers; %};
void set_enable_cooling_markers(bool value)
%code{% THIS->enable_cooling_markers = value; %};
int layer_count()
%code{% RETVAL = THIS->layer_count; %};
void set_layer_count(int value)
%code{% THIS->layer_count = value; %};
int layer_index()
%code{% RETVAL = THIS->layer_index; %};
void set_layer_index(int value)
%code{% THIS->layer_index = value; %};
bool has_layer()
%code{% RETVAL = THIS->layer != NULL; %};
Ref<Layer> layer()
%code{% RETVAL = THIS->layer; %};
void set_layer(Layer* ptr)
%code{% THIS->layer = ptr; %};
bool first_layer()
%code{% RETVAL = THIS->first_layer; %};
void set_first_layer(bool value)
%code{% THIS->first_layer = value; %};
unsigned int elapsed_time()
%code{% RETVAL = THIS->elapsed_time; %};
void set_elapsed_time(unsigned int value)
%code{% THIS->elapsed_time = value; %};
bool last_pos_defined();
Ref<Point> last_pos()
%code{% RETVAL = &(THIS->last_pos()); %};
void set_last_pos(Point* pos)
%code{% THIS->set_last_pos(*pos); %};
double volumetric_speed()
%code{% RETVAL = THIS->volumetric_speed; %};
void set_volumetric_speed(double value)
%code{% THIS->volumetric_speed = value; %};
void apply_print_config(PrintConfig* print_config)
%code{% THIS->apply_print_config(*print_config); %};
void set_extruders(std::vector<unsigned int> extruder_ids);
void set_origin(Pointf* pointf)
%code{% THIS->set_origin(*pointf); %};
std::string preamble();
std::string change_layer(Layer* layer)
%code{% RETVAL = THIS->change_layer(*layer); %};
%name{extrude_loop} std::string extrude(ExtrusionLoop* loop, std::string description = "", double speed = -1)
%code{% RETVAL = THIS->extrude(*loop, description, speed); %};
%name{extrude_path} std::string extrude(ExtrusionPath* path, std::string description = "", double speed = -1)
%code{% RETVAL = THIS->extrude(*path, description, speed); %};
std::string travel_to(Point* point, ExtrusionRole role, std::string comment)
%code{% RETVAL = THIS->travel_to(*point, role, comment); %};
bool needs_retraction(Polyline* travel, ExtrusionRole role = erNone)
%code{% RETVAL = THIS->needs_retraction(*travel, role); %};
std::string retract(bool toolchange = false);
std::string unretract();
std::string set_extruder(unsigned int extruder_id);
Clone<Pointf> point_to_gcode(Point* point)
%code{% RETVAL = THIS->point_to_gcode(*point); %};
%{
std::string
GCode::extrude(entity, description, speed)
SV* entity
std::string description;
double speed;
CODE:
ExtrusionEntity* e = (ExtrusionEntity *)SvIV((SV*)SvRV( entity ));
RETVAL = THIS->extrude(*e, description, speed);
OUTPUT:
RETVAL
%}
};

View File

@ -17,8 +17,8 @@
%code%{ RETVAL = &THIS->thin_fills; %};
Ref<SurfaceCollection> fill_surfaces()
%code%{ RETVAL = &THIS->fill_surfaces; %};
Ref<ExPolygonCollection> bridged()
%code%{ RETVAL = &THIS->bridged; %};
Polygons bridged()
%code%{ RETVAL = THIS->bridged; %};
Ref<PolylineCollection> unsupported_bridge_edges()
%code%{ RETVAL = &THIS->unsupported_bridge_edges; %};
Ref<ExtrusionEntityCollection> perimeters()
@ -30,10 +30,16 @@
%code%{ RETVAL = THIS->flow(role, bridge, width); %};
void merge_slices();
void prepare_fill_surfaces();
void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces)
%code%{ THIS->make_perimeters(*slices, fill_surfaces); %};
double infill_area_threshold();
};
%name{Slic3r::Layer} class Layer {
// owned by PrintObject, no constructor/destructor
Ref<Layer> as_layer()
%code%{ RETVAL = THIS; %};
int id();
void set_id(int id);
@ -70,6 +76,9 @@
int ptr()
%code%{ RETVAL = (int)(intptr_t)THIS; %};
Ref<SupportLayer> as_support_layer()
%code%{ RETVAL = dynamic_cast<SupportLayer*>(THIS); %};
void make_slices();
void merge_slices();
bool any_internal_region_slice_contains_polyline(Polyline* polyline)
@ -80,7 +89,10 @@
%name{Slic3r::Layer::Support} class SupportLayer {
// owned by PrintObject, no constructor/destructor
Ref<Layer> as_layer()
%code%{ RETVAL = THIS; %};
Ref<ExPolygonCollection> support_islands()
%code%{ RETVAL = &THIS->support_islands; %};
Ref<ExtrusionEntityCollection> support_fills()

View File

@ -0,0 +1,34 @@
%module{Slic3r::XS};
%{
#include <myinit.h>
#include "libslic3r/PerimeterGenerator.hpp"
%}
%name{Slic3r::Layer::PerimeterGenerator} class PerimeterGenerator {
PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow* flow,
PrintRegionConfig* config, PrintObjectConfig* object_config,
PrintConfig* print_config, ExtrusionEntityCollection* loops,
ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces)
%code{% RETVAL = new PerimeterGenerator(slices, layer_height, *flow,
config, object_config, print_config, loops, gap_fill, fill_surfaces); %};
~PerimeterGenerator();
void set_lower_slices(ExPolygonCollection* lower_slices)
%code{% THIS->lower_slices = lower_slices; %};
void set_layer_id(int layer_id)
%code{% THIS->layer_id = layer_id; %};
void set_perimeter_flow(Flow* flow)
%code{% THIS->perimeter_flow = *flow; %};
void set_ext_perimeter_flow(Flow* flow)
%code{% THIS->ext_perimeter_flow = *flow; %};
void set_overhang_flow(Flow* flow)
%code{% THIS->overhang_flow = *flow; %};
void set_solid_infill_flow(Flow* flow)
%code{% THIS->solid_infill_flow = *flow; %};
Ref<PrintRegionConfig> config()
%code{% RETVAL = THIS->config; %};
void process();
};

View File

@ -7,39 +7,16 @@
%}
%name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser {
%name{_new} PlaceholderParser();
PlaceholderParser();
~PlaceholderParser();
Clone<PlaceholderParser> clone()
%code{% RETVAL = THIS; %};
void update_timestamp();
void apply_env_variables();
void apply_config(DynamicPrintConfig *config)
%code%{ THIS->apply_config(*config); %};
void set(std::string key, std::string value);
void set_multiple(std::string key, std::vector<std::string> values)
%code%{ THIS->set(key, values); %};
void _single_set(std::string k, std::string v)
%code%{ THIS->_single[k] = v; %};
std::string _single_get(std::string k)
%code%{ RETVAL = THIS->_single[k]; %};
std::string _multiple_get(std::string k)
%code%{ RETVAL = THIS->_multiple[k]; %};
std::vector<std::string> _single_keys()
%code{%
for (std::map<std::string, std::string>::iterator i = THIS->_single.begin();
i != THIS->_single.end(); ++i)
{
RETVAL.push_back(i->first);
}
%};
std::vector<std::string> _multiple_keys()
%code{%
for (std::map<std::string, std::string>::iterator i = THIS->_multiple.begin();
i != THIS->_multiple.end(); ++i)
{
RETVAL.push_back(i->first);
}
%};
%name{set_multiple} void set(std::string key, std::vector<std::string> values);
std::string process(std::string str) const;
};

View File

@ -107,6 +107,7 @@ _constant()
void set_step_started(PrintObjectStep step)
%code%{ THIS->state.set_started(step); %};
void process_external_surfaces();
void bridge_over_infill();
int ptr()
@ -115,7 +116,7 @@ _constant()
%name{Slic3r::Print} class Print {
%name{_new} Print();
Print();
~Print();
Ref<PrintConfig> config()

View File

@ -170,6 +170,22 @@ PlaceholderParser* O_OBJECT_SLIC3R
Ref<PlaceholderParser> O_OBJECT_SLIC3R_T
Clone<PlaceholderParser> O_OBJECT_SLIC3R_T
AvoidCrossingPerimeters* O_OBJECT_SLIC3R
Ref<AvoidCrossingPerimeters> O_OBJECT_SLIC3R_T
Clone<AvoidCrossingPerimeters> O_OBJECT_SLIC3R_T
Wipe* O_OBJECT_SLIC3R
Ref<Wipe> O_OBJECT_SLIC3R_T
Clone<Wipe> O_OBJECT_SLIC3R_T
OozePrevention* O_OBJECT_SLIC3R
Ref<OozePrevention> O_OBJECT_SLIC3R_T
Clone<OozePrevention> O_OBJECT_SLIC3R_T
GCode* O_OBJECT_SLIC3R
Ref<GCode> O_OBJECT_SLIC3R_T
Clone<GCode> O_OBJECT_SLIC3R_T
MotionPlanner* O_OBJECT_SLIC3R
Ref<MotionPlanner> O_OBJECT_SLIC3R_T
Clone<MotionPlanner> O_OBJECT_SLIC3R_T
@ -186,6 +202,10 @@ BridgeDetector* O_OBJECT_SLIC3R
Ref<BridgeDetector> O_OBJECT_SLIC3R_T
Clone<BridgeDetector> O_OBJECT_SLIC3R_T
PerimeterGenerator* O_OBJECT_SLIC3R
Ref<PerimeterGenerator> O_OBJECT_SLIC3R_T
Clone<PerimeterGenerator> O_OBJECT_SLIC3R_T
GLVertexArray* O_OBJECT_SLIC3R
Axis T_UV

View File

@ -97,6 +97,12 @@
%typemap{BridgeDetector*};
%typemap{Ref<BridgeDetector>}{simple};
%typemap{Clone<BridgeDetector>}{simple};
%typemap{SurfaceCollection*};
%typemap{Ref<SurfaceCollection>}{simple};
%typemap{Clone<SurfaceCollection>}{simple};
%typemap{PerimeterGenerator*};
%typemap{Ref<PerimeterGenerator>}{simple};
%typemap{Clone<PerimeterGenerator>}{simple};
%typemap{Surface*};
%typemap{Ref<Surface>}{simple};
@ -128,6 +134,22 @@
%typemap{Ref<PlaceholderParser>}{simple};
%typemap{Clone<PlaceholderParser>}{simple};
%typemap{AvoidCrossingPerimeters*};
%typemap{Ref<AvoidCrossingPerimeters>}{simple};
%typemap{Clone<AvoidCrossingPerimeters>}{simple};
%typemap{Wipe*};
%typemap{Ref<Wipe>}{simple};
%typemap{Clone<Wipe>}{simple};
%typemap{OozePrevention*};
%typemap{Ref<OozePrevention>}{simple};
%typemap{Clone<OozePrevention>}{simple};
%typemap{GCode*};
%typemap{Ref<GCode>}{simple};
%typemap{Clone<GCode>}{simple};
%typemap{Points};
%typemap{Pointfs};
@ -140,7 +162,6 @@
%typemap{Polygons*};
%typemap{TriangleMesh*};
%typemap{TriangleMeshPtrs};
%typemap{Ref<SurfaceCollection>}{simple};
%typemap{Extruder*};
%typemap{Ref<Extruder>}{simple};
%typemap{Clone<Extruder>}{simple};