Merge branch 'master' into sender

This commit is contained in:
Alessandro Ranellucci 2014-12-26 01:30:48 +01:00
commit 005f138ce7
102 changed files with 3245 additions and 1672 deletions

View File

@ -23,6 +23,7 @@ my %prereqs = qw(
Test::More 0
Thread::Semaphore 0
IO::Scalar 0
threads 1.96
Time::HiRes 0
);
my %recommends = qw(

View File

@ -351,10 +351,11 @@ The author of the Silk icon set is Mark James.
--extruder-offset Offset of each extruder, if firmware doesn't handle the displacement
(can be specified multiple times, default: 0x0)
--perimeter-extruder
Extruder to use for perimeters (1+, default: 1)
Extruder to use for perimeters and brim (1+, default: 1)
--infill-extruder Extruder to use for infill (1+, default: 1)
--solid-infill-extruder Extruder to use for solid infill (1+, default: 1)
--support-material-extruder
Extruder to use for support material (1+, default: 1)
Extruder to use for support material, raft and skirt (1+, default: 1)
--support-material-interface-extruder
Extruder to use for support material interface (1+, default: 1)
--ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping

View File

@ -19,14 +19,15 @@ our $have_threads;
BEGIN {
use Config;
$have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
warn "threads.pm >= 1.96 is required, please update\n" if $have_threads && $threads::VERSION < 1.96;
### temporarily disable threads if using the broken Moo version
use Moo;
$have_threads = 0 if $Moo::VERSION == 1.003000;
}
warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n"
if $^V >= v5.16;
warn "Running Slic3r under Perl 5.16 is not supported nor recommended\n"
if $^V == v5.16;
use FindBin;
our $var = "$FindBin::Bin/var";
@ -48,7 +49,6 @@ use Slic3r::Format::STL;
use Slic3r::GCode;
use Slic3r::GCode::ArcFitting;
use Slic3r::GCode::CoolingBuffer;
use Slic3r::GCode::Layer;
use Slic3r::GCode::MotionPlanner;
use Slic3r::GCode::PlaceholderParser;
use Slic3r::GCode::PressureRegulator;
@ -65,6 +65,7 @@ use Slic3r::Point;
use Slic3r::Polygon;
use Slic3r::Polyline;
use Slic3r::Print;
use Slic3r::Print::GCode;
use Slic3r::Print::Object;
use Slic3r::Print::Simple;
use Slic3r::Print::SupportMaterial;
@ -79,29 +80,40 @@ use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI;
use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15;
use constant INFILL_OVERLAP_OVER_SPACING => 0.45;
use constant EXTERNAL_INFILL_MARGIN => 3;
use constant INSET_OVERLAP_TOLERANCE => 0.2;
use constant INSET_OVERLAP_TOLERANCE => 0.4;
# keep track of threads we created
my @my_threads = ();
my @threads : shared = ();
my $sema = Thread::Semaphore->new;
my $pause_sema = Thread::Semaphore->new;
my $parallel_sema;
my $paused = 0;
sub spawn_thread {
my ($cb) = @_;
my $parent_tid = threads->tid;
lock @threads;
@_ = ();
my $thread = threads->create(sub {
@my_threads = ();
Slic3r::debugf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid;
local $SIG{'KILL'} = sub {
Slic3r::debugf "Exiting thread...\n";
Slic3r::debugf "Exiting thread %d...\n", threads->tid;
$parallel_sema->up if $parallel_sema;
kill_all_threads();
Slic3r::thread_cleanup();
threads->exit();
};
local $SIG{'STOP'} = sub {
$sema->down;
$sema->up;
$pause_sema->down;
$pause_sema->up;
};
$cb->();
});
push @my_threads, $thread->tid;
push @threads, $thread->tid;
return $thread;
}
@ -109,15 +121,21 @@ sub spawn_thread {
sub parallelize {
my %params = @_;
lock @threads;
if (!$params{disable} && $Slic3r::have_threads && $params{threads} > 1) {
my @items = (ref $params{items} eq 'CODE') ? $params{items}->() : @{$params{items}};
my $q = Thread::Queue->new;
$q->enqueue(@items, (map undef, 1..$params{threads}));
$parallel_sema = Thread::Semaphore->new(-$params{threads});
$parallel_sema->up;
my $thread_cb = sub {
# execute thread callback
$params{thread_cb}->($q);
# signal the parent thread that we're done
$parallel_sema->up;
# cleanup before terminating thread
Slic3r::thread_cleanup();
@ -129,17 +147,17 @@ sub parallelize {
# The downside to using this exit is that we can't return
# any value to the main thread but we're not doing that
# anymore anyway.
# collect_cb is completely useless now
# and should be removed from the codebase.
threads->exit;
};
$params{collect_cb} ||= sub {};
@_ = ();
my @my_threads = map spawn_thread($thread_cb), 1..$params{threads};
foreach my $th (@my_threads) {
$params{collect_cb}->($th->join);
}
# We use a semaphore instead of $th->join because joined threads are
# not listed by threads->list or threads->object anymore, thus can't
# be signalled.
$parallel_sema->down;
$_->detach for @my_threads;
} else {
$params{no_threads_cb}->();
}
@ -181,6 +199,7 @@ sub thread_cleanup {
*Slic3r::Geometry::BoundingBoxf::DESTROY = sub {};
*Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {};
*Slic3r::Line::DESTROY = sub {};
*Slic3r::Linef3::DESTROY = sub {};
*Slic3r::Model::DESTROY = sub {};
*Slic3r::Model::Object::DESTROY = sub {};
*Slic3r::Point::DESTROY = sub {};
@ -198,35 +217,45 @@ sub thread_cleanup {
}
sub get_running_threads {
return grep defined($_), map threads->object($_), @threads;
return grep defined($_), map threads->object($_), @_;
}
sub kill_all_threads {
# detach any running thread created in the current one
my @killed = ();
foreach my $thread (get_running_threads()) {
# if we're the main thread, we send SIGKILL to all the running threads
if (threads->tid == 0) {
lock @threads;
foreach my $thread (get_running_threads(@threads)) {
Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid;
$thread->kill('KILL');
push @killed, $thread;
}
# unlock semaphore before we block on wait
# otherwise we'd get a deadlock if threads were paused
resume_threads();
$_->join for @killed; # block until threads are killed
@threads = ();
resume_all_threads();
}
sub pause_threads {
# in any thread we wait for our children
foreach my $thread (get_running_threads(@my_threads)) {
Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid;
$thread->join; # block until threads are killed
Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid;
}
@my_threads = ();
}
sub pause_all_threads {
return if $paused;
lock @threads;
$paused = 1;
$sema->down;
$_->kill('STOP') for get_running_threads();
$pause_sema->down;
$_->kill('STOP') for get_running_threads(@threads);
}
sub resume_threads {
sub resume_all_threads {
return unless $paused;
lock @threads;
$paused = 0;
$sema->up;
$pause_sema->up;
}
sub encode_path {

View File

@ -250,14 +250,14 @@ sub validate {
die "Invalid value for --fill-pattern\n"
if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}};
# --solid-fill-pattern
die "Invalid value for --solid-fill-pattern\n"
if !first { $_ eq $self->solid_fill_pattern } @{$Options->{solid_fill_pattern}{values}};
# --external-fill-pattern
die "Invalid value for --external-fill-pattern\n"
if !first { $_ eq $self->external_fill_pattern } @{$Options->{external_fill_pattern}{values}};
# --fill-density
die "The selected fill pattern is not supposed to work at 100% density\n"
if $self->fill_density == 100
&& !first { $_ eq $self->fill_pattern } @{$Options->{solid_fill_pattern}{values}};
&& !first { $_ eq $self->fill_pattern } @{$Options->{external_fill_pattern}{values}};
# --infill-every-layers
die "Invalid value for --infill-every-layers\n"

View File

@ -3,14 +3,9 @@ use Moo;
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Fill::3DHoneycomb;
use Slic3r::Fill::ArchimedeanChords;
use Slic3r::Fill::Base;
use Slic3r::Fill::Concentric;
use Slic3r::Fill::Flowsnake;
use Slic3r::Fill::HilbertCurve;
use Slic3r::Fill::Honeycomb;
use Slic3r::Fill::Line;
use Slic3r::Fill::OctagramSpiral;
use Slic3r::Fill::PlanePath;
use Slic3r::Fill::Rectilinear;
use Slic3r::Flow ':roles';
@ -83,7 +78,7 @@ sub make_fill {
? $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL)->width
: $solid_infill_flow->width;
$pattern[$i] = $groups[$i][0]->is_external
? $layerm->config->solid_fill_pattern
? $layerm->config->external_fill_pattern
: 'rectilinear';
} else {
$is_solid[$i] = 0;
@ -179,7 +174,6 @@ sub make_fill {
}
my @fills = ();
my @fills_ordering_points = ();
SURFACE: foreach my $surface (@surfaces) {
next if $surface->surface_type == S_TYPE_INTERNALVOID;
my $filler = $layerm->config->fill_pattern;
@ -190,72 +184,103 @@ sub make_fill {
my $is_bridge = $layerm->id > 0 && $surface->is_bridge;
my $is_solid = $surface->is_solid;
# force 100% density and rectilinear fill for external surfaces
if ($surface->surface_type != S_TYPE_INTERNAL) {
if ($surface->is_solid) {
$density = 100;
$filler = $layerm->config->solid_fill_pattern;
if ($is_bridge) {
$filler = 'rectilinear';
} elsif ($surface->surface_type == S_TYPE_INTERNALSOLID) {
$filler = 'rectilinear';
if ($surface->is_external) {
$filler = $layerm->config->external_fill_pattern;
}
} else {
next SURFACE unless $density > 0;
}
# get filler object
my $f = $self->filler($filler);
# calculate the actual flow we'll be using for this infill
my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness;
my $flow = $layerm->region->flow(
$role,
$h,
$is_bridge,
$is_bridge || $f->use_bridge_flow,
$layerm->id == 0,
-1,
$layerm->object,
);
my $f = $self->filler($filler);
# calculate flow spacing for infill pattern generation
my $using_internal_flow = 0;
if (!$is_solid && !$is_bridge) {
# it's internal infill, so we can calculate a generic flow spacing
# for all layers, for avoiding the ugly effect of
# misaligned infill on first layer because of different extrusion width and
# layer height
my $internal_flow = $layerm->region->flow(
FLOW_ROLE_INFILL,
$layerm->object->config->layer_height, # TODO: handle infill_every_layers?
0, # no bridge
0, # no first layer
-1, # auto width
$layerm->object,
);
$f->spacing($internal_flow->spacing);
$using_internal_flow = 1;
} else {
$f->spacing($flow->spacing);
}
$f->layer_id($layerm->id);
$f->z($layerm->print_z);
$f->angle(deg2rad($layerm->config->fill_angle));
my ($params, @polylines) = $f->fill_surface(
$f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
my @polylines = $f->fill_surface(
$surface,
density => $density/100,
flow => $flow,
layer_height => $h,
);
next unless @polylines;
# calculate actual flow from spacing (which might have been adjusted by the infill
# pattern generator)
if ($using_internal_flow) {
# if we used the internal flow we're not doing a solid infill
# so we can safely ignore the slight variation that might have
# been applied to $f->flow_spacing
} else {
$flow = Slic3r::Flow->new_from_spacing(
spacing => $f->spacing,
nozzle_diameter => $flow->nozzle_diameter,
layer_height => $h,
bridge => $is_bridge || $f->use_bridge_flow,
);
}
my $mm3_per_mm = $flow->mm3_per_mm;
# save into layer
push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new;
$collection->no_sort($params->{no_sort});
{
my $role = $is_bridge ? EXTR_ROLE_BRIDGE
: $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL)
: EXTR_ROLE_FILL;
push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new;
$collection->no_sort($f->no_sort);
$collection->append(
map Slic3r::ExtrusionPath->new(
polyline => $_,
role => ($is_bridge
? EXTR_ROLE_BRIDGE
: $is_solid
? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL)
: EXTR_ROLE_FILL),
role => $role,
mm3_per_mm => $mm3_per_mm,
width => $flow->width,
height => ($is_bridge ? $flow->width : $h),
height => $flow->height,
), @polylines,
);
push @fills_ordering_points, $polylines[0]->first_point;
}
}
# add thin fill regions
foreach my $thin_fill (@{$layerm->thin_fills}) {
push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill);
push @fills_ordering_points, $thin_fill->first_point;
}
# organize infill paths using a nearest-neighbor search
@fills = @fills[ @{chained_path(\@fills_ordering_points)} ];
return @fills;
}

View File

@ -7,22 +7,17 @@ use POSIX qw(ceil fmod);
use Slic3r::Geometry qw(scale scaled_epsilon);
use Slic3r::Geometry::Clipper qw(intersection_pl);
# require bridge flow since most of this pattern hangs in air
sub use_bridge_flow { 1 }
sub fill_surface {
my ($self, $surface, %params) = @_;
# use bridge flow since most of this pattern hangs in air
my $flow = Slic3r::Flow->new(
width => $params{flow}->width,
height => $params{flow}->height,
nozzle_diameter => $params{flow}->nozzle_diameter,
bridge => 1,
);
my $expolygon = $surface->expolygon;
my $bb = $expolygon->bounding_box;
my $size = $bb->size;
my $distance = $flow->scaled_spacing / $params{density};
my $distance = scale($self->spacing) / $params{density};
# align bounding box to a multiple of our honeycomb grid
{
@ -71,7 +66,7 @@ sub fill_surface {
}
# TODO: return ExtrusionLoop objects to get better chained paths
return { flow => $flow}, @polylines;
return @polylines;
}

View File

@ -1,7 +0,0 @@
package Slic3r::Fill::ArchimedeanChords;
use Moo;
extends 'Slic3r::Fill::PlanePath';
use Math::PlanePath::ArchimedeanChords;
1;

View File

@ -4,6 +4,8 @@ use Moo;
has 'layer_id' => (is => 'rw');
has 'z' => (is => 'rw'); # in unscaled coordinates
has 'angle' => (is => 'rw'); # in radians, ccw, 0 = East
has 'spacing' => (is => 'rw'); # in unscaled coordinates
has 'loop_clipping' => (is => 'rw', default => sub { 0 }); # in scaled coordinates
has 'bounding_box' => (is => 'ro', required => 0); # Slic3r::Geometry::BoundingBox object
sub adjust_solid_spacing {
@ -17,6 +19,9 @@ sub adjust_solid_spacing {
return $params{distance} + $extra_space / ($number_of_lines - 1);
}
sub no_sort { 0 }
sub use_bridge_flow { 0 }
package Slic3r::Fill::WithDirection;
use Moo::Role;

View File

@ -15,22 +15,15 @@ sub fill_surface {
my $expolygon = $surface->expolygon;
my $bounding_box = $expolygon->bounding_box;
my $flow = $params{flow};
my $min_spacing = $flow->scaled_spacing;
my $min_spacing = scale($self->spacing);
my $distance = $min_spacing / $params{density};
my $flow_spacing = $flow->spacing;
if ($params{density} == 1 && !$params{dont_adjust}) {
$distance = $self->adjust_solid_spacing(
width => $bounding_box->size->[X],
distance => $distance,
);
$flow = Slic3r::Flow->new_from_spacing(
spacing => unscale($distance),
nozzle_diameter => $flow->nozzle_diameter,
layer_height => ($params{layer_height} or die "No layer_height supplied to fill_surface()"),
bridge => $flow->bridge,
);
$self->spacing(unscale $distance);
}
# compensate the overlap which is good for rectilinear but harmful for concentric
@ -54,12 +47,11 @@ sub fill_surface {
}
# clip the paths to prevent the extruder from getting exactly on the first point of the loop
my $clip_length = scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER;
$_->clip_end($clip_length) for @paths;
$_->clip_end($self->loop_clipping) for @paths;
@paths = grep $_->is_valid, @paths; # remove empty paths (too short, thus eaten by clipping)
# TODO: return ExtrusionLoop objects to get better chained paths
return { flow => $flow, no_sort => 1 }, @paths;
return @paths;
}
1;

View File

@ -1,18 +0,0 @@
package Slic3r::Fill::Flowsnake;
use Moo;
extends 'Slic3r::Fill::PlanePath';
use Math::PlanePath::Flowsnake;
use Slic3r::Geometry qw(X);
# Sorry, this fill is currently broken.
sub process_polyline {
my $self = shift;
my ($polyline, $bounding_box) = @_;
$_->[X] += $bounding_box->center->[X] for @$polyline;
}
1;

View File

@ -1,7 +0,0 @@
package Slic3r::Fill::HilbertCurve;
use Moo;
extends 'Slic3r::Fill::PlanePath';
use Math::PlanePath::HilbertCurve;
1;

View File

@ -18,11 +18,11 @@ sub fill_surface {
my $rotate_vector = $self->infill_direction($surface);
# cache hexagons math
my $cache_id = sprintf "d%s_s%s", $params{density}, $params{flow}->spacing;
my $cache_id = sprintf "d%s_s%s", $params{density}, $self->spacing;
my $m;
if (!($m = $self->cache->{$cache_id})) {
$m = $self->cache->{$cache_id} = {};
my $min_spacing = $params{flow}->scaled_spacing;
my $min_spacing = scale($self->spacing);
$m->{distance} = $min_spacing / $params{density};
$m->{hex_side} = $m->{distance} / (sqrt(3)/2);
$m->{hex_width} = $m->{distance} * 2; # $m->{hex_width} == $m->{hex_side} * sqrt(3);
@ -123,7 +123,7 @@ sub fill_surface {
)};
}
return { flow => $params{flow} }, @paths;
return @paths;
}
1;

View File

@ -1,8 +0,0 @@
package Slic3r::Fill::Line;
use Moo;
extends 'Slic3r::Fill::Rectilinear';
# Sorry for breaking OOP, but Line is implemented inside Rectilinear.
1;

View File

@ -1,9 +0,0 @@
package Slic3r::Fill::OctagramSpiral;
use Moo;
extends 'Slic3r::Fill::PlanePath';
use Math::PlanePath::OctagramSpiral;
sub multiplier () { sqrt(2) }
1;

View File

@ -7,16 +7,9 @@ with qw(Slic3r::Fill::WithDirection);
use Slic3r::Geometry qw(scale X1 Y1 X2 Y2);
use Slic3r::Geometry::Clipper qw(intersection_pl);
sub angles () { [0] }
sub multiplier () { 1 }
sub get_n {
my $self = shift;
my ($path, $bounding_box) = @_;
my ($n_lo, $n_hi) = $path->rect_to_n_range(@$bounding_box);
return ($n_lo .. $n_hi);
}
sub process_polyline {}
sub fill_surface {
@ -28,16 +21,37 @@ sub fill_surface {
my $rotate_vector = $self->infill_direction($surface);
$self->rotate_points($expolygon, $rotate_vector);
my $flow = $params{flow};
my $distance_between_lines = $flow->scaled_spacing / $params{density} * $self->multiplier;
my $bounding_box = $expolygon->bounding_box;
my $distance_between_lines = scale($self->spacing) / $params{density} * $self->multiplier;
# align infill across layers using the object's bounding box
my $bb_polygon = $self->bounding_box->polygon;
$self->rotate_points($bb_polygon, $rotate_vector);
my $bounding_box = $bb_polygon->bounding_box;
(ref $self) =~ /::([^:]+)$/;
my $path = "Math::PlanePath::$1"->new;
my @n = $self->get_n($path, [ map +($_ / $distance_between_lines), @{$bounding_box->min_point}, @{$bounding_box->max_point} ]);
my $translate = Slic3r::Point->new(0,0); # vector
if ($path->x_negative || $path->y_negative) {
# if the curve extends on both positive and negative coordinate space,
# center our expolygon around origin
$translate = $bounding_box->center->negative;
} else {
# if the curve does not extend in negative coordinate space,
# move expolygon entirely in positive coordinate space
$translate = $bounding_box->min_point->negative;
}
$expolygon->translate(@$translate);
$bounding_box->translate(@$translate);
my ($n_lo, $n_hi) = $path->rect_to_n_range(
map { $_ / $distance_between_lines }
@{$bounding_box->min_point},
@{$bounding_box->max_point},
);
my $polyline = Slic3r::Polyline->new(
map [ map {$_*$distance_between_lines} $path->n_to_xy($_) ], @n,
map [ map { $_ * $distance_between_lines } $path->n_to_xy($_) ], ($n_lo..$n_hi)
);
return {} if @$polyline <= 1;
@ -48,15 +62,57 @@ sub fill_surface {
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("fill.svg",
polygons => $expolygon,
polylines => [map $_->p, @paths],
no_arrows => 1,
polygons => \@$expolygon,
green_polygons => [ $bounding_box->polygon ],
polylines => [ $polyline ],
red_polylines => \@paths,
);
}
# paths must be rotated back
# paths must be repositioned and rotated back
$_->translate(@{$translate->negative}) for @paths;
$self->rotate_points_back(\@paths, $rotate_vector);
return { flow => $flow }, @paths;
return @paths;
}
package Slic3r::Fill::ArchimedeanChords;
use Moo;
extends 'Slic3r::Fill::PlanePath';
use Math::PlanePath::ArchimedeanChords;
package Slic3r::Fill::Flowsnake;
use Moo;
extends 'Slic3r::Fill::PlanePath';
use Math::PlanePath::Flowsnake;
use Slic3r::Geometry qw(X);
# Sorry, this fill is currently broken.
sub process_polyline {
my $self = shift;
my ($polyline, $bounding_box) = @_;
$_->[X] += $bounding_box->center->[X] for @$polyline;
}
package Slic3r::Fill::HilbertCurve;
use Moo;
extends 'Slic3r::Fill::PlanePath';
use Math::PlanePath::HilbertCurve;
package Slic3r::Fill::OctagramSpiral;
use Moo;
extends 'Slic3r::Fill::PlanePath';
use Math::PlanePath::OctagramSpiral;
sub multiplier () { sqrt(2) }
1;

View File

@ -4,10 +4,13 @@ use Moo;
extends 'Slic3r::Fill::Base';
with qw(Slic3r::Fill::WithDirection);
has 'cache' => (is => 'rw', default => sub {{}});
has '_min_spacing' => (is => 'rw');
has '_line_spacing' => (is => 'rw');
has '_diagonal_distance' => (is => 'rw');
has '_line_oscillation' => (is => 'rw');
use Slic3r::Geometry qw(A B X Y MIN scale unscale scaled_epsilon);
use Slic3r::Geometry::Clipper qw(intersection_pl offset);
use Slic3r::Geometry qw(scale unscale scaled_epsilon);
use Slic3r::Geometry::Clipper qw(intersection_pl);
sub fill_surface {
my $self = shift;
@ -18,47 +21,32 @@ sub fill_surface {
my $rotate_vector = $self->infill_direction($surface);
$self->rotate_points($expolygon, $rotate_vector);
my $flow = $params{flow} or die "No flow supplied to fill_surface()";
my $min_spacing = $flow->scaled_spacing;
my $line_spacing = $min_spacing / $params{density};
my $line_oscillation = $line_spacing - $min_spacing;
my $is_line_pattern = $self->isa('Slic3r::Fill::Line');
$self->_min_spacing(scale $self->spacing);
$self->_line_spacing($self->_min_spacing / $params{density});
$self->_diagonal_distance($self->_line_spacing * 2);
$self->_line_oscillation($self->_line_spacing - $self->_min_spacing); # only for Line infill
my $bounding_box = $expolygon->bounding_box;
# define flow spacing according to requested density
if ($params{density} == 1 && !$params{dont_adjust}) {
$line_spacing = $self->adjust_solid_spacing(
width => $bounding_box->size->[X],
distance => $line_spacing,
);
$flow = Slic3r::Flow->new_from_spacing(
spacing => unscale($line_spacing),
nozzle_diameter => $flow->nozzle_diameter,
layer_height => ($params{layer_height} or die "No layer_height supplied to fill_surface()"),
bridge => $flow->bridge,
);
$self->_line_spacing($self->adjust_solid_spacing(
width => $bounding_box->size->x,
distance => $self->_line_spacing,
));
$self->spacing(unscale $self->_line_spacing);
} else {
# extend bounding box so that our pattern will be aligned with other layers
$bounding_box->merge_point(Slic3r::Point->new(
$bounding_box->x_min - ($bounding_box->x_min % $line_spacing),
$bounding_box->y_min - ($bounding_box->y_min % $line_spacing),
$bounding_box->x_min - ($bounding_box->x_min % $self->_line_spacing),
$bounding_box->y_min - ($bounding_box->y_min % $self->_line_spacing),
));
}
# generate the basic pattern
my $i = 0;
my $x = $bounding_box->x_min;
my $x_max = $bounding_box->x_max + scaled_epsilon;
my @vertical_lines = ();
while ($x <= $x_max) {
my $vertical_line = [ [$x, $bounding_box->y_max], [$x, $bounding_box->y_min] ];
if ($is_line_pattern && $i % 2) {
$vertical_line->[A][X] += $line_oscillation;
$vertical_line->[B][X] -= $line_oscillation;
}
push @vertical_lines, Slic3r::Polyline->new(@$vertical_line);
$i++;
$x += $line_spacing;
for (my $x = $bounding_box->x_min; $x <= $x_max; $x += $self->_line_spacing) {
push @vertical_lines, $self->_line($#vertical_lines, $x, $bounding_box->y_min, $bounding_box->y_max);
}
# clip paths against a slightly larger expolygon, so that the first and last paths
@ -70,19 +58,10 @@ sub fill_surface {
# connect lines
unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections
my ($expolygon_off) = @{$expolygon->offset_ex($min_spacing/2)};
my ($expolygon_off) = @{$expolygon->offset_ex($self->_min_spacing/2)};
my $collection = Slic3r::Polyline::Collection->new(@polylines);
@polylines = ();
my $tolerance = 10 * scaled_epsilon;
my $diagonal_distance = $line_spacing * 2;
my $can_connect = $is_line_pattern
? sub {
($_[X] >= ($line_spacing - $line_oscillation) - $tolerance) && ($_[X] <= ($line_spacing + $line_oscillation) + $tolerance)
&& $_[Y] <= $diagonal_distance
}
: sub { $_[X] <= $diagonal_distance && $_[Y] <= $diagonal_distance };
foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) {
if (@polylines) {
my $first_point = $polyline->first_point;
@ -91,7 +70,7 @@ sub fill_surface {
# TODO: we should also check that both points are on a fill_boundary to avoid
# connecting paths on the boundaries of internal regions
if ($can_connect->(@distance) && $expolygon_off->contains_line(Slic3r::Line->new($last_point, $first_point))) {
if ($self->_can_connect(@distance) && $expolygon_off->contains_line(Slic3r::Line->new($last_point, $first_point))) {
$polylines[-1]->append_polyline($polyline);
next;
}
@ -105,7 +84,55 @@ sub fill_surface {
# paths must be rotated back
$self->rotate_points_back(\@polylines, $rotate_vector);
return { flow => $flow }, @polylines;
return @polylines;
}
sub _line {
my ($self, $i, $x, $y_min, $y_max) = @_;
return Slic3r::Polyline->new(
[$x, $y_min],
[$x, $y_max],
);
}
sub _can_connect {
my ($self, $dist_X, $dist_Y) = @_;
return $dist_X <= $self->_diagonal_distance
&& $dist_Y <= $self->_diagonal_distance;
}
package Slic3r::Fill::Line;
use Moo;
extends 'Slic3r::Fill::Rectilinear';
use Slic3r::Geometry qw(scaled_epsilon);
sub _line {
my ($self, $i, $x, $y_min, $y_max) = @_;
if ($i % 2) {
return Slic3r::Polyline->new(
[$x - $self->_line_oscillation, $y_min],
[$x + $self->_line_oscillation, $y_max],
);
} else {
return Slic3r::Polyline->new(
[$x, $y_min],
[$x, $y_max],
);
}
}
sub _can_connect {
my ($self, $dist_X, $dist_Y) = @_;
my $TOLERANCE = 10 * scaled_epsilon;
return ($dist_X >= ($self->_line_spacing - $self->_line_oscillation) - $TOLERANCE)
&& ($dist_X <= ($self->_line_spacing + $self->_line_oscillation) + $TOLERANCE)
&& $dist_Y <= $self->_diagonal_distance;
}
1;

View File

@ -96,6 +96,10 @@ sub change_layer {
$gcode .= $self->retract;
}
$gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer->id . ')');
# forget last wiping path as wiping after raising Z is pointless
$self->wipe->path(undef);
return $gcode;
}
@ -210,10 +214,10 @@ sub extrude_loop {
# create the destination point along the first segment and rotate it
# we make sure we don't exceed the segment length because we don't know
# the rotation of the second segment so we might cross the object boundary
my $first_segment = Slic3r::Line->new(@$last_path_polyline[0,1]);
my $first_segment = Slic3r::Line->new(@{$paths[0]->polyline}[0,1]);
my $distance = min(scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)), $first_segment->length);
my $point = $first_segment->point_at($distance);
$point->rotate($angle, $last_path_polyline->first_point);
$point->rotate($angle, $first_segment->a);
# generate the travel move
$gcode .= $self->travel_to($point, $paths[-1]->role, "move inwards before travel");
@ -256,10 +260,10 @@ sub _extrude_path {
$acceleration = $self->config->first_layer_acceleration;
} elsif ($self->config->perimeter_acceleration && $path->is_perimeter) {
$acceleration = $self->config->perimeter_acceleration;
} elsif ($self->config->infill_acceleration && $path->is_fill) {
$acceleration = $self->config->infill_acceleration;
} elsif ($self->config->bridge_acceleration && $path->is_bridge) {
$acceleration = $self->config->bridge_acceleration;
} elsif ($self->config->infill_acceleration && $path->is_infill) {
$acceleration = $self->config->infill_acceleration;
} else {
$acceleration = $self->config->default_acceleration;
}
@ -337,30 +341,34 @@ sub travel_to {
# Skip retraction at all in the following cases:
# - travel length is shorter than the configured threshold
# - user has enabled "Only retract when crossing perimeters" and the travel move is
# contained in a single island of the current layer *and* a single island in the
# upper layer (so that ooze will not be visible)
# contained in a single internal fill_surface (this includes the bottom layer when
# bottom_solid_layers == 0) or in a single internal slice (this would exclude such
# bottom layer but preserve perimeter-to-infill moves in all the other layers)
# - the path that will be extruded after this travel move is a support material
# extrusion and the travel move is contained in a single support material island
if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)
|| ($self->config->only_retract_when_crossing_perimeters
&& $self->config->fill_density > 0
&& $self->layer->slices->contains_line($travel)
&& (!$self->layer->has_upper_layer || $self->layer->upper_layer->slices->contains_line($travel)))
&& defined($self->layer)
&& ($self->layer->any_internal_region_slice_contains_line($travel)
|| $self->layer->any_internal_region_fill_surface_contains_line($travel)))
|| (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel))
) {
# Just perform a straight travel move without any retraction.
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment);
} elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->straight_once) {
} elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) {
# If avoid_crossing_perimeters is enabled and the disable_once flag is not set
# we need to plan a multi-segment travel move inside the configuration space.
$gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment);
} else {
# If avoid_crossing_perimeters is disabled or the straight_once flag is set,
# If avoid_crossing_perimeters is disabled or the disable_once flag is set,
# perform a straight move with a retraction.
$gcode .= $self->retract;
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || '');
}
# Re-allow avoid_crossing_perimeters for the next travel moves
$self->avoid_crossing_perimeters->straight_once(0);
$self->avoid_crossing_perimeters->disable_once(0);
return $gcode;
}
@ -557,8 +565,11 @@ use Moo;
has '_external_mp' => (is => 'rw');
has '_layer_mp' => (is => 'rw');
has 'new_object' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move
has 'straight_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move
has 'use_external_mp' => (is => 'rw', default => sub {0});
has 'use_external_mp_once' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move
has 'disable_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move
use Slic3r::Geometry qw(scale);
sub init_external_mp {
my ($self, $islands) = @_;
@ -575,11 +586,8 @@ sub travel_to {
my $gcode = "";
# If avoid_crossing_perimeters is enabled and the straight_once flag is not set
# we need to plan a multi-segment travel move inside the configuration space.
if ($self->new_object) {
# If we're moving to a new object we need to use the external configuration space.
$self->new_object(0);
if ($self->use_external_mp || $self->use_external_mp_once) {
$self->use_external_mp_once(0);
# represent $point in G-code coordinates
$point = $point->clone;
@ -606,13 +614,12 @@ sub _plan {
# if the path is not contained in a single island we need to retract
$gcode .= $gcodegen->retract
if !$gcodegen->config->only_retract_when_crossing_perimeters
|| !$gcodegen->layer->slices->contains_polyline($travel)
|| ($gcodegen->layer->has_upper_layer && !$gcodegen->layer->upper_layer->slices->contains_polyline($travel));
|| !$gcodegen->layer->any_internal_region_fill_surface_contains_polyline($travel);
# append the actual path and return
# use G1 because we rely on paths being straight (G0 may make round paths)
$gcode .= join '',
map $gcodegen->writer->travel_to_xy($self->point_to_gcode($_->b), $comment),
map $gcodegen->writer->travel_to_xy($gcodegen->point_to_gcode($_->b), $comment),
@{$travel->lines};
return $gcode;

View File

@ -1,251 +0,0 @@
package Slic3r::GCode::Layer;
use Moo;
use List::Util qw(first);
use Slic3r::Geometry qw(X Y unscale);
has 'print' => (is => 'ro', required => 1, handles => [qw(config)]);
has 'gcodegen' => (is => 'ro', required => 1);
has 'origin' => (is => 'ro', default => sub { Slic3r::Pointf->new(0,0) });
has 'spiralvase' => (is => 'lazy');
has 'vibration_limit' => (is => 'lazy');
has 'arc_fitting' => (is => 'lazy');
has 'pressure_regulator' => (is => 'lazy');
has 'skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1
has 'brim_done' => (is => 'rw');
has 'second_layer_things_done' => (is => 'rw');
has '_last_obj_copy' => (is => 'rw');
sub _build_spiralvase {
my $self = shift;
return $self->print->config->spiral_vase
? Slic3r::GCode::SpiralVase->new(config => $self->print->config)
: undef;
}
sub _build_vibration_limit {
my $self = shift;
return $self->print->config->vibration_limit
? Slic3r::GCode::VibrationLimit->new(config => $self->print->config)
: undef;
}
sub _build_arc_fitting {
my $self = shift;
return $self->print->config->gcode_arcs
? Slic3r::GCode::ArcFitting->new(config => $self->print->config)
: undef;
}
sub _build_pressure_regulator {
my $self = shift;
return $self->print->config->pressure_advance > 0
? Slic3r::GCode::PressureRegulator->new(config => $self->print->config)
: undef;
}
sub process_layer {
my $self = shift;
my ($layer, $object_copies) = @_;
my $gcode = "";
my $object = $layer->object;
$self->gcodegen->config->apply_object_config($object->config);
# check whether we're going to apply spiralvase logic
if (defined $self->spiralvase) {
$self->spiralvase->enable(
($layer->id > 0 || $self->print->config->brim_width == 0)
&& ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1)
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
&& !defined(first { @{$_->perimeters} > 1 } @{$layer->regions})
&& !defined(first { @{$_->fills} > 0 } @{$layer->regions})
);
}
# if we're going to apply spiralvase to this layer, disable loop clipping
$self->gcodegen->enable_loop_clipping(!defined $self->spiralvase || !$self->spiralvase->enable);
if (!$self->second_layer_things_done && $layer->id == 1) {
for my $extruder (@{$self->gcodegen->writer->extruders}) {
my $temperature = $self->config->get_at('temperature', $extruder->id);
$gcode .= $self->gcodegen->writer->set_temperature($temperature, 0, $extruder->id)
if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id);
}
$gcode .= $self->gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature)
if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature;
$self->second_layer_things_done(1);
}
# set new layer - this will change Z and force a retraction if retract_layer_change is enabled
$gcode .= $self->gcodegen->change_layer($layer);
$gcode .= $self->gcodegen->placeholder_parser->process($self->print->config->layer_gcode, {
layer_num => $layer->id,
}) . "\n" if $self->print->config->layer_gcode;
# extrude skirt
if (((values %{$self->skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1)
&& !$self->skirt_done->{$layer->print_z}) {
$self->gcodegen->set_origin($self->origin);
my @extruder_ids = map { $_->id } @{$self->gcodegen->writer->extruders};
$gcode .= $self->gcodegen->set_extruder($extruder_ids[0]);
# skip skirt if we have a large brim
if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) {
# distribute skirt loops across all extruders
my @skirt_loops = @{$self->print->skirt};
for my $i (0 .. $#skirt_loops) {
# when printing layers > 0 ignore 'min_skirt_length' and
# just use the 'skirts' setting; also just use the current extruder
last if ($layer->id > 0) && ($i >= $self->print->config->skirts);
my $extruder_id = $extruder_ids[($i/@extruder_ids) % @extruder_ids];
$gcode .= $self->gcodegen->set_extruder($extruder_id)
if $layer->id == 0;
$gcode .= $self->gcodegen->extrude_loop($skirt_loops[$i], 'skirt', $object->config->support_material_speed);
}
}
$self->skirt_done->{$layer->print_z} = 1;
$self->gcodegen->avoid_crossing_perimeters->straight_once(1);
}
# extrude brim
if (!$self->brim_done) {
$gcode .= $self->gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1);
$self->gcodegen->set_origin($self->origin);
$gcode .= $self->gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed)
for @{$self->print->brim};
$self->brim_done(1);
$self->gcodegen->avoid_crossing_perimeters->straight_once(1);
}
for my $copy (@$object_copies) {
$self->gcodegen->avoid_crossing_perimeters->new_object(1) if ($self->_last_obj_copy // '') ne "$copy";
$self->_last_obj_copy("$copy");
$self->gcodegen->set_origin(Slic3r::Pointf->new(map $self->origin->[$_] + unscale $copy->[$_], X,Y));
# extrude support material before other things because it might use a lower Z
# and also because we avoid travelling on other things when printing it
if ($layer->isa('Slic3r::Layer::Support')) {
if ($layer->support_interface_fills->count > 0) {
$gcode .= $self->gcodegen->set_extruder($object->config->support_material_interface_extruder-1);
$gcode .= $self->gcodegen->extrude_path($_, 'support material interface', $object->config->get_abs_value('support_material_interface_speed'))
for @{$layer->support_interface_fills->chained_path_from($self->gcodegen->last_pos, 0)};
}
if ($layer->support_fills->count > 0) {
$gcode .= $self->gcodegen->set_extruder($object->config->support_material_extruder-1);
$gcode .= $self->gcodegen->extrude_path($_, 'support material', $object->config->get_abs_value('support_material_speed'))
for @{$layer->support_fills->chained_path_from($self->gcodegen->last_pos, 0)};
}
}
# tweak region ordering to save toolchanges
my @region_ids = 0 .. ($self->print->region_count-1);
if ($self->gcodegen->writer->multiple_extruders) {
my $last_extruder_id = $self->gcodegen->writer->extruder->id;
my $best_region_id = first { $self->print->regions->[$_]->config->perimeter_extruder-1 == $last_extruder_id } @region_ids;
@region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id;
}
foreach my $region_id (@region_ids) {
my $layerm = $layer->regions->[$region_id] or next;
my $region = $self->print->regions->[$region_id];
$self->gcodegen->config->apply_region_config($region->config);
# group extrusions by island
my @perimeters_by_island = map [], 0..$#{$layer->slices}; # slice idx => @perimeters
my @infill_by_island = map [], 0..$#{$layer->slices}; # slice idx => @fills
# NOTE: we assume $layer->slices was already ordered with chained_path()!
PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) {
for my $i (0 .. $#{$layer->slices}-1) {
if ($layer->slices->[$i]->contour->contains_point($perimeter->first_point)) {
push @{ $perimeters_by_island[$i] }, $perimeter;
next PERIMETER;
}
}
push @{ $perimeters_by_island[-1] }, $perimeter; # optimization
}
FILL: foreach my $fill (@{$layerm->fills}) {
for my $i (0 .. $#{$layer->slices}-1) {
if ($layer->slices->[$i]->contour->contains_point($fill->first_point)) {
push @{ $infill_by_island[$i] }, $fill;
next FILL;
}
}
push @{ $infill_by_island[-1] }, $fill; # optimization
}
for my $i (0 .. $#{$layer->slices}) {
# give priority to infill if we were already using its extruder and it wouldn't
# be good for perimeters
if ($self->print->config->infill_first
|| ($self->gcodegen->writer->multiple_extruders && $region->config->infill_extruder-1 == $self->gcodegen->writer->extruder->id && $region->config->infill_extruder != $region->config->perimeter_extruder)) {
$gcode .= $self->_extrude_infill($infill_by_island[$i], $region);
$gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region);
} else {
$gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region);
$gcode .= $self->_extrude_infill($infill_by_island[$i], $region);
}
}
}
}
# apply spiral vase post-processing if this layer contains suitable geometry
# (we must feed all the G-code into the post-processor, including the first
# bottom non-spiral layers otherwise it will mess with positions)
$gcode = $self->spiralvase->process_layer($gcode)
if defined $self->spiralvase;
# apply vibration limit if enabled
$gcode = $self->vibration_limit->process($gcode)
if $self->print->config->vibration_limit != 0;
# apply pressure regulation if enabled
$gcode = $self->pressure_regulator->process($gcode)
if $self->print->config->pressure_advance > 0;
# apply arc fitting if enabled
$gcode = $self->arc_fitting->process($gcode)
if $self->print->config->gcode_arcs;
return $gcode;
}
sub _extrude_perimeters {
my $self = shift;
my ($island_perimeters, $region) = @_;
return "" if !@$island_perimeters;
my $gcode = "";
$gcode .= $self->gcodegen->set_extruder($region->config->perimeter_extruder-1);
$gcode .= $self->gcodegen->extrude($_, 'perimeter') for @$island_perimeters;
return $gcode;
}
sub _extrude_infill {
my $self = shift;
my ($island_fills, $region) = @_;
return "" if !@$island_fills;
my $gcode = "";
$gcode .= $self->gcodegen->set_extruder($region->config->infill_extruder-1);
for my $fill (@$island_fills) {
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
$gcode .= $self->gcodegen->extrude($_, 'fill')
for @{$fill->chained_path_from($self->gcodegen->last_pos, 0)};
} else {
$gcode .= $self->gcodegen->extrude($fill, 'fill') ;
}
}
return $gcode;
}
1;

View File

@ -50,7 +50,7 @@ sub parse {
if ($command =~ /^G[01]$/) {
foreach my $axis (@AXES) {
if (exists $args{$axis}) {
$self->$axis = 0 if $axis eq 'E' && $self->config->use_relative_e_distances;
$self->$axis(0) if $axis eq 'E' && $self->config->use_relative_e_distances;
$info{"dist_$axis"} = $args{$axis} - $self->$axis;
$info{"new_$axis"} = $args{$axis};
} else {
@ -58,7 +58,7 @@ sub parse {
$info{"new_$axis"} = $self->$axis;
}
}
$info{dist_XY} = Slic3r::Geometry::unscale(Slic3r::Line->new_scale([0,0], [@info{qw(dist_X dist_Y)}])->length);
$info{dist_XY} = sqrt(($info{dist_X}**2) + ($info{dist_Y}**2));
if (exists $args{E}) {
if ($info{dist_E} > 0) {
$info{extruding} = 1;

View File

@ -3,7 +3,6 @@ use Moo;
extends 'Slic3r::GCode::Reader';
has 'config' => (is => 'ro', required => 1);
has '_min_time' => (is => 'lazy');
has '_last_dir' => (is => 'ro', default => sub { [0,0] });
has '_dir_time' => (is => 'ro', default => sub { [0,0] });
@ -11,7 +10,6 @@ has '_dir_time' => (is => 'ro', default => sub { [0,0] });
# inspired by http://hydraraptor.blogspot.it/2010/12/frequency-limit.html
use List::Util qw(max);
use Slic3r::Geometry qw(X Y);
sub _build__min_time {
my ($self) = @_;
@ -27,15 +25,16 @@ sub process {
my ($reader, $cmd, $args, $info) = @_;
if ($cmd eq 'G1' && $info->{dist_XY} > 0) {
my $point = Slic3r::Point->new($args->{X} // $reader->X, $args->{Y} // $reader->Y);
my $point = Slic3r::Pointf->new($args->{X} // $reader->X, $args->{Y} // $reader->Y);
my @dir = (
($point->x <=> $reader->X),
($point->y <=> $reader->Y), #$
);
my $time = $info->{dist_XY} / ($args->{F} // $reader->F); # in minutes
if ($time > 0) {
my @pause = ();
foreach my $axis (X,Y) {
foreach my $axis (0..$#dir) {
if ($dir[$axis] != 0 && $self->_last_dir->[$axis] != $dir[$axis]) {
if ($self->_last_dir->[$axis] != 0) {
# this axis is changing direction: check whether we need to pause

View File

@ -59,7 +59,7 @@ our $Settings = {
},
};
our $have_button_icons = &Wx::wxVERSION_STRING =~ / 2\.9\.[1-9]/;
our $have_button_icons = &Wx::wxVERSION_STRING =~ / (?:2\.9\.[1-9]|3\.)/;
our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$small_font->SetPointSize(11) if !&Wx::wxMSW;
our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);

View File

@ -82,7 +82,6 @@ sub new {
tooltip => 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.',
default => [0,0],
));
$optgroup->on_change->($_) for qw(rect_size rect_origin); # set defaults
}
{
my $optgroup = $self->_init_shape_options_page('Circular');
@ -94,7 +93,6 @@ sub new {
sidetext => 'mm',
default => 200,
));
$optgroup->on_change->($_) for qw(diameter); # set defaults
}
{
my $optgroup = $self->_init_shape_options_page('Custom');
@ -162,6 +160,7 @@ sub _set_shape {
my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR];
$optgroup->set_value('rect_size', [ $x_max-$x_min, $y_max-$y_min ]);
$optgroup->set_value('rect_origin', $origin);
$self->_update_shape;
return;
}
}
@ -172,16 +171,18 @@ sub _set_shape {
my $center = $polygon->bounding_box->center;
my @vertex_distances = map $center->distance_to($_), @$polygon;
my $avg_dist = sum(@vertex_distances)/@vertex_distances;
if (!defined first { abs($_ - $avg_dist) > scaled_epsilon } @vertex_distances) {
if (!defined first { abs($_ - $avg_dist) > 10*scaled_epsilon } @vertex_distances) {
# all vertices are equidistant to center
$self->{shape_options_book}->SetSelection(SHAPE_CIRCULAR);
my $optgroup = $self->{optgroups}[SHAPE_CIRCULAR];
$optgroup->set_value('diameter', sprintf("%.0f", unscale($avg_dist*2)));
$self->_update_shape;
return;
}
}
$self->{shape_options_book}->SetSelection(SHAPE_CUSTOM);
$self->_update_shape;
}
sub _update_shape {
@ -189,13 +190,14 @@ sub _update_shape {
my $page_idx = $self->{shape_options_book}->GetSelection;
if ($page_idx == SHAPE_RECTANGULAR) {
return if grep !defined($self->{"_$_"}), qw(rect_size rect_origin); # not loaded yet
my ($x, $y) = @{$self->{_rect_size}};
my $rect_size = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_size');
my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin');
my ($x, $y) = @$rect_size;
return if !$x || !$y; # empty strings
my ($x0, $y0) = (0,0);
my ($x1, $y1) = ($x,$y);
{
my ($dx, $dy) = @{$self->{_rect_origin}};
my ($dx, $dy) = @$rect_origin;
return if $dx eq '' || $dy eq ''; # empty strings
$x0 -= $dx;
$x1 -= $dx;
@ -209,9 +211,9 @@ sub _update_shape {
[$x0,$y1],
];
} elsif ($page_idx == SHAPE_CIRCULAR) {
return if grep !defined($self->{"_$_"}), qw(diameter); # not loaded yet
return if !$self->{_diameter};
my $r = $self->{_diameter}/2;
my $diameter = $self->{optgroups}[SHAPE_CIRCULAR]->get_value('diameter');
return if !$diameter;
my $r = $diameter/2;
my $twopi = 2*PI;
my $edges = 60;
my $polygon = Slic3r::Polygon->new_scale(
@ -366,7 +368,7 @@ sub _init_shape_options_page {
label_width => 100,
on_change => sub {
my ($opt_id) = @_;
$self->{"_$opt_id"} = $optgroup->get_value($opt_id);
#$self->{"_$opt_id"} = $optgroup->get_value($opt_id);
$self->_update_shape;
},
);

View File

@ -38,16 +38,6 @@ sub new {
$self->{loaded} = 1;
# declare events
EVT_CLOSE($self, sub {
my (undef, $event) = @_;
if ($event->CanVeto && !$self->check_unsaved_changes) {
$event->Veto;
return;
}
$event->Skip;
});
# initialize layout
{
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
@ -56,11 +46,36 @@ sub new {
$self->SetSizer($sizer);
$self->Fit;
$self->SetMinSize([760, 490]);
if (defined $Slic3r::GUI::Settings->{_}{main_frame_size}) {
$self->SetSize([ split ',', $Slic3r::GUI::Settings->{_}{main_frame_size}, 2 ]);
$self->Move([ split ',', $Slic3r::GUI::Settings->{_}{main_frame_pos}, 2 ]);
$self->Maximize(1) if $Slic3r::GUI::Settings->{_}{main_frame_maximized};
} else {
$self->SetSize($self->GetMinSize);
}
$self->Show;
$self->Layout;
}
# declare events
EVT_CLOSE($self, sub {
my (undef, $event) = @_;
if ($event->CanVeto && !$self->check_unsaved_changes) {
$event->Veto;
return;
}
# save window size
$Slic3r::GUI::Settings->{_}{main_frame_pos} = join ',', $self->GetScreenPositionXY;
$Slic3r::GUI::Settings->{_}{main_frame_size} = join ',', $self->GetSizeWH;
$Slic3r::GUI::Settings->{_}{main_frame_maximized} = $self->IsMaximized;
wxTheApp->save_settings;
# propagate event
$event->Skip;
});
return $self;
}
@ -192,10 +207,6 @@ sub _init_menubar {
$self->_append_menu_item($self->{plater_menu}, "Export AMF...", 'Export current plate as AMF', sub {
$plater->export_amf;
});
$self->{plater_menu}->AppendSeparator();
$self->_append_menu_item($self->{plater_menu}, "Toolpaths preview…", 'Open a viewer with toolpaths preview', sub {
$plater->toolpaths_preview;
});
$self->{object_menu} = $self->{plater}->object_menu;
$self->on_plater_selection_changed(0);
@ -308,7 +319,14 @@ sub quick_slice {
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
wxTheApp->save_settings;
my $print_center;
{
my $bed_shape = Slic3r::Polygon->new_scale(@{$config->bed_shape});
$print_center = Slic3r::Pointf->new_unscale(@{$bed_shape->bounding_box->center});
}
my $sprint = Slic3r::Print::Simple->new(
print_center => $print_center,
status_cb => sub {
my ($percent, $message) = @_;
return if &Wx::wxVERSION_STRING !~ / 2\.(8\.|9\.[2-9])/;
@ -417,7 +435,7 @@ sub extra_variables {
my %extra_variables = ();
if ($self->{mode} eq 'expert') {
$extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->current_preset->{name}
$extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->get_current_preset->name
for qw(print filament printer);
}
return { %extra_variables };

View File

@ -231,7 +231,7 @@ use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use List::Util qw(first);
use Wx qw(:misc :combobox);
use Wx qw(wxTheApp :misc :combobox);
use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
sub BUILD {
@ -258,8 +258,18 @@ sub BUILD {
$label = $value;
}
# The MSW implementation of wxComboBox will leave the field blank if we call
# SetValue() in the EVT_COMBOBOX event handler, so we postpone the call.
wxTheApp->CallAfter(sub {
my $dce = $self->disable_change_event;
$self->disable_change_event(1);
# ChangeValue() is not exported in wxPerl
$field->SetValue($label);
$self->disable_change_event($dce);
});
$self->disable_change_event($disable_change_event);
$self->_on_change($self->option->opt_id);
});

View File

@ -26,7 +26,7 @@ use constant TB_45CW => &Wx::NewId;
use constant TB_45CCW => &Wx::NewId;
use constant TB_SCALE => &Wx::NewId;
use constant TB_SPLIT => &Wx::NewId;
use constant TB_VIEW => &Wx::NewId;
use constant TB_CUT => &Wx::NewId;
use constant TB_SETTINGS => &Wx::NewId;
use constant CONFIG_TIMER_ID => &Wx::NewId;
@ -68,27 +68,25 @@ sub new {
# Initialize preview notebook
$self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM);
# Initialize 2D preview canvas
$self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config});
$self->{preview_notebook}->AddPage($self->{canvas}, '2D');
$self->{canvas}->on_select_object(sub {
# Initialize handlers for canvases
my $on_select_object = sub {
my ($obj_idx) = @_;
$self->select_object($obj_idx);
});
$self->{canvas}->on_double_click(sub {
$self->object_cut_dialog if $self->selected_object;
});
$self->{canvas}->on_right_click(sub {
my ($click_pos) = @_;
};
my $on_double_click = sub {
$self->object_settings_dialog if $self->selected_object;
};
my $on_right_click = sub {
my ($canvas, $click_pos) = @_;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $menu = $self->object_menu;
$self->{canvas}->PopupMenu($menu, $click_pos);
$canvas->PopupMenu($menu, $click_pos);
$menu->Destroy;
});
$self->{canvas}->on_instance_moved(sub {
};
my $on_instance_moved = sub {
my ($obj_idx, $instance_idx) = @_;
$self->update;
@ -100,12 +98,27 @@ sub new {
} else {
$self->resume_background_process;
}
});
};
# Initialize 3D preview canvas
# Initialize 2D preview canvas
$self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config});
$self->{preview_notebook}->AddPage($self->{canvas}, '2D');
$self->{canvas}->on_select_object($on_select_object);
$self->{canvas}->on_double_click($on_double_click);
$self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); });
$self->{canvas}->on_instance_moved($on_instance_moved);
# Initialize 3D preview and toolpaths preview
if ($Slic3r::GUI::have_OpenGL) {
$self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config});
$self->{preview_notebook}->AddPage($self->{canvas3D}, '3D');
$self->{canvas3D}->set_on_select_object($on_select_object);
$self->{canvas3D}->set_on_double_click($on_double_click);
$self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
$self->{canvas3D}->set_on_instance_moved($on_instance_moved);
$self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print});
$self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Preview');
}
# toolbar for object manipulation
@ -124,7 +137,7 @@ sub new {
$self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new("$Slic3r::var/arrow_out.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_VIEW, "Cut…", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG), '');
} else {
@ -137,20 +150,20 @@ sub new {
decrease => "",
rotate45ccw => "",
rotate45cw => "",
rotate => "Rotate…",
changescale => "Scale…",
split => "Split",
view => "View/Cut…",
cut => "Cut…",
settings => "Settings…",
);
$self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw rotate changescale split view settings)) {
for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) {
$self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
$self->{btoolbar}->Add($self->{"btn_$_"});
}
}
$self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, [250,-1], wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS);
$self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize,
wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS );
$self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145);
$self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45);
$self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER);
@ -169,11 +182,8 @@ sub new {
# right pane buttons
$self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_toolpaths_preview} = Wx::Button->new($self, -1, "Toolpaths preview…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font);
$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font);
$self->{btn_toolpaths_preview}->SetFont($Slic3r::GUI::small_font);
$self->{btn_toolpaths_preview}->Disable;
#$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font);
#$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
my %icons = qw(
@ -183,7 +193,6 @@ sub new {
arrange bricks.png
export_gcode cog_go.png
export_stl brick_go.png
toolpaths_preview joystick.png
increase add.png
decrease delete.png
@ -191,7 +200,7 @@ sub new {
rotate45ccw arrow_rotate_anticlockwise.png
changescale arrow_out.png
split shape_ungroup.png
view package.png
cut package.png
settings cog.png
);
for (grep $self->{"btn_$_"}, keys %icons) {
@ -204,8 +213,7 @@ sub new {
$self->export_gcode;
Slic3r::thread_cleanup();
});
EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
EVT_BUTTON($self, $self->{btn_toolpaths_preview}, \&toolpaths_preview);
#EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
if ($self->{htoolbar}) {
EVT_TOOL($self, TB_ADD, sub { $self->add; });
@ -218,7 +226,7 @@ sub new {
EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) });
EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); });
EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; });
EVT_TOOL($self, TB_VIEW, sub { $_[0]->object_cut_dialog });
EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
} else {
EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; });
@ -231,12 +239,12 @@ sub new {
EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45) });
EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); });
EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; });
EVT_BUTTON($self, $self->{btn_view}, sub { $_[0]->object_cut_dialog });
EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog });
EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog });
}
$_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self))
for $self, $self->{canvas}, $self->{list};
for grep defined($_), $self, $self->{canvas}, $self->{canvas3D}, $self->{list};
EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub {
my ($self, $event) = @_;
@ -274,32 +282,33 @@ sub new {
});
$self->{canvas}->update_bed_size;
if ($self->{canvas3D}) {
$self->{canvas3D}->update_bed_size;
$self->{canvas3D}->zoom_to_bed;
}
$self->update;
{
my $presets;
if ($self->GetFrame->{mode} eq 'expert') {
$presets = Wx::BoxSizer->new(wxVERTICAL);
$presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2);
$presets->AddGrowableCol(1, 1);
$presets->SetFlexibleDirection(wxHORIZONTAL);
my %group_labels = (
print => 'Print settings',
filament => 'Filament',
printer => 'Printer',
);
$self->{preset_choosers} = {};
$self->{preset_choosers_sizers} = {};
for my $group (qw(print filament printer)) {
my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
$text->SetFont($Slic3r::GUI::small_font);
my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [140, -1], []);
my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []);
$choice->SetFont($Slic3r::GUI::small_font);
$self->{preset_choosers}{$group} = [$choice];
EVT_CHOICE($choice, $choice, sub { $self->_on_select_preset($group, @_) });
$self->{preset_choosers_sizers}{$group} = Wx::BoxSizer->new(wxVERTICAL);
$self->{preset_choosers_sizers}{$group}->Add($choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
$presets->Add($text, 0, wxALIGN_LEFT | wxRIGHT, 4);
$presets->Add($self->{preset_choosers_sizers}{$group}, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, 8);
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 8);
}
}
@ -343,23 +352,20 @@ sub new {
}
}
my $right_buttons_sizer = Wx::BoxSizer->new(wxVERTICAL);
$right_buttons_sizer->Add($presets, 0, wxEXPAND, 0) if defined $presets;
$right_buttons_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxTOP, 8);
$right_buttons_sizer->Add($self->{btn_toolpaths_preview}, 0, wxEXPAND | wxTOP, 2);
$right_buttons_sizer->Add($self->{btn_export_stl}, 0, wxEXPAND | wxTOP, 2);
my $right_top_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$right_top_sizer->Add($self->{list}, 1, wxEXPAND | wxLEFT, 5);
$right_top_sizer->Add($right_buttons_sizer, 0, wxEXPAND | wxALL, 10);
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$buttons_sizer->AddStretchSpacer(1);
$buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0);
$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0);
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
$right_sizer->Add($right_top_sizer, 1, wxEXPAND | wxBOTTOM, 10);
$right_sizer->Add($object_info_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 5);
$right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets;
$right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5);
$right_sizer->Add($self->{list}, 1, wxEXPAND, 5);
$right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1);
$hsizer->Add($right_sizer, 0, wxEXPAND | wxBOTTOM, 0);
$hsizer->Add($right_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 3);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar};
@ -473,7 +479,7 @@ sub load_model_objects {
$need_arrange = 1;
# add a default instance and center object around origin
$o->center_around_origin;
$o->center_around_origin; # also aligns object to Z = 0
$o->add_instance(offset => $bed_centerf);
}
@ -507,6 +513,11 @@ sub objects_loaded {
}
$self->arrange unless $params{no_arrange};
$self->update;
# zoom to objects
$self->{canvas3D}->zoom_to_volumes
if $self->{canvas3D};
$self->{list}->Update;
$self->{list}->Select($obj_idxs->[-1], 1);
$self->object_list_changed;
@ -528,6 +539,9 @@ sub remove {
$self->stop_background_process;
# Prevent toolpaths preview from rendering while we modify the Print object
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
# if no object index is supplied, remove the selected one
if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object;
@ -550,6 +564,9 @@ sub reset {
$self->stop_background_process;
# Prevent toolpaths preview from rendering while we modify the Print object
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
@{$self->{objects}} = ();
$self->{model}->clear_objects;
$self->{print}->clear_objects;
@ -653,7 +670,6 @@ sub rotate {
$self->selection_changed; # refresh info (size etc.)
$self->update;
$self->{canvas}->Refresh;
}
sub flip {
@ -682,7 +698,7 @@ sub flip {
$self->selection_changed; # refresh info (size etc.)
$self->update;
$self->{canvas}->Refresh;
$self->refresh_canvases;
}
sub changescale {
@ -737,7 +753,7 @@ sub changescale {
$self->selection_changed(1); # refresh info (size, volume etc.)
$self->update;
$self->{canvas}->Refresh;
$self->refresh_canvases;
}
sub arrange {
@ -763,14 +779,17 @@ sub arrange {
} else {
$self->resume_background_process;
}
$self->{canvas}->Refresh;
$self->refresh_canvases;
}
sub split_object {
my $self = shift;
my ($obj_idx, $current_object) = $self->selected_object;
my $current_model_object = $self->{model}->objects->[$obj_idx];
# we clone model object because split_object() adds the split volumes
# into the same model object, thus causing duplicated when we call load_model_objects()
my $current_model_object = $self->{model}->clone->objects->[$obj_idx];
if (@{$current_model_object->volumes} > 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object can't be split because it contains more than one volume/material.");
@ -781,7 +800,7 @@ sub split_object {
my @model_objects = @{$current_model_object->split_object};
if (@model_objects == 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it already contains a single part.");
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains only one part.");
return;
}
@ -809,7 +828,7 @@ sub schedule_background_process {
if (defined $self->{apply_config_timer}) {
$self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot
$self->{btn_toolpaths_preview}->Disable;
$self->{toolpaths2D}->reload_print;
}
}
@ -853,7 +872,10 @@ sub start_background_process {
$self->GetFrame->config->validate;
$self->{print}->validate;
};
return if $@;
if ($@) {
$self->statusbar->SetStatusText($@);
return;
}
# apply extra variables
{
@ -869,9 +891,9 @@ sub start_background_process {
};
if ($@) {
Slic3r::debugf "Discarding background process error: $@\n";
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, 0));
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, $@));
} else {
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, 1));
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, undef));
}
Slic3r::thread_cleanup();
});
@ -885,7 +907,7 @@ sub stop_background_process {
$self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy;
$self->statusbar->SetStatusText("");
$self->{btn_toolpaths_preview}->Disable;
$self->{toolpaths2D}->reload_print;
if ($self->{process_thread}) {
Slic3r::debugf "Killing background process.\n";
@ -907,7 +929,7 @@ sub pause_background_process {
my ($self) = @_;
if ($self->{process_thread} || $self->{export_thread}) {
Slic3r::pause_threads();
Slic3r::pause_all_threads();
} elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) {
$self->{apply_config_timer}->Stop;
}
@ -917,7 +939,7 @@ sub resume_background_process {
my ($self) = @_;
if ($self->{process_thread} || $self->{export_thread}) {
Slic3r::resume_threads();
Slic3r::resume_all_threads();
}
}
@ -993,18 +1015,18 @@ sub export_gcode {
# This gets called only if we have threads.
sub on_process_completed {
my ($self, $result) = @_;
my ($self, $error) = @_;
$self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy;
$self->statusbar->SetStatusText("");
$self->statusbar->SetStatusText($error // "");
Slic3r::debugf "Background processing completed.\n";
$self->{process_thread}->detach if $self->{process_thread};
$self->{process_thread} = undef;
return if !$result;
$self->{btn_toolpaths_preview}->Enable;
return if $error;
$self->{toolpaths2D}->reload_print;
# if we have an export filename, start a new thread for exporting G-code
if ($self->{export_gcode_output_file}) {
@ -1138,7 +1160,7 @@ sub on_thumbnail_made {
$self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx);
$self->update;
$self->{canvas}->Refresh;
$self->refresh_canvases;
}
# this method gets called whenever print center is changed or the objects' bounding box changes
@ -1150,8 +1172,7 @@ sub update {
$self->{model}->center_instances_around_point($self->bed_centerf);
}
$self->{canvas}->Refresh;
$self->{canvas3D}->update if $self->{canvas3D};
$self->refresh_canvases;
}
sub on_extruders_change {
@ -1162,13 +1183,15 @@ sub on_extruders_change {
my @presets = $choices->[0]->GetStrings;
push @$choices, Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [@presets]);
$choices->[-1]->SetFont($Slic3r::GUI::small_font);
$self->{preset_choosers_sizers}{filament}->Add($choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
$self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0);
$self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
EVT_CHOICE($choices->[-1], $choices->[-1], sub { $self->_on_select_preset('filament', @_) });
my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
$choices->[-1]->SetSelection($i || 0);
}
while (@$choices > $num_extruders) {
$self->{preset_choosers_sizers}{filament}->Remove(-1);
$self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label
$self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice
$choices->[-1]->Destroy;
pop @$choices;
}
@ -1183,6 +1206,7 @@ sub on_config_change {
$self->{config}->set($opt_key, $config->get($opt_key));
if ($opt_key eq 'bed_shape') {
$self->{canvas}->update_bed_size;
$self->{canvas3D}->update_bed_size if $self->{canvas3D};
$self->update;
}
}
@ -1199,7 +1223,7 @@ sub list_item_deselected {
if ($self->{list}->GetFirstSelected == -1) {
$self->select_object(undef);
$self->{canvas}->Refresh;
$self->refresh_canvases;
}
}
@ -1209,7 +1233,7 @@ sub list_item_selected {
my $obj_idx = $event->GetIndex;
$self->select_object($obj_idx);
$self->{canvas}->Refresh;
$self->refresh_canvases;
}
sub list_item_activated {
@ -1268,6 +1292,8 @@ sub object_settings_dialog {
# update thumbnail since parts may have changed
if ($dlg->PartsChanged) {
# recenter and re-align to Z = 0
$model_object->center_around_origin;
$self->make_thumbnail($obj_idx);
}
@ -1281,19 +1307,6 @@ sub object_settings_dialog {
}
}
sub toolpaths_preview {
my ($self) = @_;
# TODO: we should check whether steps are done in $print rather then checking the thread
if ($self->{process_thread}) {
Slic3r::GUI::show_error($self, "Unable to show preview while toolpaths are being generated.");
return;
}
my $dlg = Slic3r::GUI::Plater::2DToolpaths::Dialog->new($self, $self->{print});
$dlg->ShowModal;
}
sub object_list_changed {
my $self = shift;
@ -1316,11 +1329,11 @@ sub selection_changed {
my $method = $have_sel ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method
for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split view settings);
for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings);
if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool($_, $have_sel)
for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_VIEW, TB_SETTINGS);
for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS);
}
if ($self->{object_info_size}) { # have we already loaded the info pane?
@ -1388,6 +1401,13 @@ sub selected_object {
return ($obj_idx, $self->{objects}[$obj_idx]),
}
sub refresh_canvases {
my ($self) = @_;
$self->{canvas}->Refresh;
$self->{canvas3D}->update if $self->{canvas3D};
}
sub validate_config {
my $self = shift;

View File

@ -210,14 +210,13 @@ sub mouse_event {
}
}
$self->Refresh;
} elsif ($event->ButtonUp(&Wx::wxMOUSE_BTN_LEFT)) {
} elsif ($event->LeftUp) {
$self->{on_instance_moved}->(@{ $self->{drag_object} })
if $self->{drag_object};
$self->Refresh;
$self->{drag_start_pos} = undef;
$self->{drag_object} = undef;
$self->SetCursor(wxSTANDARD_CURSOR);
} elsif ($event->ButtonDClick) {
} elsif ($event->LeftDClick) {
$self->{on_double_click}->();
} elsif ($event->Dragging) {
return if !$self->{drag_start_pos}; # concurrency problems

View File

@ -3,21 +3,19 @@ use strict;
use warnings;
use utf8;
use List::Util qw();
use Slic3r::Geometry qw();
use Wx qw(:misc :sizer :slider :statictext);
use Slic3r::Print::State ':steps';
use Wx qw(:misc :sizer :slider :statictext wxWHITE);
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
use base 'Wx::Panel';
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(print enabled));
sub new {
my $class = shift;
my ($parent, $print) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition);
# init print
$self->{print} = $print;
$self->reload_print;
$self->SetBackgroundColour(wxWHITE);
# init GUI elements
my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print);
@ -25,7 +23,9 @@ sub new {
$self, -1,
0, # default
0, # min
scalar(@{$self->{layers_z}})-1, # max
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
# will skip drawing the slider if max <= min:
1, # max
wxDefaultPosition,
wxDefaultSize,
wxVERTICAL | wxSL_INVERSE,
@ -43,7 +43,8 @@ sub new {
$sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
EVT_SLIDER($self, $slider, sub {
$self->set_z($self->{layers_z}[$slider->GetValue]);
$self->set_z($self->{layers_z}[$slider->GetValue])
if $self->enabled;
});
EVT_KEY_DOWN($canvas, sub {
my ($s, $event) = @_;
@ -62,7 +63,9 @@ sub new {
$self->SetMinSize($self->GetSize);
$sizer->SetSizeHints($self);
$self->set_z($self->{layers_z}[0]);
# init print
$self->{print} = $print;
$self->reload_print;
return $self;
}
@ -70,18 +73,41 @@ sub new {
sub reload_print {
my ($self) = @_;
# we require that there's at least one object and the posSlice step
# is performed on all of them (this ensures that _shifted_copies was
# populated and we know the number of layers)
if (!$self->print->object_step_done(STEP_SLICE)) {
$self->enabled(0);
$self->{slider}->Hide;
$self->{canvas}->Refresh; # clears canvas
return;
}
$self->{canvas}->bb($self->print->total_bounding_box);
my %z = (); # z => 1
foreach my $object (@{$self->{print}->objects}) {
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
$z{$layer->print_z} = 1;
}
}
$self->enabled(1);
$self->{layers_z} = [ sort { $a <=> $b } keys %z ];
$self->{slider}->SetRange(0, scalar(@{$self->{layers_z}})-1);
if ((my $z_idx = $self->{slider}->GetValue) <= $#{$self->{layers_z}}) {
$self->set_z($self->{layers_z}[$z_idx]);
} else {
$self->{slider}->SetValue(0);
$self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}};
}
$self->{slider}->Show;
$self->Layout;
}
sub set_z {
my ($self, $z) = @_;
return if !$self->enabled;
$self->{z_label}->SetLabel(sprintf '%.2f', $z);
$self->{canvas}->set_z($z);
}
@ -89,14 +115,15 @@ sub set_z {
package Slic3r::GUI::Plater::2DToolpaths::Canvas;
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
use OpenGL qw(:glconstants :glfunctions :glufunctions);
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
use base qw(Wx::GLCanvas Class::Accessor);
use Wx::GLCanvas qw(:all);
use List::Util qw(min first);
use Slic3r::Geometry qw(scale unscale epsilon);
use Slic3r::Print::State ':steps';
__PACKAGE__->mk_accessors(qw(print z layers color init dirty bb));
__PACKAGE__->mk_accessors(qw(print z layers color init bb));
# make OpenGL::Array thread-safe
{
@ -109,15 +136,12 @@ sub new {
my $self = $class->SUPER::new($parent);
$self->print($print);
$self->bb($self->print->total_bounding_box);
EVT_PAINT($self, sub {
my $dc = Wx::PaintDC->new($self);
$self->Render($dc);
});
EVT_SIZE($self, sub { $self->dirty(1) });
EVT_IDLE($self, sub {
return unless $self->dirty;
EVT_SIZE($self, sub {
return if !$self->IsShownOnScreen;
$self->Resize( $self->GetSizeWH );
$self->Refresh;
@ -152,7 +176,7 @@ sub set_z {
$self->z($z);
$self->layers([ @layers ]);
$self->dirty(1);
$self->Refresh;
}
sub Render {
@ -164,6 +188,15 @@ sub Render {
$self->SetCurrent($context);
$self->InitGL;
glClearColor(1, 1, 1, 0);
glClear(GL_COLOR_BUFFER_BIT);
if (!$self->GetParent->enabled || !$self->layers) {
glFlush();
$self->SwapBuffers;
return;
}
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
my $bb = $self->bb;
@ -184,8 +217,66 @@ sub Render {
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(1, 1, 1, 0);
glClear(GL_COLOR_BUFFER_BIT);
# anti-alias
if (0) {
glEnable(GL_LINE_SMOOTH);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE);
}
my $tess;
if (!(&Wx::wxMSW && $OpenGL::VERSION < 0.6704)) {
# We can't use the GLU tesselator on MSW with older OpenGL versions
# because of an upstream bug:
# http://sourceforge.net/p/pogl/bugs/16/
$tess = gluNewTess();
gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
}
foreach my $layer (@{$self->layers}) {
my $object = $layer->object;
# only draw the slice for the current layer
next unless abs($layer->print_z - $self->z) < epsilon;
# draw slice contour
glLineWidth(1);
foreach my $copy (@{ $object->_shifted_copies }) {
glPushMatrix();
glTranslatef(@$copy, 0);
foreach my $slice (@{$layer->slices}) {
glColor3f(0.95, 0.95, 0.95);
if ($tess) {
gluTessBeginPolygon($tess);
foreach my $polygon (@$slice) {
gluTessBeginContour($tess);
gluTessVertex_p($tess, @$_, 0) for @$polygon;
gluTessEndContour($tess);
}
gluTessEndPolygon($tess);
}
glColor3f(0.9, 0.9, 0.9);
foreach my $polygon (@$slice) {
foreach my $line (@{$polygon->lines}) {
glBegin(GL_LINES);
glVertex2f(@{$line->a});
glVertex2f(@{$line->b});
glEnd();
}
}
}
glPopMatrix();
}
}
my $skirt_drawn = 0;
my $brim_drawn = 0;
@ -194,32 +285,41 @@ sub Render {
my $print_z = $layer->print_z;
# draw brim
if ($layer->id == 0 && !$brim_drawn) {
if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) {
$self->color([0, 0, 0]);
$self->_draw(undef, $print_z, $_) for @{$self->print->brim};
$brim_drawn = 1;
}
if (($self->print->config->skirt_height == -1 || $self->print->config->skirt_height >= $layer->id) && !$skirt_drawn) {
if ($self->print->step_done(STEP_SKIRT)
&& ($self->print->config->skirt_height == -1 || $self->print->config->skirt_height > $layer->id)
&& !$skirt_drawn) {
$self->color([0, 0, 0]);
$self->_draw(undef, $print_z, $_) for @{$self->print->skirt};
$skirt_drawn = 1;
}
foreach my $layerm (@{$layer->regions}) {
if ($object->step_done(STEP_PERIMETERS)) {
$self->color([0.7, 0, 0]);
$self->_draw($object, $print_z, $_) for @{$layerm->perimeters};
}
if ($object->step_done(STEP_INFILL)) {
$self->color([0, 0, 0.7]);
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills};
}
}
if ($object->step_done(STEP_SUPPORTMATERIAL)) {
if ($layer->isa('Slic3r::Layer::Support')) {
$self->color([0, 0, 0]);
$self->_draw($object, $print_z, $_) for @{$layer->support_fills};
$self->_draw($object, $print_z, $_) for @{$layer->support_interface_fills};
}
}
}
gluDeleteTess($tess) if $tess;
glFlush();
$self->SwapBuffers;
}
@ -249,13 +349,15 @@ sub _draw_path {
if (defined $object) {
foreach my $copy (@{ $object->_shifted_copies }) {
glPushMatrix();
glTranslatef(@$copy, 0);
foreach my $line (@{$path->polyline->lines}) {
$line->translate(@$copy);
glBegin(GL_LINES);
glVertex2f(@{$line->a});
glVertex2f(@{$line->b});
glEnd();
}
glPopMatrix();
}
} else {
foreach my $line (@{$path->polyline->lines}) {
@ -299,7 +401,6 @@ sub Resize {
my ($self, $x, $y) = @_;
return unless $self->GetContext;
$self->dirty(0);
$self->SetCurrent($self->GetContext);
glViewport(0, 0, $x, $y);

View File

@ -15,32 +15,98 @@ sub new {
my ($parent, $objects, $model, $config) = @_;
my $self = $class->SUPER::new($parent);
$self->enable_picking(1);
$self->enable_moving(1);
$self->{objects} = $objects;
$self->{model} = $model;
$self->{config} = $config;
$self->{on_select_object} = sub {};
$self->{on_double_click} = sub {};
$self->{on_right_click} = sub {};
$self->{on_instance_moved} = sub {};
$self->on_select(sub {
my ($volume_idx) = @_;
my $obj_idx = undef;
if ($volume_idx != -1) {
$obj_idx = $self->{_volumes_inv}{$volume_idx};
$self->volumes->[$_]->selected(1) for @{$self->{_volumes}{$obj_idx}};
$self->Refresh;
}
$self->{on_select_object}->($obj_idx)
if $self->{on_select_object};
});
$self->on_hover(sub {
my ($volume_idx) = @_;
my $obj_idx = $self->{_volumes_inv}{$volume_idx};
$self->volumes->[$_]->hover(1) for @{$self->{_volumes}{$obj_idx}};
});
$self->on_move(sub {
my ($volume_idx) = @_;
my $volume = $self->volumes->[$volume_idx];
my $obj_idx = $self->{_volumes_inv}{$volume_idx};
my $model_object = $self->{model}->get_object($obj_idx);
$model_object
->instances->[$volume->instance_idx]
->offset
->translate($volume->origin->x, $volume->origin->y); #))
$model_object->invalidate_bounding_box;
$self->{on_instance_moved}->($obj_idx, $volume->instance_idx)
if $self->{on_instance_moved};
});
return $self;
}
sub set_on_select_object {
my ($self, $cb) = @_;
$self->{on_select_object} = $cb;
}
sub set_on_double_click {
my ($self, $cb) = @_;
$self->on_double_click($cb);
}
sub set_on_right_click {
my ($self, $cb) = @_;
$self->on_right_click($cb);
}
sub set_on_instance_moved {
my ($self, $cb) = @_;
$self->{on_instance_moved} = $cb;
}
sub update {
my ($self) = @_;
$self->{_volumes} = {}; # obj_idx => [ volume_idx, volume_idx ]
$self->{_volumes_inv} = {}; # volume_idx => obj_idx
$self->reset_objects;
return if $self->{model}->objects_count == 0;
$self->set_bounding_box($self->{model}->bounding_box);
$self->set_bed_shape($self->{config}->bed_shape);
$self->update_bed_size;
foreach my $model_object (@{$self->{model}->objects}) {
$self->load_object($model_object, 1);
foreach my $obj_idx (0..$#{$self->{model}->objects}) {
my $model_object = $self->{model}->get_object($obj_idx);
my @volume_idxs = $self->load_object($model_object, 1);
# store mapping between canvas volumes and model objects
$self->{_volumes}{$obj_idx} = [ @volume_idxs ];
$self->{_volumes_inv}{$_} = $obj_idx for @volume_idxs;
if ($self->{objects}[$obj_idx]->selected) {
$self->select_volume($_) for @volume_idxs;
}
}
}
sub update_bed_size {
my ($self) = @_;
$self->set_bed_shape($self->{config}->bed_shape);
}
1;

View File

@ -88,10 +88,10 @@ sub new {
if ($Slic3r::GUI::have_OpenGL) {
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self);
$canvas->load_object($self->{model_object});
$canvas->set_bounding_box($self->{model_object}->bounding_box);
$canvas->set_auto_bed_shape;
$canvas->SetSize([500,500]);
$canvas->SetMinSize($canvas->GetSize);
$canvas->zoom_to_volumes;
}
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);

View File

@ -70,9 +70,9 @@ sub new {
if ($Slic3r::GUI::have_OpenGL) {
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self);
$canvas->load_object($self->{model_object});
$canvas->set_bounding_box($self->{model_object}->bounding_box);
$canvas->set_auto_bed_shape;
$canvas->SetSize([500,500]);
$canvas->zoom_to_volumes;
}
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
@ -214,7 +214,7 @@ sub on_btn_load {
$new_volume->set_name(basename($input_file));
# apply the same translation we applied to the object
$new_volume->mesh->translate(@{$self->{model_object}->origin_translation}, 0);
$new_volume->mesh->translate(@{$self->{model_object}->origin_translation});
# set a default extruder value, since user can't add it manually
$new_volume->config->set_ifndef('extruder', 0);
@ -226,7 +226,9 @@ sub on_btn_load {
$self->reload_tree;
if ($self->{canvas}) {
$self->{canvas}->reset_objects;
$self->{canvas}->load_object($self->{model_object});
$self->{canvas}->set_bounding_box($self->{model_object}->bounding_box);
$self->{canvas}->Render;
}
}

View File

@ -4,7 +4,7 @@ use warnings;
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
# must load OpenGL *before* Wx::GLCanvas
use OpenGL qw(:glconstants :glfunctions :glufunctions);
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
use base qw(Wx::GLCanvas Class::Accessor);
use Math::Trig qw(asin);
use List::Util qw(reduce min max first);
@ -12,21 +12,38 @@ use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan scal
use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl);
use Wx::GLCanvas qw(:all);
__PACKAGE__->mk_accessors( qw(quat dirty init mview_init
object_bounding_box
volumes initpos
sphi stheta
__PACKAGE__->mk_accessors( qw(_quat _dirty init
enable_picking
enable_moving
on_hover
on_select
on_double_click
on_right_click
on_move
volumes
print
_sphi _stheta
cutting_plane_z
cut_lines_vertices
bed_shape
bed_triangles
bed_grid_lines
origin
_mouse_pos
_hover_volume_idx
_drag_volume_idx
_drag_start_pos
_drag_start_xy
_dragged
_camera_target
_zoom
) );
use constant TRACKBALLSIZE => 0.8;
use constant TURNTABLE_MODE => 1;
use constant GROUND_Z => 0.02;
use constant SELECTED_COLOR => [0,1,0,1];
use constant HOVER_COLOR => [0.8,0.8,0,1];
use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
# make OpenGL::Array thread-safe
@ -43,73 +60,236 @@ sub new {
my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "",
[WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0]);
$self->quat((0, 0, 0, 1));
$self->sphi(45);
$self->stheta(-45);
$self->_quat((0, 0, 0, 1));
$self->_stheta(45);
$self->_sphi(45);
$self->_zoom(1);
# 3D point in model space
$self->_camera_target(Slic3r::Pointf3->new(0,0,0));
$self->reset_objects;
EVT_PAINT($self, sub {
my $dc = Wx::PaintDC->new($self);
return if !$self->object_bounding_box;
$self->Render($dc);
});
EVT_SIZE($self, sub { $self->dirty(1) });
EVT_SIZE($self, sub { $self->_dirty(1) });
EVT_IDLE($self, sub {
return unless $self->dirty;
return unless $self->_dirty;
return if !$self->IsShownOnScreen;
return if !$self->object_bounding_box;
$self->Resize( $self->GetSizeWH );
$self->Refresh;
});
EVT_MOUSEWHEEL($self, sub {
my ($self, $e) = @_;
my $zoom = ($e->GetWheelRotation() / $e->GetWheelDelta() / 10);
$zoom = $zoom > 0 ? (1.0 + $zoom) : 1 / (1.0 - $zoom);
my @pos3d = $self->mouse_to_3d($e->GetX(), $e->GetY());
$self->ZoomTo($zoom, $pos3d[0], $pos3d[1]);
# Calculate the zoom delta and apply it to the current zoom factor
my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
$zoom = max(min($zoom, 4), -4);
$zoom /= 10;
$self->_zoom($self->_zoom * (1-$zoom));
# In order to zoom around the mouse point we need to translate
# the camera target
my $size = Slic3r::Pointf->new($self->GetSizeWH);
my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
$self->_camera_target->translate(
# ($pos - $size/2) represents the vector from the viewport center
# to the mouse point. By multiplying it by $zoom we get the new,
# transformed, length of such vector.
# Since we want that point to stay fixed, we move our camera target
# in the opposite direction by the delta of the length of such vector
# ($zoom - 1). We then scale everything by 1/$self->_zoom since
# $self->_camera_target is expressed in terms of model units.
-($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
-($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
0,
) if 0;
$self->_dirty(1);
$self->Refresh;
});
EVT_MOUSE_EVENTS($self, sub {
EVT_MOUSE_EVENTS($self, \&mouse_event);
return $self;
}
sub mouse_event {
my ($self, $e) = @_;
if ($e->Dragging() && $e->LeftIsDown()) {
$self->handle_rotation($e);
} elsif ($e->Dragging() && $e->RightIsDown()) {
$self->handle_translation($e);
} elsif ($e->LeftUp() || $e->RightUp()) {
$self->initpos(undef);
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
if ($e->Entering && &Wx::wxMSW) {
# wxMSW needs focus in order to catch mouse wheel events
$self->SetFocus;
} elsif ($e->LeftDClick) {
$self->on_double_click->()
if $self->on_double_click;
} elsif ($e->LeftDown || $e->RightDown) {
# If user pressed left or right button we first check whether this happened
# on a volume or not.
my $volume_idx = $self->_hover_volume_idx // -1;
# select volume in this 3D canvas
if ($self->enable_picking) {
$self->deselect_volumes;
$self->select_volume($volume_idx);
$self->Refresh;
}
# propagate event through callback
$self->on_select->($volume_idx)
if $self->on_select;
if ($volume_idx != -1) {
if ($e->LeftDown && $self->enable_moving) {
$self->_drag_volume_idx($volume_idx);
$self->_drag_start_pos($self->mouse_to_3d(@$pos));
} elsif ($e->RightDown) {
# if right clicking on volume, propagate event through callback
$self->on_right_click->($e->GetPosition)
if $self->on_right_click;
}
}
} elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) {
# get volume being dragged
my $volume = $self->volumes->[$self->_drag_volume_idx];
# get new position at the same Z of the initial click point
my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY);
my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z);
# calculate the translation vector
my $vector = $self->_drag_start_pos->vector_to($cur_pos);
# apply new temporary volume origin and ignore Z
$volume->origin->translate($vector->x, $vector->y, 0); #,,
$self->_drag_start_pos($cur_pos);
$self->_dragged(1);
$self->Refresh;
} elsif ($e->Dragging && !defined $self->_hover_volume_idx) {
if ($e->LeftIsDown) {
# if dragging over blank area with left button, rotate
if (defined $self->_drag_start_pos) {
my $orig = $self->_drag_start_pos;
if (TURNTABLE_MODE) {
$self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
$self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #-
$self->_stheta(150) if $self->_stheta > 150;
$self->_stheta(0) if $self->_stheta < 0;
} else {
my $size = $self->GetClientSize;
my @quat = trackball(
$orig->x / ($size->width / 2) - 1,
1 - $orig->y / ($size->height / 2), #/
$pos->x / ($size->width / 2) - 1,
1 - $pos->y / ($size->height / 2), #/
);
$self->_quat(mulquats($self->_quat, \@quat));
}
$self->Refresh;
}
$self->_drag_start_pos($pos);
} elsif ($e->MiddleIsDown || $e->RightIsDown) {
# if dragging over blank area with right button, translate
if (defined $self->_drag_start_xy) {
# get point in model space at Z = 0
my $cur_pos = $self->mouse_ray($e->GetX, $e->GetY)->intersect_plane(0);
my $orig = $self->mouse_ray(@{$self->_drag_start_xy})->intersect_plane(0);
$self->_camera_target->translate(
@{$orig->vector_to($cur_pos)->negative},
);
$self->Refresh;
}
$self->_drag_start_xy($pos);
}
} elsif ($e->LeftUp || $e->RightUp) {
if ($self->on_move && defined $self->_drag_volume_idx) {
$self->on_move->($self->_drag_volume_idx) if $self->_dragged;
}
$self->_drag_volume_idx(undef);
$self->_drag_start_pos(undef);
$self->_drag_start_xy(undef);
$self->_dragged(undef);
} elsif ($e->Moving) {
$self->_mouse_pos($pos);
$self->Refresh;
} else {
$e->Skip();
}
});
return $self;
}
sub reset_objects {
my ($self) = @_;
$self->volumes([]);
$self->dirty(1);
$self->_dirty(1);
}
# this method accepts a Slic3r::BoudingBox3f object
sub set_bounding_box {
sub zoom_to_bounding_box {
my ($self, $bb) = @_;
$self->object_bounding_box($bb);
$self->dirty(1);
# calculate the zoom factor needed to adjust viewport to
# bounding box
my $max_size = max(@{$bb->size}) * 2;
my $min_viewport_size = min($self->GetSizeWH);
$self->_zoom($min_viewport_size / $max_size);
# center view around bounding box center
$self->_camera_target($bb->center);
}
sub zoom_to_bed {
my ($self) = @_;
if ($self->bed_shape) {
$self->zoom_to_bounding_box($self->bed_bounding_box);
}
}
sub zoom_to_volume {
my ($self, $volume_idx) = @_;
my $volume = $self->volumes->[$volume_idx];
my $bb = $volume->bounding_box;
$self->zoom_to_bounding_box($bb);
}
sub zoom_to_volumes {
my ($self) = @_;
$self->zoom_to_bounding_box($self->volumes_bounding_box);
}
sub volumes_bounding_box {
my ($self) = @_;
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
$bb->merge($_->bounding_box) for @{$self->volumes};
return $bb;
}
sub bed_bounding_box {
my ($self) = @_;
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
$bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape};
return $bb;
}
sub max_bounding_box {
my ($self) = @_;
my $bb = $self->bed_bounding_box;
$bb->merge($self->volumes_bounding_box);
return $bb;
}
sub set_auto_bed_shape {
my ($self, $bed_shape) = @_;
# draw a default square bed around object center
my $max_size = max(@{ $self->object_bounding_box->size });
my $center = $self->object_bounding_box->center;
my $max_size = max(@{ $self->volumes_bounding_box->size });
my $center = $self->volumes_bounding_box->center;
$self->set_bed_shape([
[ $center->x - $max_size, $center->y - $max_size ], #--
[ $center->x + $max_size, $center->y - $max_size ], #--
@ -122,6 +302,8 @@ sub set_auto_bed_shape {
sub set_bed_shape {
my ($self, $bed_shape) = @_;
$self->bed_shape($bed_shape);
# triangulate bed
my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
my $bed_bb = $expolygon->bounding_box;
@ -163,9 +345,11 @@ sub load_object {
# sort volumes: non-modifiers first
my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes};
my @volumes_idx = ();
foreach my $volume (@volumes) {
my @instances = $all_instances ? @{$object->instances} : $object->instances->[0];
foreach my $instance (@instances) {
my @instance_idxs = $all_instances ? (0..$#{$object->instances}) : (0);
foreach my $instance_idx (@instance_idxs) {
my $instance = $object->instances->[$instance_idx];
my $mesh = $volume->mesh->clone;
$instance->transform_mesh($mesh);
@ -178,24 +362,40 @@ sub load_object {
my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
push @$color, $volume->modifier ? 0.5 : 1;
push @{$self->volumes}, my $v = {
push @{$self->volumes}, my $v = Slic3r::GUI::PreviewCanvas::Volume->new(
instance_idx => $instance_idx,
mesh => $mesh,
color => $color,
z_min => $z_min,
};
origin => Slic3r::Pointf3->new(0,0,-$z_min),
);
push @volumes_idx, $#{$self->volumes};
{
my $vertices = $mesh->vertices;
my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets};
$v->{verts} = OpenGL::Array->new_list(GL_FLOAT, @verts);
$v->verts(OpenGL::Array->new_list(GL_FLOAT, @verts));
}
{
my @norms = map { @$_, @$_, @$_ } @{$mesh->normals};
$v->{norms} = OpenGL::Array->new_list(GL_FLOAT, @norms);
$v->norms(OpenGL::Array->new_list(GL_FLOAT, @norms));
}
}
}
return @volumes_idx;
}
sub deselect_volumes {
my ($self) = @_;
$_->selected(0) for @{$self->volumes};
}
sub select_volume {
my ($self, $volume_idx) = @_;
$self->volumes->[$volume_idx]->selected(1)
if $volume_idx != -1;
}
sub SetCuttingPlane {
@ -207,7 +407,7 @@ sub SetCuttingPlane {
my @verts = ();
foreach my $volume (@{$self->volumes}) {
foreach my $volume (@{$self->volumes}) {
my $expolygons = $volume->{mesh}->slice([ $z + $volume->{z_min} ])->[0];
my $expolygons = $volume->mesh->slice([ $z - $volume->origin->z ])->[0];
$expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
@ -328,73 +528,26 @@ sub mulquats {
@$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
}
sub handle_rotation {
my ($self, $e) = @_;
if (not defined $self->initpos) {
$self->initpos($e->GetPosition());
} else {
my $orig = $self->initpos;
my $new = $e->GetPosition();
my $size = $self->GetClientSize();
if (TURNTABLE_MODE) {
$self->sphi($self->sphi + ($new->x - $orig->x)*TRACKBALLSIZE);
$self->stheta($self->stheta + ($new->y - $orig->y)*TRACKBALLSIZE); #-
} else {
my @quat = trackball($orig->x / ($size->width / 2) - 1,
1 - $orig->y / ($size->height / 2), #/
$new->x / ($size->width / 2) - 1,
1 - $new->y / ($size->height / 2), #/
);
$self->quat(mulquats($self->quat, \@quat));
}
$self->initpos($new);
$self->Refresh;
}
}
sub handle_translation {
my ($self, $e) = @_;
if (not defined $self->initpos) {
$self->initpos($e->GetPosition());
} else {
my $new = $e->GetPosition();
my $orig = $self->initpos;
my @orig3d = $self->mouse_to_3d($orig->x, $orig->y); #)()
my @new3d = $self->mouse_to_3d($new->x, $new->y); #)()
glTranslatef($new3d[0] - $orig3d[0], $new3d[1] - $orig3d[1], 0);
$self->initpos($new);
$self->Refresh;
}
}
sub mouse_to_3d {
my ($self, $x, $y) = @_;
my ($self, $x, $y, $z) = @_;
my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items
my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items
my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items
my @projected = gluUnProject_p($x, $viewport[3] - $y, 1.0, @mview, @proj, @viewport);
return @projected;
$y = $viewport[3] - $y;
$z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport);
return Slic3r::Pointf3->new(@projected);
}
sub ZoomTo {
my ($self, $factor, $tox, $toy) = @_;
sub mouse_ray {
my ($self, $x, $y) = @_;
return if !$self->init;
glTranslatef($tox, $toy, 0);
glMatrixMode(GL_MODELVIEW);
$self->Zoom($factor);
glTranslatef(-$tox, -$toy, 0);
}
sub Zoom {
my ($self, $factor) = @_;
glMatrixMode(GL_MODELVIEW);
glScalef($factor, $factor, 1);
return Slic3r::Linef3->new(
$self->mouse_to_3d($x, $y, 0),
$self->mouse_to_3d($x, $y, 1),
);
}
sub GetContext {
@ -417,37 +570,26 @@ sub SetCurrent {
}
}
sub ResetModelView {
my ($self, $factor) = @_;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
my $win_size = $self->GetClientSize();
my $ratio = $factor * min($win_size->width, $win_size->height) / (2 * max(@{ $self->object_bounding_box->size }));
glScalef($ratio, $ratio, 1);
}
sub Resize {
my ($self, $x, $y) = @_;
return unless $self->GetContext;
$self->dirty(0);
$self->_dirty(0);
$self->SetCurrent($self->GetContext);
glViewport(0, 0, $x, $y);
$x /= $self->_zoom;
$y /= $self->_zoom;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(
-$x/2, $x/2, -$y/2, $y/2,
-200, 10 * max(@{ $self->object_bounding_box->size }),
-200, 10 * max(@{ $self->max_bounding_box->size }),
);
glMatrixMode(GL_MODELVIEW);
unless ($self->mview_init) {
$self->mview_init(1);
$self->ResetModelView(0.9);
}
}
sub InitGL {
@ -466,6 +608,11 @@ sub InitGL {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
# Set antialiasing/multisampling
glDisable(GL_LINE_SMOOTH);
glDisable(GL_POLYGON_SMOOTH);
glEnable(GL_MULTISAMPLE);
# ambient lighting
glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1);
@ -508,22 +655,41 @@ sub Render {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
my $bb = $self->object_bounding_box;
my $object_size = $bb->size;
glTranslatef(0, 0, -max(@$object_size[0..1]));
if (TURNTABLE_MODE) {
glRotatef(-$self->_stheta, 1, 0, 0); # pitch
glRotatef($self->_sphi, 0, 0, 1); # yaw
} else {
my @rotmat = quat_to_rotmatrix($self->quat);
glMultMatrixd_p(@rotmat[0..15]);
glRotatef($self->stheta, 1, 0, 0);
glRotatef($self->sphi, 0, 0, 1);
}
glTranslatef(@{ $self->_camera_target->negative });
# center everything around 0,0 since that's where we're looking at (glOrtho())
my $center = $bb->center;
glTranslatef(-$center->x, -$center->y, 0); #,,
if ($self->enable_picking) {
glDisable(GL_LIGHTING);
$self->draw_volumes(1);
glFlush();
glFinish();
if (my $pos = $self->_mouse_pos) {
my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
$self->_hover_volume_idx(undef);
$_->hover(0) for @{$self->volumes};
if ($volume_idx <= $#{$self->volumes}) {
$self->_hover_volume_idx($volume_idx);
$self->volumes->[$volume_idx]->hover(1);
$self->on_hover->($volume_idx) if $self->on_hover;
}
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glFlush();
glFinish();
glEnable(GL_LIGHTING);
}
# draw objects
$self->draw_mesh;
$self->draw_volumes;
# draw ground and axes
glDisable(GL_LIGHTING);
@ -537,7 +703,7 @@ sub Render {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(0.5, 0.5, 0.5, 0.3);
glColor4f(0.6, 0.7, 0.5, 0.3);
glNormal3d(0,0,1);
glVertexPointer_p(3, $self->bed_triangles);
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
@ -548,18 +714,23 @@ sub Render {
# draw grid
glTranslatef(0, 0, 0.02);
glLineWidth(3);
glColor3f(1.0, 1.0, 1.0);
glColor3f(0.95, 0.95, 0.95);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer_p(3, $self->bed_grid_lines);
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
}
my $volumes_bb = $self->volumes_bounding_box;
{
# draw axes
$ground_z += 0.02;
my $origin = $self->origin;
my $axis_len = 2 * max(@{ $object_size });
my $axis_len = max(
0.3 * max(@{ $self->bed_bounding_box->size }),
2 * max(@{ $volumes_bb->size }),
);
glLineWidth(2);
glBegin(GL_LINES);
# draw line for x axis
@ -580,6 +751,7 @@ sub Render {
# draw cutting plane
if (defined $self->cutting_plane_z) {
my $plane_z = $z0 + $self->cutting_plane_z;
my $bb = $volumes_bb;
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -597,35 +769,107 @@ sub Render {
glEnable(GL_LIGHTING);
glPopMatrix();
glFlush();
$self->SwapBuffers();
}
sub draw_mesh {
my $self = shift;
sub draw_volumes {
my ($self, $fakecolor) = @_;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (defined($self->print) && !$fakecolor) {
my $tess = gluNewTess();
gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
foreach my $object (@{$self->print->objects}) {
foreach my $layer (@{$object->layers}) {
my $gap = 0;
my $top_z = $layer->print_z;
my $bottom_z = $layer->print_z - $layer->height + $gap;
foreach my $copy (@{ $object->_shifted_copies }) {
glPushMatrix();
glTranslatef(map unscale($_), @$copy, 0);
foreach my $slice (@{$layer->slices}) {
glColor3f(@{COLORS->[0]});
gluTessBeginPolygon($tess);
glNormal3f(0,0,1);
foreach my $polygon (@$slice) {
gluTessBeginContour($tess);
gluTessVertex_p($tess, (map unscale($_), @$_), $layer->print_z) for @$polygon;
gluTessEndContour($tess);
}
gluTessEndPolygon($tess);
foreach my $polygon (@$slice) {
foreach my $line (@{$polygon->lines}) {
if (0) {
glLineWidth(1);
glColor3f(0,0,0);
glBegin(GL_LINES);
glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
glEnd();
}
glLineWidth(0);
glColor3f(@{COLORS->[0]});
glBegin(GL_QUADS);
glNormal3f((map $_/$line->length, @{$line->normal}), 0);
glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
glVertex3f((map unscale($_), @{$line->b}), $top_z);
glVertex3f((map unscale($_), @{$line->a}), $top_z);
glEnd();
}
}
}
glPopMatrix(); # copy
}
}
}
gluDeleteTess($tess);
return;
}
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
foreach my $volume (@{$self->volumes}) {
glTranslatef(0, 0, -$volume->{z_min});
foreach my $volume_idx (0..$#{$self->volumes}) {
my $volume = $self->volumes->[$volume_idx];
glPushMatrix();
glTranslatef(@{$volume->origin});
glVertexPointer_p(3, $volume->{verts});
glVertexPointer_p(3, $volume->verts);
glCullFace(GL_BACK);
glNormalPointer_p($volume->{norms});
if ($volume->{selected}) {
glNormalPointer_p($volume->norms);
if ($fakecolor) {
my $r = ($volume_idx & 0x000000FF) >> 0;
my $g = ($volume_idx & 0x0000FF00) >> 8;
my $b = ($volume_idx & 0x00FF0000) >> 16;
glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
} elsif ($volume->selected) {
glColor4f(@{ &SELECTED_COLOR });
} elsif ($volume->hover) {
glColor4f(@{ &HOVER_COLOR });
} else {
glColor4f(@{ $volume->{color} });
glColor4f(@{ $volume->color });
}
glDrawArrays(GL_TRIANGLES, 0, $volume->{verts}->elements / 3);
glDrawArrays(GL_TRIANGLES, 0, $volume->verts->elements / 3);
glTranslatef(0, 0, +$volume->{z_min});
glPopMatrix();
}
glDisableClientState(GL_NORMAL_ARRAY);
glDisable(GL_BLEND);
@ -639,4 +883,24 @@ sub draw_mesh {
glDisableClientState(GL_VERTEX_ARRAY);
}
package Slic3r::GUI::PreviewCanvas::Volume;
use Moo;
has 'mesh' => (is => 'ro', required => 1);
has 'color' => (is => 'ro', required => 1);
has 'instance_idx' => (is => 'ro', default => sub { 0 });
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
has 'verts' => (is => 'rw');
has 'norms' => (is => 'rw');
has 'selected' => (is => 'rw', default => sub { 0 });
has 'hover' => (is => 'rw', default => sub { 0 });
sub bounding_box {
my ($self) = @_;
my $bb = $self->mesh->bounding_box;
$bb->translate(@{$self->origin});
return $bb;
}
1;

View File

@ -8,7 +8,9 @@ use List::Util qw(first);
use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :window
wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_TREE_SEL_CHANGED);
use base 'Wx::Panel';
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(current_preset));
sub new {
my $class = shift;
@ -86,18 +88,17 @@ sub new {
EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset });
EVT_BUTTON($self, $self->{btn_delete_preset}, sub {
my $i = $self->{presets_choice}->GetSelection;
my $i = $self->current_preset;
return if $i == 0; # this shouldn't happen but let's trap it anyway
my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal;
return unless $res == wxID_YES;
if (-e $self->{presets}[$i]{file}) {
unlink $self->{presets}[$i]{file};
if (-e $self->{presets}[$i]->file) {
unlink $self->{presets}[$i]->file;
}
splice @{$self->{presets}}, $i, 1;
$self->set_dirty(0);
$self->{presets_choice}->Delete($i);
$self->{presets_choice}->SetSelection(0);
$self->on_select_preset;
$self->current_preset(undef);
$self->select_preset(0);
$self->_on_presets_changed;
});
@ -111,14 +112,14 @@ sub new {
return $self;
}
sub current_preset {
sub get_current_preset {
my $self = shift;
return $self->{presets}[ $self->{presets_choice}->GetSelection ];
return $self->get_preset($self->current_preset);
}
sub get_preset {
my $self = shift;
return $self->{presets}[ $_[0] ];
my ($self, $i) = @_;
return $self->{presets}[$i];
}
sub save_preset {
@ -130,23 +131,22 @@ sub save_preset {
$self->{treectrl}->SetFocus;
if (!defined $name) {
my $preset = $self->current_preset;
my $default_name = $preset->{default} ? 'Untitled' : basename($preset->{name});
my $preset = $self->get_current_preset;
my $default_name = $preset->default ? 'Untitled' : $preset->name;
$default_name =~ s/\.ini$//i;
my $dlg = Slic3r::GUI::SavePresetWindow->new($self,
title => lc($self->title),
default => $default_name,
values => [ map { my $name = $_->{name}; $name =~ s/\.ini$//i; $name } @{$self->{presets}} ],
values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ],
);
return unless $dlg->ShowModal == wxID_OK;
$name = $dlg->get_name;
}
$self->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->name, $name);
$self->set_dirty(0);
$self->load_presets;
$self->select_preset(first { basename($self->{presets}[$_]{file}) eq $name . ".ini" } 1 .. $#{$self->{presets}});
$self->select_preset_by_name($name);
$self->_on_presets_changed;
}
@ -185,7 +185,7 @@ sub config { $_[0]->{config}->clone }
sub select_default_preset {
my $self = shift;
$self->{presets_choice}->SetSelection(0);
$self->select_preset(0);
}
sub select_preset {
@ -194,21 +194,30 @@ sub select_preset {
$self->on_select_preset;
}
sub select_preset_by_name {
my ($self, $name) = @_;
$self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}});
}
sub on_select_preset {
my $self = shift;
if ($self->{dirty}) {
my $name = $self->{dirty} == 0 ? 'Default preset' : "Preset \"$self->{presets}[$self->{dirty}]{name}\"";
my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes. Discard changes and continue anyway?",
if ($self->is_dirty) {
my $old_preset = $self->get_current_preset;
my $name = $old_preset->default ? 'Default preset' : "Preset \"" . $old_preset->name . "\"";
my $changes = join "\n",
map { "- " . ($Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) }
@{$self->dirty_options};
my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDiscard changes and continue anyway?",
'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
if ($confirm->ShowModal == wxID_NO) {
$self->{presets_choice}->SetSelection($self->{dirty});
$self->{presets_choice}->SetSelection($self->current_preset);
return;
}
$self->set_dirty(0);
}
my $preset = $self->current_preset;
$self->current_preset($self->{presets_choice}->GetSelection);
my $preset = $self->get_current_preset;
my $preset_config = $self->get_preset_config($preset);
eval {
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
@ -216,21 +225,14 @@ sub on_select_preset {
$self->{config}->set($opt_key, $preset_config->get($opt_key))
if $preset_config->has($opt_key);
}
($preset->{default} || $preset->{external})
($preset->default || $preset->external)
? $self->{btn_delete_preset}->Disable
: $self->{btn_delete_preset}->Enable;
$self->_update;
$self->on_preset_loaded;
$self->reload_config;
# use CallAfter because some field triggers schedule on_change calls using CallAfter,
# and we don't want them to be called after this set_dirty(0) as they would mark the
# preset dirty again
wxTheApp->CallAfter(sub {
$self->set_dirty(0);
});
$Slic3r::GUI::Settings->{presets}{$self->name} = $preset->{file} ? basename($preset->{file}) : '';
$Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : '';
};
if ($@) {
$@ = "I was unable to load the selected config file: $@";
@ -238,31 +240,18 @@ sub on_select_preset {
$self->select_default_preset;
}
# use CallAfter because some field triggers schedule on_change calls using CallAfter,
# and we don't want them to be called after this update_dirty() as they would mark the
# preset dirty again
# (not sure this is true anymore now that update_dirty is idempotent)
wxTheApp->CallAfter(sub {
$self->_on_presets_changed;
$self->update_dirty;
});
wxTheApp->save_settings;
}
sub get_preset_config {
my $self = shift;
my ($preset) = @_;
if ($preset->{default}) {
return Slic3r::Config->new_from_defaults(@{$self->{config}->get_keys});
} else {
if (!-e $preset->{file}) {
Slic3r::GUI::show_error($self, "The selected preset does not exist anymore ($preset->{file}).");
return;
}
# apply preset values on top of defaults
my $external_config = Slic3r::Config->load($preset->{file});
my $config = Slic3r::Config->new;
$config->set($_, $external_config->get($_))
for grep $external_config->has($_), @{$self->{config}->get_keys};
return $config;
}
}
sub init_config_options {
my ($self, @opt_keys) = @_;
$self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys));
@ -305,53 +294,52 @@ sub update_tree {
}
}
sub set_dirty {
sub update_dirty {
my $self = shift;
my ($dirty) = @_;
return if $dirty and $self->is_dirty;
return if (not $dirty) and (not $self->is_dirty);
my $selection = $self->{presets_choice}->GetSelection;
my $i = $self->{dirty} // $selection; #/
my $text = $self->{presets_choice}->GetString($i);
if ($dirty) {
$self->{dirty} = $i;
if ($text !~ / \(modified\)$/) {
$self->{presets_choice}->SetString($i, "$text (modified)");
$self->{presets_choice}->SetSelection($selection); # http://trac.wxwidgets.org/ticket/13769
}
foreach my $i (0..$#{$self->{presets}}) {
my $preset = $self->get_preset($i);
if ($i == $self->current_preset && $self->is_dirty) {
$self->{presets_choice}->SetString($i, $preset->name . " (modified)");
} else {
$self->{dirty} = undef;
$text =~ s/ \(modified\)$//;
$self->{presets_choice}->SetString($i, $text);
$self->{presets_choice}->SetSelection($selection); # http://trac.wxwidgets.org/ticket/13769
$self->{presets_choice}->SetString($i, $preset->name);
}
}
$self->{presets_choice}->SetSelection($self->current_preset); # http://trac.wxwidgets.org/ticket/13769
$self->_on_presets_changed;
}
sub is_dirty {
my $self = shift;
return (defined $self->{dirty});
return @{$self->dirty_options} > 0;
}
sub dirty_options {
my $self = shift;
return [] if !defined $self->current_preset; # happens during initialization
return $self->get_preset_config($self->get_current_preset)->diff($self->{config});
}
sub load_presets {
my $self = shift;
$self->{presets} = [{
$self->{presets} = [
Slic3r::GUI::Tab::Preset->new(
default => 1,
name => '- default -',
}];
),
];
my %presets = wxTheApp->presets($self->name);
foreach my $preset_name (sort keys %presets) {
push @{$self->{presets}}, {
push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new(
name => $preset_name,
file => $presets{$preset_name},
};
);
}
$self->current_preset(undef);
$self->{presets_choice}->Clear;
$self->{presets_choice}->Append($_->{name}) for @{$self->{presets}};
{
@ -370,11 +358,11 @@ sub load_config_file {
my $i = first { $self->{presets}[$_]{file} eq $file && $self->{presets}[$_]{external} } 1..$#{$self->{presets}};
if (!$i) {
my $preset_name = basename($file); # keep the .ini suffix
push @{$self->{presets}}, {
push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new(
file => $file,
name => $preset_name,
external => 1,
};
);
$self->{presets_choice}->Append($preset_name);
$i = $#{$self->{presets}};
}
@ -389,11 +377,16 @@ sub load_config {
foreach my $opt_key (@{$self->{config}->diff($config)}) {
$self->{config}->set($opt_key, $config->get($opt_key));
$self->set_dirty(1);
$self->update_dirty;
}
$self->reload_config;
}
sub get_preset_config {
my ($self, $preset) = @_;
return $preset->config($self->{config}->get_keys);
}
sub get_field {
my ($self, $opt_key, $opt_index) = @_;
@ -432,7 +425,7 @@ sub build {
top_solid_layers bottom_solid_layers
extra_perimeters avoid_crossing_perimeters thin_walls overhangs
seam_position external_perimeters_first
fill_density fill_pattern solid_fill_pattern
fill_density fill_pattern external_fill_pattern
infill_every_layers infill_only_where_needed
solid_infill_every_layers fill_angle solid_infill_below_area
only_retract_when_crossing_perimeters infill_first
@ -454,7 +447,8 @@ sub build {
complete_objects extruder_clearance_radius extruder_clearance_height
gcode_comments output_filename_format
post_process
perimeter_extruder infill_extruder support_material_extruder support_material_interface_extruder
perimeter_extruder infill_extruder solid_infill_extruder
support_material_extruder support_material_interface_extruder
ooze_prevention standby_temperature_delta
interface_shells
extrusion_width first_layer_extrusion_width perimeter_extrusion_width
@ -505,7 +499,7 @@ sub build {
my $optgroup = $page->new_optgroup('Infill');
$optgroup->append_single_option_line('fill_density');
$optgroup->append_single_option_line('fill_pattern');
$optgroup->append_single_option_line('solid_fill_pattern');
$optgroup->append_single_option_line('external_fill_pattern');
}
{
my $optgroup = $page->new_optgroup('Reducing printing time');
@ -522,39 +516,6 @@ sub build {
}
}
{
my $page = $self->add_options_page('Speed', 'time.png');
{
my $optgroup = $page->new_optgroup('Speed for print moves');
$optgroup->append_single_option_line('perimeter_speed');
$optgroup->append_single_option_line('small_perimeter_speed');
$optgroup->append_single_option_line('external_perimeter_speed');
$optgroup->append_single_option_line('infill_speed');
$optgroup->append_single_option_line('solid_infill_speed');
$optgroup->append_single_option_line('top_solid_infill_speed');
$optgroup->append_single_option_line('support_material_speed');
$optgroup->append_single_option_line('support_material_interface_speed');
$optgroup->append_single_option_line('bridge_speed');
$optgroup->append_single_option_line('gap_fill_speed');
}
{
my $optgroup = $page->new_optgroup('Speed for non-print moves');
$optgroup->append_single_option_line('travel_speed');
}
{
my $optgroup = $page->new_optgroup('Modifiers');
$optgroup->append_single_option_line('first_layer_speed');
}
{
my $optgroup = $page->new_optgroup('Acceleration control (advanced)');
$optgroup->append_single_option_line('perimeter_acceleration');
$optgroup->append_single_option_line('infill_acceleration');
$optgroup->append_single_option_line('bridge_acceleration');
$optgroup->append_single_option_line('first_layer_acceleration');
$optgroup->append_single_option_line('default_acceleration');
}
}
{
my $page = $self->add_options_page('Skirt and brim', 'box.png');
{
@ -594,51 +555,35 @@ sub build {
}
{
my $page = $self->add_options_page('Notes', 'note.png');
my $page = $self->add_options_page('Speed', 'time.png');
{
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
);
my $option = $optgroup->get_option('notes');
$option->full_width(1);
$option->height(250);
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Output options', 'page_white_go.png');
{
my $optgroup = $page->new_optgroup('Sequential printing');
$optgroup->append_single_option_line('complete_objects');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Extruder clearance (mm)',
);
foreach my $opt_key (qw(extruder_clearance_radius extruder_clearance_height)) {
my $option = $optgroup->get_option($opt_key);
$option->width(60);
$line->append_option($option);
}
$optgroup->append_line($line);
my $optgroup = $page->new_optgroup('Speed for print moves');
$optgroup->append_single_option_line('perimeter_speed');
$optgroup->append_single_option_line('small_perimeter_speed');
$optgroup->append_single_option_line('external_perimeter_speed');
$optgroup->append_single_option_line('infill_speed');
$optgroup->append_single_option_line('solid_infill_speed');
$optgroup->append_single_option_line('top_solid_infill_speed');
$optgroup->append_single_option_line('support_material_speed');
$optgroup->append_single_option_line('support_material_interface_speed');
$optgroup->append_single_option_line('bridge_speed');
$optgroup->append_single_option_line('gap_fill_speed');
}
{
my $optgroup = $page->new_optgroup('Output file');
$optgroup->append_single_option_line('gcode_comments');
{
my $option = $optgroup->get_option('output_filename_format');
$option->full_width(1);
$optgroup->append_single_option_line($option);
}
my $optgroup = $page->new_optgroup('Speed for non-print moves');
$optgroup->append_single_option_line('travel_speed');
}
{
my $optgroup = $page->new_optgroup('Post-processing scripts',
label_width => 0,
);
my $option = $optgroup->get_option('post_process');
$option->full_width(1);
$option->height(50);
$optgroup->append_single_option_line($option);
my $optgroup = $page->new_optgroup('Modifiers');
$optgroup->append_single_option_line('first_layer_speed');
}
{
my $optgroup = $page->new_optgroup('Acceleration control (advanced)');
$optgroup->append_single_option_line('perimeter_acceleration');
$optgroup->append_single_option_line('infill_acceleration');
$optgroup->append_single_option_line('bridge_acceleration');
$optgroup->append_single_option_line('first_layer_acceleration');
$optgroup->append_single_option_line('default_acceleration');
}
}
@ -648,6 +593,7 @@ sub build {
my $optgroup = $page->new_optgroup('Extruders');
$optgroup->append_single_option_line('perimeter_extruder');
$optgroup->append_single_option_line('infill_extruder');
$optgroup->append_single_option_line('solid_infill_extruder');
$optgroup->append_single_option_line('support_material_extruder');
$optgroup->append_single_option_line('support_material_interface_extruder');
}
@ -688,6 +634,55 @@ sub build {
$optgroup->append_single_option_line('resolution');
}
}
{
my $page = $self->add_options_page('Output options', 'page_white_go.png');
{
my $optgroup = $page->new_optgroup('Sequential printing');
$optgroup->append_single_option_line('complete_objects');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Extruder clearance (mm)',
);
foreach my $opt_key (qw(extruder_clearance_radius extruder_clearance_height)) {
my $option = $optgroup->get_option($opt_key);
$option->width(60);
$line->append_option($option);
}
$optgroup->append_line($line);
}
{
my $optgroup = $page->new_optgroup('Output file');
$optgroup->append_single_option_line('gcode_comments');
{
my $option = $optgroup->get_option('output_filename_format');
$option->full_width(1);
$optgroup->append_single_option_line($option);
}
}
{
my $optgroup = $page->new_optgroup('Post-processing scripts',
label_width => 0,
);
my $option = $optgroup->get_option('post_process');
$option->full_width(1);
$option->height(50);
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Notes', 'note.png');
{
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
);
my $option = $optgroup->get_option('notes');
$option->full_width(1);
$option->height(250);
$optgroup->append_single_option_line($option);
}
}
}
sub _update {
@ -713,11 +708,28 @@ sub _update {
my $have_perimeters = $config->perimeters > 0;
$self->get_field($_)->toggle($have_perimeters)
for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first);
for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first
external_perimeter_extrusion_width
perimeter_speed small_perimeter_speed external_perimeter_speed);
my $have_infill = $config->fill_density > 0;
$self->get_field($_)->toggle($have_infill)
for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers);
for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers
solid_infill_below_area infill_extruder);
my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0);
$self->get_field($_)->toggle($have_solid_infill)
for qw(external_fill_pattern infill_first solid_infill_extruder solid_infill_extrusion_width
solid_infill_speed);
$self->get_field($_)->toggle($have_infill || $have_solid_infill)
for qw(fill_angle infill_extrusion_width infill_speed bridge_speed);
$self->get_field('gap_fill_speed')->toggle($have_perimeters && $have_infill);
my $have_top_solid_infill = $config->top_solid_layers > 0;
$self->get_field($_)->toggle($have_top_solid_infill)
for qw(top_infill_extrusion_width top_solid_infill_speed);
my $have_default_acceleration = $config->default_acceleration > 0;
$self->get_field($_)->toggle($have_default_acceleration)
@ -727,15 +739,23 @@ sub _update {
$self->get_field($_)->toggle($have_skirt)
for qw(skirt_distance skirt_height);
my $have_brim = $config->brim_width > 0;
$self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim);
my $have_support_material = $config->support_material || $config->raft_layers > 0;
my $have_support_interface = $config->support_material_interface_layers > 0;
$self->get_field($_)->toggle($have_support_material)
for qw(support_material_threshold support_material_enforce_layers
support_material_pattern support_material_spacing support_material_angle
support_material_interface_layers dont_support_bridges
support_material_extruder);
support_material_extrusion_width);
$self->get_field($_)->toggle($have_support_material && $have_support_interface)
for qw(support_material_interface_spacing support_material_interface_extruder);
for qw(support_material_interface_spacing support_material_interface_extruder
support_material_interface_speed);
$self->get_field('perimeter_extrusion_width')->toggle($have_perimeters || $have_skirt || $have_brim);
$self->get_field('support_material_extruder')->toggle($have_support_material || $have_skirt);
$self->get_field('support_material_speed')->toggle($have_support_material || $have_brim || $have_skirt);
my $have_sequential_printing = $config->complete_objects;
$self->get_field($_)->toggle($have_sequential_printing)
@ -918,7 +938,7 @@ sub build {
if ($dlg->ShowModal == wxID_OK) {
my $value = $dlg->GetValue;
$self->{config}->set('bed_shape', $value);
$self->set_dirty(1);
$self->update_dirty;
$self->_on_value_change('bed_shape', $value);
}
});
@ -962,7 +982,7 @@ sub build {
$optgroup->on_change(sub {
my ($opt_id) = @_;
if ($opt_id eq 'extruders_count') {
$self->set_dirty(1);
$self->update_dirty;
$self->_extruders_count_changed($optgroup->get_value('extruders_count'));
}
});
@ -1100,12 +1120,10 @@ sub _update {
my $config = $self->{config};
$self->get_field('toolchange_gcode')->toggle($self->{extruders_count} > 1);
my $have_multiple_extruders = $self->{extruders_count} > 1;
$self->get_field('toolchange_gcode')->toggle($have_multiple_extruders);
for my $i (0 .. ($self->{extruders_count}-1)) {
# disable extruder offset for first extruder
$self->get_field('extruder_offset', $i)->toggle($i != 0);
my $have_retract_length = $config->get_at('retract_length', $i) > 0;
# when using firmware retraction, firmware decides retraction length
@ -1124,9 +1142,11 @@ sub _update {
$self->get_field($_, $i)->toggle($retraction && !$config->use_firmware_retraction)
for qw(retract_speed retract_restart_extra wipe);
$self->get_field('retract_length_toolchange', $i)->toggle($have_multiple_extruders);
my $toolchange_retraction = $config->get_at('retract_length_toolchange', $i) > 0;
$self->get_field($_, $i)->toggle($toolchange_retraction)
for qw(retract_restart_extra_toolchange);
$self->get_field('retract_restart_extra_toolchange', $i)->toggle
($have_multiple_extruders && $toolchange_retraction);
}
}
@ -1183,7 +1203,7 @@ sub new_optgroup {
config => $self->GetParent->{config},
label_width => $params{label_width} // 200,
on_change => sub {
$self->GetParent->set_dirty(1);
$self->GetParent->update_dirty;
$self->GetParent->_on_value_change(@_);
},
);
@ -1230,7 +1250,7 @@ sub new {
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, "Save preset", wxDefaultPosition, wxDefaultSize);
my @values = grep $_ ne '- default -', @{$params{values}};
my @values = @{$params{values}};
my $text = Wx::StaticText->new($self, -1, "Save " . lc($params{title}) . " as:", wxDefaultPosition, wxDefaultSize);
$self->{combo} = Wx::ComboBox->new($self, -1, $params{default}, wxDefaultPosition, wxDefaultSize, \@values,
@ -1270,4 +1290,33 @@ sub get_name {
return $self->{chosen_name};
}
package Slic3r::GUI::Tab::Preset;
use Moo;
has 'default' => (is => 'ro', default => sub { 0 });
has 'external' => (is => 'ro', default => sub { 0 });
has 'name' => (is => 'rw', required => 1);
has 'file' => (is => 'rw');
sub config {
my ($self, $keys) = @_;
if ($self->default) {
return Slic3r::Config->new_from_defaults(@$keys);
} else {
if (!-e $self->file) {
Slic3r::GUI::show_error($self, "The selected preset does not exist anymore (" . $self->file . ").");
return;
}
# apply preset values on top of defaults
my $external_config = Slic3r::Config->load($self->file);
my $config = Slic3r::Config->new;
$config->set($_, $external_config->get($_))
for grep $external_config->has($_), @$keys;
return $config;
}
}
1;

View File

@ -246,7 +246,7 @@ sub make_perimeters {
my ($polynodes, $depth, $is_contour) = @_;
# convert all polynodes to ExtrusionLoop objects
my $collection = Slic3r::ExtrusionPath::Collection->new;
my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection
my @children = ();
foreach my $polynode (@$polynodes) {
my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
@ -303,7 +303,7 @@ sub make_perimeters {
# (clone because the collection gets DESTROY'ed)
# We allow polyline reversal because Clipper may have randomly
# reversed polylines during clipping.
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths);
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection
@paths = map $_->clone, @{$collection->chained_path(0)};
} else {
push @paths, Slic3r::ExtrusionPath->new(
@ -350,16 +350,12 @@ sub make_perimeters {
# use a nearest neighbor search to order these children
# TODO: supply second argument to chained_path() too?
# Optimization: since islands are going to be sorted by slice anyway in the
# G-code export process, we skip chained_path here
my ($sorted_collection, @orig_indices);
if ($is_contour && $depth == 0) {
$sorted_collection = $collection;
@orig_indices = (0..$#$sorted_collection);
} else {
$sorted_collection = $collection->chained_path_indices(0);
@orig_indices = @{$sorted_collection->orig_indices};
}
# (We used to skip this chiained_path() when $is_contour &&
# $depth == 0 because slices are ordered at G_code export
# time, but multiple top-level perimeters might belong to
# the same slice actually, so that was a broken optimization.)
my $sorted_collection = $collection->chained_path_indices(0);
my @orig_indices = @{$sorted_collection->orig_indices};
my @loops = ();
foreach my $loop (@$sorted_collection) {
@ -496,7 +492,8 @@ sub process_external_surfaces {
$angle = $bridge_detector->angle;
if (defined $angle && $self->object->config->support_material) {
$self->bridged->append($_) for @{ $bridge_detector->coverage_with_angle($angle) };
$self->bridged->append(Slic3r::ExPolygon->new($_))
for @{ $bridge_detector->coverage_by_angle($angle) };
$self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges };
}
}

View File

@ -7,11 +7,6 @@ use parent 'Slic3r::Polyline';
use Slic3r::Geometry qw(PI);
sub dump_perl {
my $self = shift;
return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self;
}
sub grow {
my $self = shift;
return $self->split_at_first_point->grow(@_);
@ -37,51 +32,4 @@ sub subdivide {
return Slic3r::Polygon->new(@new_points);
}
sub concave_points {
my ($self, $angle) = @_;
$angle //= PI;
# input angle threshold is checked on the internal side of the polygon
# but angle3points measures CCW angle, so we calculate the complementary angle
my $ccw_angle = 2*PI-$angle;
my @concave = ();
my @points = @$self;
my @points_pp = @{$self->pp};
for my $i (-1 .. ($#points-1)) {
# angle is measured in ccw orientation
my $vertex_angle = Slic3r::Geometry::angle3points(@points_pp[$i, $i-1, $i+1]);
if ($vertex_angle <= $ccw_angle) {
push @concave, $points[$i];
}
}
return [@concave];
}
sub convex_points {
my ($self, $angle) = @_;
$angle //= PI;
# input angle threshold is checked on the internal side of the polygon
# but angle3points measures CCW angle, so we calculate the complementary angle
my $ccw_angle = 2*PI-$angle;
my @convex = ();
my @points = @$self;
my @points_pp = @{$self->pp};
for my $i (-1 .. ($#points-1)) {
# angle is measured in ccw orientation
my $vertex_angle = Slic3r::Geometry::angle3points(@points_pp[$i, $i-1, $i+1]);
if ($vertex_angle >= $ccw_angle) {
push @convex, $points[$i];
}
}
return [@convex];
}
1;

View File

@ -10,4 +10,9 @@ sub new_scale {
return $class->new(map [ Slic3r::Geometry::scale($_->[X]), Slic3r::Geometry::scale($_->[Y]) ], @points);
}
sub dump_perl {
my $self = shift;
return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self;
}
1;

View File

@ -7,8 +7,7 @@ use File::Spec;
use List::Util qw(min max first sum);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale chained_path
convex_hull);
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale convex_hull);
use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection offset
offset2 union union_pt_chained JT_ROUND JT_SQUARE);
use Slic3r::Print::State ':steps';
@ -41,46 +40,6 @@ sub total_layer_count {
return max(map $_->total_layer_count, @{$self->objects});
}
# the bounding box of objects placed in copies position
# (without taking skirt/brim/support material into account)
sub bounding_box {
my $self = shift;
my @points = ();
foreach my $object (@{$self->objects}) {
foreach my $copy (@{$object->_shifted_copies}) {
push @points,
[ $copy->[X], $copy->[Y] ],
[ $copy->[X] + $object->size->[X], $copy->[Y] + $object->size->[Y] ];
}
}
return Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), @points ]);
}
# the total bounding box of extrusions, including skirt/brim/support material
sub total_bounding_box {
my ($self) = @_;
# get objects bounding box
my $bb = $self->bounding_box;
# check how much we need to increase it
my $extra = 0;
if ($self->has_support_material) {
$extra = &Slic3r::Print::SupportMaterial::MARGIN;
}
$extra = max($extra, $self->config->brim_width);
if ($self->config->skirts > 0) {
my $skirt_flow = $self->skirt_flow;
$extra = max($extra, $self->config->brim_width + $self->config->skirt_distance + ($self->config->skirts * $skirt_flow->spacing));
}
if ($extra > 0) {
$bb->offset(scale $extra);
}
return $bb;
}
sub size {
my $self = shift;
return $self->bounding_box->size;
@ -321,9 +280,9 @@ sub make_skirt {
Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point,
role => EXTR_ROLE_SKIRT,
mm3_per_mm => $mm3_per_mm,
mm3_per_mm => $mm3_per_mm, # this will be overridden at G-code export time
width => $flow->width,
height => $first_layer_height,
height => $first_layer_height, # this will be overridden at G-code export time
),
));
@ -373,9 +332,9 @@ sub make_brim {
}
$self->status_cb->(88, "Generating brim");
# brim is only printed on first layer and uses support material extruder
# brim is only printed on first layer and uses perimeter extruder
my $first_layer_height = $self->skirt_first_layer_height;
my $flow = $self->skirt_flow;
my $flow = $self->brim_flow;
my $mm3_per_mm = $flow->mm3_per_mm;
my $grow_distance = $flow->scaled_width / 2;
@ -423,23 +382,6 @@ sub make_brim {
$self->set_step_done(STEP_BRIM);
}
sub skirt_first_layer_height {
my ($self) = @_;
return $self->objects->[0]->config->get_abs_value('first_layer_height');
}
sub skirt_flow {
my ($self) = @_;
return Slic3r::Flow->new_from_width(
width => ($self->config->first_layer_extrusion_width || $self->regions->[0]->config->perimeter_extrusion_width),
role => FLOW_ROLE_PERIMETER,
nozzle_diameter => $self->config->get_at('nozzle_diameter', $self->objects->[0]->config->support_material_extruder-1),
layer_height => $self->skirt_first_layer_height,
bridge_flow_ratio => 0,
);
}
sub write_gcode {
my $self = shift;
my ($file) = @_;
@ -456,243 +398,11 @@ sub write_gcode {
binmode $fh, ':utf8';
}
# write some information
my @lt = localtime;
printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
$lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0];
print $fh "; $_\n" foreach split /\R/, $self->config->notes;
print $fh "\n" if $self->config->notes;
my $first_object = $self->objects->[0];
my $layer_height = $first_object->config->layer_height;
for my $region_id (0..$#{$self->regions}) {
printf $fh "; external perimeters extrusion width = %.2fmm\n",
$self->regions->[$region_id]->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; perimeters extrusion width = %.2fmm\n",
$self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; infill extrusion width = %.2fmm\n",
$self->regions->[$region_id]->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; solid infill extrusion width = %.2fmm\n",
$self->regions->[$region_id]->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; top infill extrusion width = %.2fmm\n",
$self->regions->[$region_id]->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; support material extrusion width = %.2fmm\n",
$self->objects->[0]->support_material_flow->width
if $self->has_support_material;
printf $fh "; first layer extrusion width = %.2fmm\n",
$self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, -1, $self->objects->[0])->width
if $self->regions->[$region_id]->config->first_layer_extrusion_width;
print $fh "\n";
}
# prepare the helper object for replacing placeholders in custom G-code and output filename
$self->placeholder_parser->update_timestamp;
# estimate the total number of layer changes
# TODO: only do this when M73 is enabled
my $layer_count;
if ($self->config->complete_objects) {
$layer_count = sum(map { $_->total_layer_count * @{$_->copies} } @{$self->objects});
} else {
# if sequential printing is not enable, all copies of the same object share the same layer change command(s)
$layer_count = sum(map { $_->total_layer_count } @{$self->objects});
}
# set up our helper object
my $gcodegen = Slic3r::GCode->new(
placeholder_parser => $self->placeholder_parser,
layer_count => $layer_count,
enable_cooling_markers => 1,
);
$gcodegen->apply_print_config($self->config);
$gcodegen->set_extruders($self->extruders);
print $fh $gcodegen->writer->set_fan(0, 1) if $self->config->cooling && $self->config->disable_fan_first_layers;
# set bed temperature
if ((my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) {
printf $fh $gcodegen->writer->set_bed_temperature($temp, 1);
}
# set extruder(s) temperature before and after start G-code
my $print_first_layer_temperature = sub {
my ($wait) = @_;
return if $self->config->start_gcode =~ /M(?:109|104)/i;
for my $t (@{$self->extruders}) {
my $temp = $self->config->get_at('first_layer_temperature', $t);
$temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention;
printf $fh $gcodegen->writer->set_temperature($temp, $wait, $t) if $temp > 0;
}
};
$print_first_layer_temperature->(0);
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode);
$print_first_layer_temperature->(1);
# set other general things
print $fh $gcodegen->preamble;
# initialize a motion planner for object-to-object travel moves
if ($self->config->avoid_crossing_perimeters) {
my $distance_from_objects = 1;
# compute the offsetted convex hull for each object and repeat it for each copy.
my @islands = ();
foreach my $obj_idx (0 .. ($self->object_count - 1)) {
my $convex_hull = convex_hull([
map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
]);
# discard layers only containing thin walls (offset would fail on an empty polygon)
if (@$convex_hull) {
my $expolygon = Slic3r::ExPolygon->new($convex_hull);
my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)};
foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island;
}
}
}
$gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ]));
}
# calculate wiping points if needed
if ($self->config->ooze_prevention) {
my @skirt_points = map @$_, map @$_, @{$self->skirt};
if (@skirt_points) {
my $outer_skirt = convex_hull(\@skirt_points);
my @skirts = ();
foreach my $extruder_id (@{$self->extruders}) {
push @skirts, my $s = $outer_skirt->clone;
$s->translate(map scale($_), @{$self->config->get_at('extruder_offset', $extruder_id)});
}
my $convex_hull = convex_hull([ map @$_, @skirts ]);
$gcodegen->ooze_prevention->enable(1);
$gcodegen->ooze_prevention->standby_points(
[ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ]
);
}
}
# prepare the layer processor
my $layer_gcode = Slic3r::GCode::Layer->new(
my $exporter = Slic3r::Print::GCode->new(
print => $self,
gcodegen => $gcodegen,
fh => $fh,
);
# set initial extruder only after custom start G-code
print $fh $gcodegen->set_extruder($self->extruders->[0]);
# do all objects for each layer
if ($self->config->complete_objects) {
# print objects from the smallest to the tallest to avoid collisions
# when moving onto next object starting point
my @obj_idx = sort { $self->objects->[$a]->size->[Z] <=> $self->objects->[$b]->size->[Z] } 0..($self->object_count - 1);
my $finished_objects = 0;
for my $obj_idx (@obj_idx) {
my $object = $self->objects->[$obj_idx];
for my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
# move to the origin position for the copy we're going to print.
# this happens before Z goes down to layer 0 again, so that
# no collision happens hopefully.
if ($finished_objects > 0) {
$gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
print $fh $gcodegen->retract;
print $fh $gcodegen->travel_to(
$object->_copies_shift->negative,
undef,
'move to origin position for next object',
);
}
my $buffer = Slic3r::GCode::CoolingBuffer->new(
config => $self->config,
gcodegen => $gcodegen,
);
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
for my $layer (@layers) {
# if we are printing the bottom layer of an object, and we have already finished
# another one, set first layer temperatures. this happens before the Z move
# is triggered, so machine has more time to reach such temperatures
if ($layer->id == 0 && $finished_objects > 0) {
printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature),
if $self->config->first_layer_bed_temperature;
$print_first_layer_temperature->(0);
}
print $fh $buffer->append(
$layer_gcode->process_layer($layer, [$copy]),
$layer->object->ptr,
$layer->id,
$layer->print_z,
);
}
print $fh $buffer->flush;
$finished_objects++;
}
}
} else {
# order objects using a nearest neighbor search
my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])};
# sort layers by Z
my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx
foreach my $obj_idx (0 .. ($self->object_count - 1)) {
my $object = $self->objects->[$obj_idx];
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
$layers{ $layer->print_z } ||= [];
$layers{ $layer->print_z }[$obj_idx] ||= [];
push @{$layers{ $layer->print_z }[$obj_idx]}, $layer;
}
}
my $buffer = Slic3r::GCode::CoolingBuffer->new(
config => $self->config,
gcodegen => $gcodegen,
);
foreach my $print_z (sort { $a <=> $b } keys %layers) {
foreach my $obj_idx (@obj_idx) {
foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) {
print $fh $buffer->append(
$layer_gcode->process_layer($layer, $layer->object->_shifted_copies),
$layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers
$layer->id,
$layer->print_z,
);
}
}
}
print $fh $buffer->flush;
}
# write end commands to file
print $fh $gcodegen->retract; # TODO: process this retract through PressureRegulator in order to discharge fully
print $fh $gcodegen->writer->set_fan(0);
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode);
print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100%
$self->total_used_filament(0);
$self->total_extruded_volume(0);
foreach my $extruder (@{$gcodegen->writer->extruders}) {
my $used_filament = $extruder->used_filament;
my $extruded_volume = $extruder->extruded_volume;
printf $fh "; filament used = %.1fmm (%.1fcm3)\n",
$used_filament, $extruded_volume/1000;
$self->total_used_filament($self->total_used_filament + $used_filament);
$self->total_extruded_volume($self->total_extruded_volume + $extruded_volume);
}
# append full config
print $fh "\n";
foreach my $config ($self->config, $self->default_object_config, $self->default_region_config) {
foreach my $opt_key (sort @{$config->get_keys}) {
next if $Slic3r::Config::Options->{$opt_key}{shortcut};
printf $fh "; %s = %s\n", $opt_key, $config->serialize($opt_key);
}
}
$exporter->export;
# close our gcode file
close $fh;

547
lib/Slic3r/Print/GCode.pm Normal file
View File

@ -0,0 +1,547 @@
package Slic3r::Print::GCode;
use Moo;
has 'print' => (is => 'ro', required => 1, handles => [qw(objects placeholder_parser config)]);
has 'fh' => (is => 'ro', required => 1);
has '_gcodegen' => (is => 'rw');
has '_cooling_buffer' => (is => 'rw');
has '_spiral_vase' => (is => 'rw');
has '_vibration_limit' => (is => 'rw');
has '_arc_fitting' => (is => 'rw');
has '_pressure_regulator' => (is => 'rw');
has '_skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1
has '_brim_done' => (is => 'rw');
has '_second_layer_things_done' => (is => 'rw');
has '_last_obj_copy' => (is => 'rw');
use List::Util qw(first sum);
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull);
use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset);
sub BUILD {
my ($self) = @_;
{
# estimate the total number of layer changes
# TODO: only do this when M73 is enabled
my $layer_count;
if ($self->config->complete_objects) {
$layer_count = sum(map { $_->total_layer_count * @{$_->copies} } @{$self->objects});
} else {
# if sequential printing is not enable, all copies of the same object share the same layer change command(s)
$layer_count = sum(map { $_->total_layer_count } @{$self->objects});
}
# set up our helper object
my $gcodegen = Slic3r::GCode->new(
placeholder_parser => $self->placeholder_parser,
layer_count => $layer_count,
enable_cooling_markers => 1,
);
$gcodegen->apply_print_config($self->config);
$gcodegen->set_extruders($self->print->extruders);
$self->_gcodegen($gcodegen);
}
$self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new(
config => $self->config,
gcodegen => $self->_gcodegen,
));
$self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config))
if $self->config->spiral_vase;
$self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config))
if $self->config->vibration_limit > 0;
$self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config))
if $self->config->gcode_arcs;
$self->_pressure_regulator(Slic3r::GCode::PressureRegulator->new(config => $self->config))
if $self->config->pressure_advance > 0;
}
sub export {
my ($self) = @_;
my $fh = $self->fh;
my $gcodegen = $self->_gcodegen;
# write some information
my @lt = localtime;
printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
$lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0];
print $fh "; $_\n" foreach split /\R/, $self->config->notes;
print $fh "\n" if $self->config->notes;
my $first_object = $self->objects->[0];
my $layer_height = $first_object->config->layer_height;
for my $region_id (0..$#{$self->print->regions}) {
my $region = $self->print->regions->[$region_id];
printf $fh "; external perimeters extrusion width = %.2fmm\n",
$region->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; perimeters extrusion width = %.2fmm\n",
$region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; infill extrusion width = %.2fmm\n",
$region->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; solid infill extrusion width = %.2fmm\n",
$region->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; top infill extrusion width = %.2fmm\n",
$region->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
printf $fh "; support material extrusion width = %.2fmm\n",
$self->objects->[0]->support_material_flow->width
if $self->print->has_support_material;
printf $fh "; first layer extrusion width = %.2fmm\n",
$region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, -1, $self->objects->[0])->width
if $region->config->first_layer_extrusion_width;
print $fh "\n";
}
# prepare the helper object for replacing placeholders in custom G-code and output filename
$self->placeholder_parser->update_timestamp;
print $fh $gcodegen->writer->set_fan(0, 1)
if $self->config->cooling && $self->config->disable_fan_first_layers;
# set bed temperature
if ((my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) {
printf $fh $gcodegen->writer->set_bed_temperature($temp, 1);
}
# set extruder(s) temperature before and after start G-code
$self->_print_first_layer_temperature(0);
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode);
$self->_print_first_layer_temperature(1);
# set other general things
print $fh $gcodegen->preamble;
# initialize a motion planner for object-to-object travel moves
if ($self->config->avoid_crossing_perimeters) {
my $distance_from_objects = 1;
# compute the offsetted convex hull for each object and repeat it for each copy.
my @islands = ();
foreach my $obj_idx (0 .. ($self->print->object_count - 1)) {
my $convex_hull = convex_hull([
map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
]);
# discard layers only containing thin walls (offset would fail on an empty polygon)
if (@$convex_hull) {
my $expolygon = Slic3r::ExPolygon->new($convex_hull);
my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)};
foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island;
}
}
}
$gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ]));
}
# calculate wiping points if needed
if ($self->config->ooze_prevention) {
my @skirt_points = map @$_, map @$_, @{$self->print->skirt};
if (@skirt_points) {
my $outer_skirt = convex_hull(\@skirt_points);
my @skirts = ();
foreach my $extruder_id (@{$self->print->extruders}) {
push @skirts, my $s = $outer_skirt->clone;
$s->translate(map scale($_), @{$self->config->get_at('extruder_offset', $extruder_id)});
}
my $convex_hull = convex_hull([ map @$_, @skirts ]);
$gcodegen->ooze_prevention->enable(1);
$gcodegen->ooze_prevention->standby_points(
[ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ]
);
}
}
# set initial extruder only after custom start G-code
print $fh $gcodegen->set_extruder($self->print->extruders->[0]);
# do all objects for each layer
if ($self->config->complete_objects) {
# print objects from the smallest to the tallest to avoid collisions
# when moving onto next object starting point
my @obj_idx = sort { $self->objects->[$a]->size->z <=> $self->objects->[$b]->size->z } 0..($self->print->object_count - 1);
my $finished_objects = 0;
for my $obj_idx (@obj_idx) {
my $object = $self->objects->[$obj_idx];
for my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
# move to the origin position for the copy we're going to print.
# this happens before Z goes down to layer 0 again, so that
# no collision happens hopefully.
if ($finished_objects > 0) {
$gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
print $fh $gcodegen->retract;
print $fh $gcodegen->travel_to(
$object->_copies_shift->negative,
undef,
'move to origin position for next object',
);
}
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
for my $layer (@layers) {
# if we are printing the bottom layer of an object, and we have already finished
# another one, set first layer temperatures. this happens before the Z move
# is triggered, so machine has more time to reach such temperatures
if ($layer->id == 0 && $finished_objects > 0) {
printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature),
if $self->config->first_layer_bed_temperature;
$self->_print_first_layer_temperature(0);
}
$self->process_layer($layer, [$copy]);
}
$self->flush_cooling_buffer;
$finished_objects++;
}
}
} else {
# order objects using a nearest neighbor search
my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])};
# sort layers by Z
my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx
foreach my $obj_idx (0 .. ($self->print->object_count - 1)) {
my $object = $self->objects->[$obj_idx];
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
$layers{ $layer->print_z } ||= [];
$layers{ $layer->print_z }[$obj_idx] ||= [];
push @{$layers{ $layer->print_z }[$obj_idx]}, $layer;
}
}
foreach my $print_z (sort { $a <=> $b } keys %layers) {
foreach my $obj_idx (@obj_idx) {
foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) {
$self->process_layer($layer, $layer->object->_shifted_copies);
}
}
}
$self->flush_cooling_buffer;
}
# write end commands to file
print $fh $gcodegen->retract; # TODO: process this retract through PressureRegulator in order to discharge fully
print $fh $gcodegen->writer->set_fan(0);
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode);
print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100%
$self->print->total_used_filament(0);
$self->print->total_extruded_volume(0);
foreach my $extruder (@{$gcodegen->writer->extruders}) {
my $used_filament = $extruder->used_filament;
my $extruded_volume = $extruder->extruded_volume;
printf $fh "; filament used = %.1fmm (%.1fcm3)\n",
$used_filament, $extruded_volume/1000;
$self->print->total_used_filament($self->print->total_used_filament + $used_filament);
$self->print->total_extruded_volume($self->print->total_extruded_volume + $extruded_volume);
}
# append full config
print $fh "\n";
foreach my $config ($self->print->config, $self->print->default_object_config, $self->print->default_region_config) {
foreach my $opt_key (sort @{$config->get_keys}) {
next if $Slic3r::Config::Options->{$opt_key}{shortcut};
printf $fh "; %s = %s\n", $opt_key, $config->serialize($opt_key);
}
}
}
sub _print_first_layer_temperature {
my ($self, $wait) = @_;
return if $self->config->start_gcode =~ /M(?:109|104)/i;
for my $t (@{$self->print->extruders}) {
my $temp = $self->config->get_at('first_layer_temperature', $t);
$temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention;
printf {$self->fh} $self->_gcodegen->writer->set_temperature($temp, $wait, $t) if $temp > 0;
}
}
sub process_layer {
my $self = shift;
my ($layer, $object_copies) = @_;
my $gcode = "";
my $object = $layer->object;
$self->_gcodegen->config->apply_object_config($object->config);
# check whether we're going to apply spiralvase logic
if (defined $self->_spiral_vase) {
$self->_spiral_vase->enable(
($layer->id > 0 || $self->print->config->brim_width == 0)
&& ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1)
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
&& !defined(first { @{$_->perimeters} > 1 } @{$layer->regions})
&& !defined(first { @{$_->fills} > 0 } @{$layer->regions})
);
}
# if we're going to apply spiralvase to this layer, disable loop clipping
$self->_gcodegen->enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable);
if (!$self->_second_layer_things_done && $layer->id == 1) {
for my $extruder (@{$self->_gcodegen->writer->extruders}) {
my $temperature = $self->config->get_at('temperature', $extruder->id);
$gcode .= $self->_gcodegen->writer->set_temperature($temperature, 0, $extruder->id)
if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id);
}
$gcode .= $self->_gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature)
if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature;
$self->_second_layer_things_done(1);
}
# set new layer - this will change Z and force a retraction if retract_layer_change is enabled
$gcode .= $self->_gcodegen->change_layer($layer);
$gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, {
layer_num => $layer->id,
}) . "\n" if $self->print->config->layer_gcode;
# extrude skirt
if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1)
&& !$self->_skirt_done->{$layer->print_z}) {
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders};
$gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]);
# skip skirt if we have a large brim
if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) {
my $skirt_flow = $self->print->skirt_flow;
# distribute skirt loops across all extruders
my @skirt_loops = @{$self->print->skirt};
for my $i (0 .. $#skirt_loops) {
# when printing layers > 0 ignore 'min_skirt_length' and
# just use the 'skirts' setting; also just use the current extruder
last if ($layer->id > 0) && ($i >= $self->print->config->skirts);
my $extruder_id = $extruder_ids[($i/@extruder_ids) % @extruder_ids];
$gcode .= $self->_gcodegen->set_extruder($extruder_id)
if $layer->id == 0;
# adjust flow according to this layer's layer height
my $loop = $skirt_loops[$i]->clone;
{
my $layer_skirt_flow = $skirt_flow->clone;
$layer_skirt_flow->set_height($layer->height);
my $mm3_per_mm = $layer_skirt_flow->mm3_per_mm;
foreach my $path (@$loop) {
$path->height($layer->height);
$path->mm3_per_mm($mm3_per_mm);
}
}
$gcode .= $self->_gcodegen->extrude_loop($loop, 'skirt', $object->config->support_material_speed);
}
}
$self->_skirt_done->{$layer->print_z} = 1;
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
}
# extrude brim
if (!$self->_brim_done) {
$gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1);
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
$gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed)
for @{$self->print->brim};
$self->_brim_done(1);
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
$self->_gcodegen->avoid_crossing_perimeters->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->_last_obj_copy("$copy");
$self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
# extrude support material before other things because it might use a lower Z
# and also because we avoid travelling on other things when printing it
if ($layer->isa('Slic3r::Layer::Support')) {
if ($layer->support_interface_fills->count > 0) {
$gcode .= $self->_gcodegen->set_extruder($object->config->support_material_interface_extruder-1);
$gcode .= $self->_gcodegen->extrude_path($_, 'support material interface', $object->config->get_abs_value('support_material_interface_speed'))
for @{$layer->support_interface_fills->chained_path_from($self->_gcodegen->last_pos, 0)};
}
if ($layer->support_fills->count > 0) {
$gcode .= $self->_gcodegen->set_extruder($object->config->support_material_extruder-1);
$gcode .= $self->_gcodegen->extrude_path($_, 'support material', $object->config->get_abs_value('support_material_speed'))
for @{$layer->support_fills->chained_path_from($self->_gcodegen->last_pos, 0)};
}
}
# We now define a strategy for building perimeters and fills. The separation
# between regions doesn't matter in terms of printing order, as we follow
# another logic instead:
# - we group all extrusions by extruder so that we minimize toolchanges
# - we start from the last used extruder
# - for each extruder, we group extrusions by island
# - for each island, we extrude perimeters first, unless user set the infill_first
# option
# group extrusions by extruder and then by island
my %by_extruder = (); # extruder_id => [ { perimeters => \@perimeters, infill => \@infill } ]
foreach my $region_id (0..($self->print->region_count-1)) {
my $layerm = $layer->regions->[$region_id] or next;
my $region = $self->print->get_region($region_id);
# process perimeters
{
my $extruder_id = $region->config->perimeter_extruder-1;
foreach my $perimeter (@{$layerm->perimeters}) {
# init by_extruder item only if we actually use the extruder
$by_extruder{$extruder_id} //= [];
# $perimeter is an ExtrusionLoop or ExtrusionPath object
for my $i (0 .. $#{$layer->slices}) {
if ($i == $#{$layer->slices}
|| $layer->slices->[$i]->contour->contains_point($perimeter->first_point)) {
$by_extruder{$extruder_id}[$i] //= { perimeters => {} };
$by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= [];
push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, $perimeter;
last;
}
}
}
}
# process infill
# $layerm->fills is a collection of ExtrusionPath::Collection objects, each one containing
# the ExtrusionPath objects of a certain infill "group" (also called "surface"
# throughout the code). We can redefine the order of such Collections but we have to
# do each one completely at once.
foreach my $fill (@{$layerm->fills}) {
# init by_extruder item only if we actually use the extruder
my $extruder_id = $fill->[0]->is_solid_infill
? $region->config->solid_infill_extruder-1
: $region->config->infill_extruder-1;
$by_extruder{$extruder_id} //= [];
# $fill is an ExtrusionPath::Collection object
for my $i (0 .. $#{$layer->slices}) {
if ($i == $#{$layer->slices}
|| $layer->slices->[$i]->contour->contains_point($fill->first_point)) {
$by_extruder{$extruder_id}[$i] //= { infill => {} };
$by_extruder{$extruder_id}[$i]{infill}{$region_id} //= [];
push @{ $by_extruder{$extruder_id}[$i]{infill}{$region_id} }, $fill;
last;
}
}
}
}
# tweak extruder ordering to save toolchanges
my @extruders = sort keys %by_extruder;
if (@extruders > 1) {
my $last_extruder_id = $self->_gcodegen->writer->extruder->id;
if (exists $by_extruder{$last_extruder_id}) {
@extruders = (
$last_extruder_id,
grep $_ != $last_extruder_id, @extruders,
);
}
}
foreach my $extruder_id (@extruders) {
$gcode .= $self->_gcodegen->set_extruder($extruder_id);
foreach my $island (@{ $by_extruder{$extruder_id} }) {
if ($self->print->config->infill_first) {
$gcode .= $self->_extrude_infill($island->{infill} // {});
$gcode .= $self->_extrude_perimeters($island->{perimeters} // {});
} else {
$gcode .= $self->_extrude_perimeters($island->{perimeters} // {});
$gcode .= $self->_extrude_infill($island->{infill} // {});
}
}
}
}
# apply spiral vase post-processing if this layer contains suitable geometry
# (we must feed all the G-code into the post-processor, including the first
# bottom non-spiral layers otherwise it will mess with positions)
# we apply spiral vase at this stage because it requires a full layer
$gcode = $self->_spiral_vase->process_layer($gcode)
if defined $self->_spiral_vase;
# apply cooling logic; this may alter speeds
$gcode = $self->_cooling_buffer->append(
$gcode,
$layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers
$layer->id,
$layer->print_z,
) if defined $self->_cooling_buffer;
print {$self->fh} $self->filter($gcode);
}
sub _extrude_perimeters {
my ($self, $entities_by_region) = @_;
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')
for @{ $entities_by_region->{$region_id} };
}
return $gcode;
}
sub _extrude_infill {
my ($self, $entities_by_region) = @_;
my $gcode = "";
foreach my $region_id (sort keys %$entities_by_region) {
$self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config);
my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} });
for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) {
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
$gcode .= $self->_gcodegen->extrude($_, 'infill')
for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)};
} else {
$gcode .= $self->_gcodegen->extrude($fill, 'infill') ;
}
}
}
return $gcode;
}
sub flush_cooling_buffer {
my ($self) = @_;
print {$self->fh} $self->filter($self->_cooling_buffer->flush);
}
sub filter {
my ($self, $gcode) = @_;
# apply vibration limit if enabled;
# this injects pauses according to time (thus depends on actual speeds)
$gcode = $self->_vibration_limit->process($gcode)
if $self->print->config->vibration_limit != 0;
# apply pressure regulation if enabled;
# this depends on actual speeds
$gcode = $self->_pressure_regulator->process($gcode)
if $self->print->config->pressure_advance > 0;
# apply arc fitting if enabled;
# this does not depend on speeds but changes G1 XY commands into G2/G2 IJ
$gcode = $self->_arc_fitting->process($gcode)
if $self->print->config->gcode_arcs;
return $gcode;
}
1;

View File

@ -32,24 +32,6 @@ sub support_layers {
return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ];
}
# this is the *total* layer count (including support layers)
# this value is not supposed to be compared with $layer->id
# since they have different semantics
sub total_layer_count {
my $self = shift;
return $self->layer_count + $self->support_layer_count;
}
sub bounding_box {
my $self = shift;
# since the object is aligned to origin, bounding box coincides with size
return Slic3r::Geometry::BoundingBox->new_from_points([
Slic3r::Point->new(0,0),
map Slic3r::Point->new($_->x, $_->y), $self->size #))
]);
}
# this should be idempotent
sub slice {
my $self = shift;
@ -350,7 +332,7 @@ sub _slice_region {
# consider the first one
$self->model_object->instances->[0]->transform_mesh($mesh, 1);
# align mesh to Z = 0 and apply XY shift
# align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
$mesh->translate((map unscale(-$_), @{$self->_copies_shift}), -$self->model_object->bounding_box->z_min);
# perform actual slicing
@ -444,7 +426,6 @@ sub make_perimeters {
$self->get_layer($i)->make_perimeters;
}
},
collect_cb => sub {},
no_threads_cb => sub {
$_->make_perimeters for @{$self->layers};
},
@ -524,7 +505,6 @@ sub infill {
$layerm->fills->append($_) for $self->fill_maker->make_fill($layerm);
}
},
collect_cb => sub {},
no_threads_cb => sub {
foreach my $layerm (map @{$_->regions}, @{$self->layers}) {
$layerm->fills->clear;
@ -696,7 +676,8 @@ sub detect_surfaces_type {
# Note: this method should be idempotent, but fill_surfaces gets modified
# in place. However we're now only using its boundaries (which are invariant)
# so we're safe
# so we're safe. This guarantees idempotence of prepare_infill() also in case
# that combine_infill() turns some fill_surface into VOID surfaces.
my $fill_boundaries = [ map $_->clone->p, @{$layerm->fill_surfaces} ];
$layerm->fill_surfaces->clear;
foreach my $surface (@{$layerm->slices}) {
@ -712,6 +693,8 @@ sub detect_surfaces_type {
}
}
# Idempotence of this method is guaranteed by the fact that we don't remove things from
# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
sub clip_fill_surfaces {
my $self = shift;
return unless $self->config->infill_only_where_needed;
@ -729,13 +712,13 @@ sub clip_fill_surfaces {
# clip this layer's internal surfaces to @overhangs
foreach my $layerm (@{$layer->regions}) {
# we assume that this step is run before bridge_over_infill() and combine_infill()
# so these are the only internal types we might have
my (@internal, @other) = ();
foreach my $surface (map $_->clone, @{$layerm->fill_surfaces}) {
$surface->surface_type == S_TYPE_INTERNAL
? push @internal, $surface
: push @other, $surface;
if ($surface->surface_type == S_TYPE_INTERNAL) {
push @internal, $surface;
} else {
push @other, $surface;
}
}
# keep all the original internal surfaces to detect overhangs in this layer
@ -746,8 +729,17 @@ sub clip_fill_surfaces {
surface_type => S_TYPE_INTERNAL,
),
@{intersection_ex(
$overhangs,
[ map $_->p, @internal ],
$overhangs,
)};
push @new, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALVOID,
),
@{diff_ex(
[ map $_->p, @internal ],
$overhangs,
)};
$layerm->fill_surfaces->clear;
@ -768,82 +760,6 @@ sub clip_fill_surfaces {
}
}
sub bridge_over_infill {
my $self = shift;
for my $region_id (0..($self->print->region_count - 1)) {
my $fill_density = $self->print->regions->[$region_id]->config->fill_density;
next if $fill_density == 100 || $fill_density == 0;
for my $layer_id (1..($self->layer_count - 1)) {
my $layer = $self->get_layer($layer_id);
my $layerm = $layer->regions->[$region_id];
my $lower_layer = $self->get_layer($layer_id-1);
# compute the areas needing bridge math
my @internal_solid = @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNALSOLID)};
my @lower_internal = map @{$_->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)}, @{$lower_layer->regions};
my $to_bridge = intersection_ex(
[ map $_->p, @internal_solid ],
[ map $_->p, @lower_internal ],
);
next unless @$to_bridge;
Slic3r::debugf "Bridging %d internal areas at layer %d\n", scalar(@$to_bridge), $layer_id;
# build the new collection of fill_surfaces
{
my @new_surfaces = map $_->clone, grep $_->surface_type != S_TYPE_INTERNALSOLID, @{$layerm->fill_surfaces};
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALBRIDGE,
), @$to_bridge;
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALSOLID,
), @{diff_ex(
[ map $_->p, @internal_solid ],
[ map @$_, @$to_bridge ],
1,
)};
$layerm->fill_surfaces->clear;
$layerm->fill_surfaces->append($_) for @new_surfaces;
}
# exclude infill from the layers below if needed
# see discussion at https://github.com/alexrj/Slic3r/issues/240
# Update: do not exclude any infill. Sparse infill is able to absorb the excess material.
if (0) {
my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height;
for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) {
Slic3r::debugf " skipping infill below those areas at layer %d\n", $i;
foreach my $lower_layerm (@{$self->get_layer($i)->regions}) {
my @new_surfaces = ();
# subtract the area from all types of surfaces
foreach my $group (@{$lower_layerm->fill_surfaces->group}) {
push @new_surfaces, map $group->[0]->clone(expolygon => $_),
@{diff_ex(
[ map $_->p, @$group ],
[ map @$_, @$to_bridge ],
)};
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALVOID,
), @{intersection_ex(
[ map $_->p, @$group ],
[ map @$_, @$to_bridge ],
)};
}
$lower_layerm->fill_surfaces->clear;
$lower_layerm->fill_surfaces->append($_) for @new_surfaces;
}
$excess -= $self->get_layer($i)->height;
}
}
}
}
}
sub process_external_surfaces {
my ($self) = @_;
@ -1006,42 +922,60 @@ sub discover_horizontal_shells {
}
# combine fill surfaces across layers
# Idempotence of this method is guaranteed by the fact that we don't remove things from
# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
sub combine_infill {
my $self = shift;
return unless defined first { $_->config->infill_every_layers > 1 && $_->config->fill_density > 0 } @{$self->print->regions};
my @layer_heights = map $_->height, @{$self->layers};
# define the type used for voids
my %voidtype = (
&S_TYPE_INTERNAL() => S_TYPE_INTERNALVOID,
);
# work on each region separately
for my $region_id (0 .. ($self->print->region_count-1)) {
my $region = $self->print->regions->[$region_id];
my $region = $self->print->get_region($region_id);
my $every = $region->config->infill_every_layers;
next unless $every > 1 && $region->config->fill_density > 0;
# limit the number of combined layers to the maximum height allowed by this regions' nozzle
my $nozzle_diameter = $self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1);
my $nozzle_diameter = min(
$self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1),
$self->print->config->get_at('nozzle_diameter', $region->config->solid_infill_extruder-1),
);
# define the combinations
my @combine = (); # layer_id => thickness in layers
my %combine = (); # layer_idx => number of additional combined lower layers
{
my $current_height = my $layers = 0;
for my $layer_id (1 .. $#layer_heights) {
my $height = $self->get_layer($layer_id)->height;
for my $layer_idx (0 .. ($self->layer_count-1)) {
my $layer = $self->get_layer($layer_idx);
next if $layer->id == 0; # skip first print layer (which may not be first layer in array because of raft)
my $height = $layer->height;
# check whether the combination of this layer with the lower layers' buffer
# would exceed max layer height or max combined layer count
if ($current_height + $height >= $nozzle_diameter || $layers >= $every) {
$combine[$layer_id-1] = $layers;
# append combination to lower layer
$combine{$layer_idx-1} = $layers;
$current_height = $layers = 0;
}
$current_height += $height;
$layers++;
}
# append lower layers (if any) to uppermost layer
$combine{$self->layer_count-1} = $layers;
}
# skip bottom layer
for my $layer_id (1 .. $#combine) {
next unless ($combine[$layer_id] // 1) > 1;
my @layerms = map $self->get_layer($_)->regions->[$region_id],
($layer_id - ($combine[$layer_id]-1) .. $layer_id);
# loop through layers to which we have assigned layers to combine
for my $layer_idx (sort keys %combine) {
next unless $combine{$layer_idx} > 1;
# get all the LayerRegion objects to be combined
my @layerms = map $self->get_layer($_)->get_region($region_id),
($layer_idx - ($combine{$layer_idx}-1) .. $layer_idx);
# only combine internal infill
for my $type (S_TYPE_INTERNAL) {
@ -1064,7 +998,7 @@ sub combine_infill {
Slic3r::debugf " combining %d %s regions from layers %d-%d\n",
scalar(@$intersection),
($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'),
$layer_id-($every-1), $layer_id;
$layer_idx-($every-1), $layer_idx;
# $intersection now contains the regions that can be combined across the full amount of layers
# so let's remove those areas from all layers
@ -1091,7 +1025,7 @@ sub combine_infill {
)};
# apply surfaces back with adjusted depth to the uppermost layer
if ($layerm->id == $layer_id) {
if ($layerm->id == $self->get_layer($layer_idx)->id) {
push @new_this_type,
map Slic3r::Surface->new(
expolygon => $_,
@ -1102,8 +1036,8 @@ sub combine_infill {
@$intersection;
} else {
# save void surfaces
push @this_type,
map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNALVOID),
push @new_this_type,
map Slic3r::Surface->new(expolygon => $_, surface_type => $voidtype{$type}),
@{intersection_ex(
[ map @{$_->expolygon}, @this_type ],
[ @intersection_with_clearance ],

View File

@ -68,6 +68,7 @@ sub set_model {
# if all input objects have defined position(s) apply duplication to the whole model
$model->duplicate($self->duplicate, $self->_print->config->min_object_distance);
}
$_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects};
$model->center_instances_around_point($self->print_center);
foreach my $model_object (@{$model->objects}) {

View File

@ -17,9 +17,6 @@ has 'interface_flow' => (is => 'rw', required => 1);
use constant DEBUG_CONTACT_ONLY => 0;
# how much we extend support around the actual contact area
use constant MARGIN => 1.5;
# increment used to reach MARGIN in steps to avoid trespassing thin objects
use constant MARGIN_STEP => MARGIN/3;
@ -69,6 +66,10 @@ sub generate {
$self->clip_with_object($base, $support_z, $object);
$self->clip_with_shape($base, $shape) if @$shape;
# Detect what part of base support layers are "reverse interfaces" because they
# lie above object's top surfaces.
$self->generate_bottom_interface_layers($support_z, $base, $top, $interface);
# Install support layers into object.
for my $i (0 .. $#$support_z) {
$object->add_support_layer(
@ -265,7 +266,7 @@ sub contact_area {
{
# get the average nozzle diameter used on this layer
my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_),
map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1 }
map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1, $_->config->solid_infill_extruder-1 }
@{$layer->regions};
my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
@ -338,7 +339,12 @@ sub support_layers_z {
# layer_height > nozzle_diameter * 0.75
my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->object_config->support_material_extruder-1);
my $support_material_height = max($max_object_layer_height, $nozzle_diameter * 0.75);
my @z = sort { $a <=> $b } @$contact_z, @$top_z, (map $_ + $nozzle_diameter, @$top_z);
# initialize known, fixed, support layers
my @z = sort { $a <=> $b }
@$contact_z,
@$top_z, # TODO: why we have this?
(map $_ + contact_distance($nozzle_diameter), @$top_z);
# enforce first layer height
my $first_layer_height = $self->object_config->get_value('first_layer_height');
@ -418,6 +424,48 @@ sub generate_interface_layers {
return \%interface;
}
sub generate_bottom_interface_layers {
my ($self, $support_z, $base, $top, $interface) = @_;
my $area_threshold = $self->interface_flow->scaled_spacing ** 2;
# loop through object's top surfaces
foreach my $top_z (sort keys %$top) {
my $this = $top->{$top_z};
# keep a count of the interface layers we generated for this top surface
my $interface_layers = 0;
# loop through support layers until we find the one(s) right above the top
# surface
foreach my $layer_id (0 .. $#$support_z) {
my $z = $support_z->[$layer_id];
next unless $z > $top_z;
# get the support material area that should be considered interface
my $interface_area = intersection(
$base->{$layer_id},
$this,
);
# discard too small areas
$interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
# subtract new interface area from base
$base->{$layer_id} = diff(
$base->{$layer_id},
$interface_area,
);
# add new interface area to interface
push @{$interface->{$layer_id}}, @$interface_area;
$interface_layers++;
last if $interface_layers == $self->object_config->support_material_interface_layers;
}
}
}
sub generate_base_layers {
my ($self, $support_z, $contact, $interface, $top) = @_;
@ -623,6 +671,7 @@ sub generate_toolpaths {
# interface and contact infill
if (@$interface || @$contact_infill) {
$fillers{interface}->angle($interface_angle);
$fillers{interface}->spacing($_interface_flow->spacing);
# find centerline of the external loop
$interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $_interface_flow->scaled_width/2));
@ -648,20 +697,19 @@ sub generate_toolpaths {
my @paths = ();
foreach my $expolygon (@{union_ex($interface)}) {
my ($params, @p) = $fillers{interface}->fill_surface(
my @p = $fillers{interface}->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
density => $interface_density,
flow => $_interface_flow,
layer_height => $layer->height,
complete => 1,
);
my $mm3_per_mm = $params->{flow}->mm3_per_mm;
my $mm3_per_mm = $_interface_flow->mm3_per_mm;
push @paths, map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE,
mm3_per_mm => $mm3_per_mm,
width => $params->{flow}->width,
width => $_interface_flow->width,
height => $layer->height,
), @p;
}
@ -702,22 +750,22 @@ sub generate_toolpaths {
# TODO: use offset2_ex()
$to_infill = offset_ex([ map @$_, @$to_infill ], -$_flow->scaled_spacing);
}
$filler->spacing($base_flow->spacing);
foreach my $expolygon (@$to_infill) {
my ($params, @p) = $filler->fill_surface(
my @p = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
density => $density,
flow => $base_flow,
layer_height => $layer->height,
complete => 1,
);
my $mm3_per_mm = $params->{flow}->mm3_per_mm;
my $mm3_per_mm = $base_flow->mm3_per_mm;
push @paths, map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL,
mm3_per_mm => $mm3_per_mm,
width => $params->{flow}->width,
width => $base_flow->width,
height => $layer->height,
), @p;
}

View File

@ -364,7 +364,7 @@ $j
--fill-density Infill density (range: 0%-100%, default: $config->{fill_density}%)
--fill-angle Infill angle in degrees (range: 0-90, default: $config->{fill_angle})
--fill-pattern Pattern to use to fill non-solid layers (default: $config->{fill_pattern})
--solid-fill-pattern Pattern to use to fill solid layers (default: $config->{solid_fill_pattern})
--external-fill-pattern Pattern to use to fill solid layers (default: $config->{external_fill_pattern})
--start-gcode Load initial G-code from the supplied file. This will overwrite
the default command (home all axes [G28]).
--end-gcode Load final G-code from the supplied file. This will overwrite
@ -503,10 +503,11 @@ $j
--extruder-offset Offset of each extruder, if firmware doesn't handle the displacement
(can be specified multiple times, default: 0x0)
--perimeter-extruder
Extruder to use for perimeters (1+, default: $config->{perimeter_extruder})
Extruder to use for perimeters and brim (1+, default: $config->{perimeter_extruder})
--infill-extruder Extruder to use for infill (1+, default: $config->{infill_extruder})
--solid-infill-extruder Extruder to use for solid infill (1+, default: $config->{solid_infill_extruder})
--support-material-extruder
Extruder to use for support material (1+, default: $config->{support_material_extruder})
Extruder to use for support material, raft and skirt (1+, default: $config->{support_material_extruder})
--support-material-interface-extruder
Extruder to use for support material interface (1+, default: $config->{support_material_interface_extruder})
--ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping

View File

@ -0,0 +1,21 @@
use Test::More tests => 1;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use List::Util qw(first sum);
use Slic3r;
use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('avoid_crossing_perimeters', 2);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
ok my $gcode = Slic3r::Test::gcode($print), "no crash with avoid_crossing_perimeters and multiple objects";
}
__END__

View File

@ -9,9 +9,67 @@ BEGIN {
use List::Util qw(first);
use Slic3r;
use Slic3r::Surface ':types';
use Slic3r::Test;
plan tests => 2;
plan tests => 8;
{
my $test = sub {
my ($config) = @_;
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash";
my $tool = undef;
my %layers = (); # layer_z => 1
my %layer_infill = (); # layer_z => has_infill
Slic3r::GCode::Reader->new->parse($gcode, sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0 && $tool != $config->support_material_extruder-1) {
$layer_infill{$self->Z} //= 0;
if ($tool == $config->infill_extruder-1) {
$layer_infill{$self->Z} = 1;
}
}
$layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} > 0;
});
my $layers_with_perimeters = scalar(keys %layer_infill);
my $layers_with_infill = grep $_ > 0, values %layer_infill;
is scalar(keys %layers), $layers_with_perimeters+$config->raft_layers, 'expected number of layers';
# first infill layer is never combined, so we don't consider it
$layers_with_infill--;
$layers_with_perimeters--;
# we expect that infill is generated for half the number of combined layers
# plus for each single layer that was not combined (remainder)
is $layers_with_infill,
int($layers_with_perimeters/$config->infill_every_layers) + ($layers_with_perimeters % $config->infill_every_layers),
'infill is only present in correct number of layers';
};
my $config = Slic3r::Config->new_from_defaults;
$config->set('layer_height', 0.2);
$config->set('first_layer_height', 0.2);
$config->set('nozzle_diameter', [0.5]);
$config->set('infill_every_layers', 2);
$config->set('perimeter_extruder', 1);
$config->set('infill_extruder', 2);
$config->set('support_material_extruder', 3);
$config->set('support_material_interface_extruder', 3);
$config->set('top_solid_layers', 0);
$config->set('bottom_solid_layers', 0);
$test->($config);
$config->set('skirts', 0); # prevent usage of perimeter_extruder in raft layers
$config->set('raft_layers', 5);
$test->($config);
}
{
my $config = Slic3r::Config->new_from_defaults;
@ -19,29 +77,22 @@ plan tests => 2;
$config->set('first_layer_height', 0.2);
$config->set('nozzle_diameter', [0.5]);
$config->set('infill_every_layers', 2);
$config->set('infill_extruder', 2);
$config->set('top_solid_layers', 0);
$config->set('bottom_solid_layers', 0);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash";
$print->process;
my $tool = undef;
my %layer_infill = (); # layer_z => has_infill
Slic3r::GCode::Reader->new->parse($gcode, sub {
my ($self, $cmd, $args, $info) = @_;
ok defined(first { @{$_->get_region(0)->fill_surfaces->filter_by_type(S_TYPE_INTERNALVOID)} > 0 }
@{$print->print->get_object(0)->layers}),
'infill combination produces internal void surfaces';
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
$layer_infill{$self->Z} //= 0;
if ($tool == $config->infill_extruder-1) {
$layer_infill{$self->Z} = 1;
}
}
});
my $layers_with_infill = grep $_, values %layer_infill;
$layers_with_infill--; # first layer is never combined
is $layers_with_infill, scalar(keys %layer_infill)/2, 'infill is only present in correct number of layers';
# we disable combination after infill has been generated
$config->set('infill_every_layers', 1);
$print->apply_config($config);
$print->process;
ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 }
@{$print->print->get_object(0)->layers}),
'infill combination is idempotent';
}
# the following needs to be adapted to the new API

View File

@ -49,9 +49,10 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
height => 0.4,
nozzle_diameter => 0.50,
);
$filler->spacing($flow->spacing);
foreach my $angle (0, 45) {
$surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]);
my ($params, @paths) = $filler->fill_surface($surface, flow => $flow, layer_height => 0.4, density => 0.4);
my @paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4);
is scalar @paths, 1, 'one continuous path';
}
}
@ -73,15 +74,15 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
height => 0.4,
nozzle_diameter => $flow_spacing,
);
my ($params, @paths) = $filler->fill_surface(
$filler->spacing($flow->spacing);
my @paths = $filler->fill_surface(
$surface,
flow => $flow,
layer_height => $flow->height,
density => $density // 1,
);
# check whether any part was left uncovered
my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $params->{flow}->spacing/2)}, @paths;
my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @paths;
my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1);
# ignore very small dots
@ -171,7 +172,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
my $config = Slic3r::Config->new_from_defaults;
$config->set('fill_pattern', $pattern);
$config->set('solid_fill_pattern', $pattern);
$config->set('external_fill_pattern', $pattern);
$config->set('perimeters', 1);
$config->set('skirts', 0);
$config->set('fill_density', 20);

View File

@ -1,4 +1,4 @@
use Test::More tests => 19;
use Test::More tests => 20;
use strict;
use warnings;
@ -24,14 +24,24 @@ use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('wipe', [1]);
$config->set('retract_layer_change', [0]);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $have_wipe = 0;
my @retract_speeds = ();
my $extruded_on_this_layer = 0;
my $wiping_on_new_layer = 0;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($info->{retracting} && $info->{dist_XY} > 0) {
if ($info->{travel} && $info->{dist_Z}) {
# changing layer
$extruded_on_this_layer = 0;
} elsif ($info->{extruding} && $info->{dist_XY}) {
$extruded_on_this_layer = 1;
} elsif ($info->{retracting} && $info->{dist_XY} > 0) {
$have_wipe = 1;
$wiping_on_new_layer = 1 if !$extruded_on_this_layer;
my $move_time = $info->{dist_XY} / ($args->{F} // $self->F);
push @retract_speeds, abs($info->{dist_E}) / $move_time;
}
@ -39,6 +49,7 @@ use Slic3r::Test;
ok $have_wipe, "wipe";
ok !defined (first { abs($_ - $config->retract_speed->[0]*60) < 5 } @retract_speeds), 'wipe moves don\'t retract faster than configured speed';
ok !$wiping_on_new_layer, 'no wiping after layer change';
}
{

View File

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 33;
plan tests => 38;
BEGIN {
use FindBin;
@ -213,3 +213,29 @@ my $polygons = [
is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon';
is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square';
}
{
my $triangle = Slic3r::Polygon->new(
[16000170,26257364], [714223,461012], [31286371,461008],
);
is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle';
is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle';
}
{
my $triangle = Slic3r::Polygon->new(
[16000170,26257364], [714223,461012], [20000000,461012], [31286371,461012],
);
is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle having collinear point';
is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle having collinear point';
}
{
my $triangle = Slic3r::Polygon->new(
[16000170,26257364], [714223,461012], [31286371,461008],
);
my $simplified = $triangle->simplify(250000)->[0];
is scalar(@$simplified), 3, 'triangle is never simplified to less than 3 points';
}
__END__

View File

@ -266,7 +266,7 @@ use Slic3r::Test;
my $was_extruding = 0;
my @seam_points = ();
my $print = Slic3r::Test::init_print($model_name, config => $config);
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($info->{extruding}) {

View File

@ -22,15 +22,15 @@ use Slic3r::Test;
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
$print->print->init_extruders;
my $flow = $print->print->objects->[0]->support_material_flow;
my $support_z = Slic3r::Print::SupportMaterial
->new(
my $support = Slic3r::Print::SupportMaterial->new(
object_config => $print->print->objects->[0]->config,
print_config => $print->print->config,
flow => $flow,
interface_flow => $flow,
first_layer_flow => $flow,
)
->support_layers_z(\@contact_z, \@top_z, $config->layer_height);
);
my $support_z = $support->support_layers_z(\@contact_z, \@top_z, $config->layer_height);
my $expected_top_spacing = Slic3r::Print::SupportMaterial::contact_distance($config->nozzle_diameter->[0]);
is $support_z->[0], $config->first_layer_height,
'first layer height is honored';
@ -44,9 +44,10 @@ use Slic3r::Test;
# find layer index of this top surface
my $layer_id = first { abs($support_z->[$_] - $top_z) < epsilon } 0..$#$support_z;
# check that first support layer above this top surface is spaced with nozzle diameter
# check that first support layer above this top surface (or the next one) is spaced with nozzle diameter
$wrong_top_spacing = 1
if ($support_z->[$layer_id+1] - $support_z->[$layer_id]) != $config->nozzle_diameter->[0];
if ($support_z->[$layer_id+1] - $support_z->[$layer_id]) != $expected_top_spacing
&& ($support_z->[$layer_id+2] - $support_z->[$layer_id]) != $expected_top_spacing;
}
ok !$wrong_top_spacing, 'layers above top surfaces are spaced correctly';
};
@ -67,12 +68,12 @@ use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('raft_layers', 3);
$config->set('brim_width', 6);
$config->set('brim_width', 0);
$config->set('skirts', 0);
$config->set('support_material_extruder', 2);
$config->set('support_material_interface_extruder', 2);
$config->set('layer_height', 0.4);
$config->set('first_layer_height', '100%');
$config->set('first_layer_height', 0.4);
my $print = Slic3r::Test::init_print('overhang', config => $config);
ok my $gcode = Slic3r::Test::gcode($print), 'no conflict between raft/support and brim';
@ -84,7 +85,7 @@ use Slic3r::Test;
$tool = $1;
} elsif ($info->{extruding}) {
if ($self->Z <= ($config->raft_layers * $config->layer_height)) {
fail 'not extruding raft/brim with support material extruder'
fail 'not extruding raft with support material extruder'
if $tool != ($config->support_material_extruder-1);
} else {
fail 'support material exceeds raft layers'

View File

@ -57,8 +57,11 @@ my $test = sub {
my $one_axis_would_trigger_limit_without_pause = 0;
foreach my $axis (qw(X Y)) {
# get the direction by comparing the new $axis coordinate with the current one
# 1 = positive, 0 = no change, -1 = negative
my $dir = $info->{"new_$axis"} <=> $self->$axis;
# are we changing direction on this axis?
my $dir = $info->{"dist_$axis"} <=> ($args->{$axis} // $self->$axis);
if ($dir != 0 && $dir{$axis} != $dir) {
# this move changes direction on this axis
if ($dir{$axis} != 0) {

View File

@ -20,6 +20,7 @@ my %opt = ();
my %options = (
'help' => sub { usage() },
'cut=f' => \$opt{cut},
'enable-moving' => \$opt{enable_moving},
);
GetOptions(%options) or usage(1);
$ARGV[0] or usage(1);
@ -32,9 +33,11 @@ my %opt = ();
$model->add_default_instances;
my $app = Slic3r::ViewMesh->new;
$app->{canvas}->enable_picking(1);
$app->{canvas}->enable_moving($opt{enable_moving});
$app->{canvas}->load_object($model->objects->[0]);
$app->{canvas}->set_bounding_box($model->objects->[0]->bounding_box);
$app->{canvas}->set_auto_bed_shape;
$app->{canvas}->zoom_to_volumes;
$app->{canvas}->SetCuttingPlane($opt{cut}) if defined $opt{cut};
$app->MainLoop;
}

View File

@ -20,6 +20,8 @@ my %opt = ();
my %options = (
'help' => sub { usage() },
'load=s' => \$opt{load},
'3D' => \$opt{d3},
'duplicate=i' => \$opt{duplicate},
);
GetOptions(%options) or usage(1);
$ARGV[0] or usage(1);
@ -38,12 +40,14 @@ my %opt = ();
# init print
my $sprint = Slic3r::Print::Simple->new;
$sprint->duplicate($opt{duplicate} // 1);
$sprint->apply_config($config);
$sprint->set_model($model);
$sprint->process;
# visualize toolpaths
$Slic3r::ViewToolpaths::print = $sprint->_print;
$Slic3r::ViewToolpaths::d3 = $opt{d3};
my $app = Slic3r::ViewToolpaths->new;
$app->MainLoop;
}
@ -65,9 +69,10 @@ EOF
package Slic3r::ViewToolpaths;
use Wx qw(:sizer);
use base qw(Wx::App);
use base qw(Wx::App Class::Accessor);
our $print;
our $d3;
sub OnInit {
my $self = shift;
@ -75,8 +80,23 @@ sub OnInit {
my $frame = Wx::Frame->new(undef, -1, 'Toolpaths', [-1, -1], [500, 500]);
my $panel = Wx::Panel->new($frame, -1);
my $canvas;
if ($d3) {
$canvas = Slic3r::GUI::PreviewCanvas->new($panel);
$canvas->print($print);
$canvas->set_bed_shape($print->config->bed_shape);
foreach my $object (@{$print->objects}) {
$canvas->load_object($object->model_object);
}
$canvas->zoom_to_volumes;
} else {
$canvas = Slic3r::GUI::Plater::2DToolpaths->new($panel, $print);
}
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add(Slic3r::GUI::Plater::2DToolpaths->new($panel, $print), 1, wxEXPAND, 0);
$sizer->Add($canvas, 1, wxEXPAND, 0);
$panel->SetSizer($sizer);
$frame->Show(1);

172
utils/wireframe.pl Normal file
View File

@ -0,0 +1,172 @@
#!/usr/bin/perl
# This script exports experimental G-code for wireframe printing
# (inspired by the brilliant WirePrint concept)
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Getopt::Long qw(:config no_auto_abbrev);
use Slic3r;
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale X Y PI);
my %opt = (
step_height => 5,
nozzle_angle => 30,
nozzle_width => 10,
first_layer_height => 0.3,
);
{
my %options = (
'help' => sub { usage() },
'output|o=s' => \$opt{output_file},
'step-height|h=f' => \$opt{step_height},
'nozzle-angle|a=f' => \$opt{nozzle_angle},
'nozzle-width|w=f' => \$opt{nozzle_width},
'first-layer-height=f' => \$opt{first_layer_height},
);
GetOptions(%options) or usage(1);
$opt{output_file} or usage(1);
$ARGV[0] or usage(1);
}
{
# load model
my $model = Slic3r::Model->read_from_file($ARGV[0]);
$model->add_default_instances;
$model->center_instances_around_point(Slic3r::Pointf->new(100,100));
my $mesh = $model->mesh;
$mesh->translate(0, 0, -$mesh->bounding_box->z_min);
# get slices
my @z = ();
my $z_max = $mesh->bounding_box->z_max;
for (my $z = $opt{first_layer_height}; $z <= $z_max; $z += $opt{step_height}) {
push @z, $z;
}
my @slices = @{$mesh->slice(\@z)};
my $flow = Slic3r::Flow->new(
width => 0.35,
height => 0.35,
nozzle_diameter => 0.35,
bridge => 1,
);
my $config = Slic3r::Config::Print->new;
$config->set('gcode_comments', 1);
open my $fh, '>', $opt{output_file};
my $gcodegen = Slic3r::GCode->new(
enable_loop_clipping => 0, # better bonding
);
$gcodegen->apply_print_config($config);
$gcodegen->set_extruders([0]);
print $fh $gcodegen->set_extruder(0);
print $fh $gcodegen->writer->preamble;
my $e = $gcodegen->writer->extruder->e_per_mm3 * $flow->mm3_per_mm;
foreach my $layer_id (0..$#z) {
my $z = $z[$layer_id];
foreach my $island (@{$slices[$layer_id]}) {
foreach my $polygon (@$island) {
if ($layer_id > 0) {
# find the lower polygon that we want to connect to this one
my $lower = $slices[$layer_id-1]->[0]->contour; # 't was easy, wasn't it?
my $lower_z = $z[$layer_id-1];
{
my @points = ();
# keep all points with strong angles
{
my @pp = @$polygon;
foreach my $i (0..$#pp) {
push @points, $pp[$i-1] if abs($pp[$i-1]->ccw_angle($pp[$i-2], $pp[$i]) - PI) > PI/3;
}
}
$polygon = Slic3r::Polygon->new(@points);
}
#$polygon = Slic3r::Polygon->new(@{$polygon->split_at_first_point->equally_spaced_points(scale $opt{nozzle_width})});
# find vertical lines
my @vertical = ();
foreach my $point (@{$polygon}) {
push @vertical, Slic3r::Line->new($point->projection_onto_polygon($lower), $point);
}
next if !@vertical;
my @points = ();
foreach my $line (@vertical) {
push @points, Slic3r::Pointf3->new(
unscale($line->a->x),
unscale($line->a->y), #))
$lower_z,
);
push @points, Slic3r::Pointf3->new(
unscale($line->b->x),
unscale($line->b->y), #))
$z,
);
}
# reappend first point as destination of the last diagonal segment
push @points, Slic3r::Pointf3->new(
unscale($vertical[0]->a->x),
unscale($vertical[0]->a->y), #))
$lower_z,
);
# move to the position of the first vertical line
print $fh $gcodegen->writer->travel_to_xyz(shift @points);
# extrude segments
foreach my $point (@points) {
print $fh $gcodegen->writer->extrude_to_xyz($point, $e * $gcodegen->writer->get_position->distance_to($point));
}
}
}
print $fh $gcodegen->writer->travel_to_z($z);
foreach my $polygon (@$island) {
#my $polyline = $polygon->split_at_vertex(Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1]));
my $polyline = $polygon->split_at_first_point;
print $fh $gcodegen->writer->travel_to_xy(Slic3r::Pointf->new_unscale(@{ $polyline->first_point }), "move to first contour point");
foreach my $line (@{$polyline->lines}) {
my $point = Slic3r::Pointf->new_unscale(@{ $line->b });
print $fh $gcodegen->writer->extrude_to_xy($point, $e * unscale($line->length));
}
}
}
}
close $fh;
}
sub usage {
my ($exit_code) = @_;
print <<"EOF";
Usage: wireframe.pl [ OPTIONS ] file.stl
--help Output this usage screen and exit
--output, -o Write to the specified file
--step-height, -h Use the specified step height
--nozzle-angle, -a Max nozzle angle
--nozzle-width, -w External nozzle diameter
EOF
exit ($exit_code || 0);
}
__END__

View File

@ -1723,6 +1723,7 @@ src/libslic3r/PrintConfig.cpp
src/libslic3r/PrintConfig.hpp
src/libslic3r/PrintObject.cpp
src/libslic3r/PrintRegion.cpp
src/libslic3r/SupportMaterial.hpp
src/libslic3r/Surface.cpp
src/libslic3r/Surface.hpp
src/libslic3r/SurfaceCollection.cpp
@ -1794,6 +1795,7 @@ xsp/Polygon.xsp
xsp/Polyline.xsp
xsp/PolylineCollection.xsp
xsp/Print.xsp
xsp/SupportMaterial.xsp
xsp/Surface.xsp
xsp/SurfaceCollection.xsp
xsp/TriangleMesh.xsp

View File

@ -210,6 +210,7 @@ for my $class (qw(
Slic3r::Layer::Region
Slic3r::Layer::Support
Slic3r::Line
Slic3r::Linef3
Slic3r::Model
Slic3r::Model::Instance
Slic3r::Model::Material

View File

@ -425,6 +425,18 @@ template void diff<Slic3r::Polygons, Slic3r::Polylines>(const Slic3r::Polygons &
template void diff<Slic3r::Polylines, Slic3r::Polylines>(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_);
template void diff<Slic3r::Lines, Slic3r::Lines>(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_);
template <class SubjectType, class ResultType>
void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_)
{
Slic3r::Polygons pp;
for (Slic3r::ExPolygons::const_iterator ex = clip.begin(); ex != clip.end(); ++ex) {
Slic3r::Polygons ppp = *ex;
pp.insert(pp.end(), ppp.begin(), ppp.end());
}
diff(subject, pp, retval, safety_offset_);
}
template void diff<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_);
template <class SubjectType, class ResultType>
void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_)
{

View File

@ -83,6 +83,9 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject,
template <class SubjectType, class ResultType>
void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false);
template <class SubjectType, class ResultType>
void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false);
template <class SubjectType, class ResultType>
void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false);

View File

@ -76,9 +76,18 @@ ExtrusionPath::is_perimeter() const
}
bool
ExtrusionPath::is_fill() const
ExtrusionPath::is_infill() const
{
return this->role == erInternalInfill
return this->role == erBridgeInfill
|| this->role == erInternalInfill
|| this->role == erSolidInfill
|| this->role == erTopSolidInfill;
}
bool
ExtrusionPath::is_solid_infill() const
{
return this->role == erBridgeInfill
|| this->role == erSolidInfill
|| this->role == erTopSolidInfill;
}
@ -235,9 +244,6 @@ ExtrusionLoop::split_at_vertex(const Point &point)
path->polyline.points.insert(path->polyline.points.end(), path->polyline.points.begin() + 1, path->polyline.points.begin() + idx + 1);
path->polyline.points.erase(path->polyline.points.begin(), path->polyline.points.begin() + idx);
} else {
// if we have multiple paths we assume they have different types, so no need to
// check for continuity as we do for the single path case above
// new paths list starts with the second half of current path
ExtrusionPaths new_paths;
{
@ -247,10 +253,10 @@ ExtrusionLoop::split_at_vertex(const Point &point)
}
// then we add all paths until the end of current path list
new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path
new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path
// then we add all paths since the beginning of current list up to the previous one
new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path
new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path
// finally we add the first half of current path
{
@ -332,6 +338,31 @@ ExtrusionLoop::has_overhang_point(const Point &point) const
return false;
}
bool
ExtrusionLoop::is_perimeter() const
{
return this->paths.front().role == erPerimeter
|| this->paths.front().role == erExternalPerimeter
|| this->paths.front().role == erOverhangPerimeter;
}
bool
ExtrusionLoop::is_infill() const
{
return this->paths.front().role == erBridgeInfill
|| this->paths.front().role == erInternalInfill
|| this->paths.front().role == erSolidInfill
|| this->paths.front().role == erTopSolidInfill;
}
bool
ExtrusionLoop::is_solid_infill() const
{
return this->paths.front().role == erBridgeInfill
|| this->paths.front().role == erSolidInfill
|| this->paths.front().role == erTopSolidInfill;
}
#ifdef SLIC3RXS
REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop");
#endif

View File

@ -64,7 +64,8 @@ class ExtrusionPath : public ExtrusionEntity
void simplify(double tolerance);
double length() const;
bool is_perimeter() const;
bool is_fill() const;
bool is_infill() const;
bool is_solid_infill() const;
bool is_bridge() const;
std::string gcode(Extruder* extruder, double e, double F,
double xofs, double yofs, std::string extrusion_axis,
@ -96,6 +97,9 @@ class ExtrusionLoop : public ExtrusionEntity
void split_at(const Point &point);
void clip_end(double distance, ExtrusionPaths* paths) const;
bool has_overhang_point(const Point &point) const;
bool is_perimeter() const;
bool is_infill() const;
bool is_solid_infill() const;
};
}

View File

@ -13,7 +13,7 @@ Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &wid
float w;
if (bridge_flow_ratio > 0) {
// if bridge flow was requested, calculate bridge width
w = Flow::_bridge_width(nozzle_diameter, bridge_flow_ratio);
height = w = Flow::_bridge_width(nozzle_diameter, bridge_flow_ratio);
} else if (!width.percent && width.value == 0) {
// if user left option to 0, calculate a sane default width
w = Flow::_auto_width(role, nozzle_diameter, height);

View File

@ -491,6 +491,12 @@ GCodeWriter::unlift()
return gcode;
}
Pointf3
GCodeWriter::get_position() const
{
return this->_pos;
}
#ifdef SLIC3RXS
REGISTER_CLASS(GCodeWriter, "GCode::Writer");
#endif

View File

@ -45,6 +45,7 @@ class GCodeWriter {
std::string unretract();
std::string lift();
std::string unlift();
Pointf3 get_position() const;
private:
std::string _extrusion_axis;

View File

@ -120,6 +120,29 @@ Layer::make_slices()
}
}
template <class T>
bool
Layer::any_internal_region_slice_contains(const T &item) const
{
FOREACH_LAYERREGION(this, layerm) {
if ((*layerm)->slices.any_internal_contains(item)) return true;
}
return false;
}
template bool Layer::any_internal_region_slice_contains<Line>(const Line &item) const;
template <class T>
bool
Layer::any_internal_region_fill_surface_contains(const T &item) const
{
FOREACH_LAYERREGION(this, layerm) {
if ((*layerm)->fill_surfaces.any_internal_contains(item)) return true;
}
return false;
}
template bool Layer::any_internal_region_fill_surface_contains<Line>(const Line &item) const;
template bool Layer::any_internal_region_fill_surface_contains<Polyline>(const Polyline &item) const;
#ifdef SLIC3RXS
REGISTER_CLASS(Layer, "Layer");

View File

@ -40,15 +40,18 @@ class LayerRegion
// collection of expolygons representing the bridged areas (thus not
// needing support material)
// (this could be just a Polygons object)
ExPolygonCollection bridged;
// collection of polylines representing the unsupported bridge edges
PolylineCollection unsupported_bridge_edges;
// ordered collection of extrusion paths/loops to build all perimeters
// (this collection contains both ExtrusionPath and ExtrusionLoop objects)
ExtrusionEntityCollection perimeters;
// ordered collection of extrusion paths to fill surfaces
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection fills;
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
@ -90,6 +93,8 @@ class Layer {
LayerRegion* add_region(PrintRegion* print_region);
void make_slices();
template <class T> bool any_internal_region_slice_contains(const T &item) const;
template <class T> bool any_internal_region_fill_surface_contains(const T &item) const;
protected:
int _id; // sequential number of layer, 0-based

View File

@ -133,6 +133,12 @@ Line::vector() const
return Vector(this->b.x - this->a.x, this->b.y - this->a.y);
}
Vector
Line::normal() const
{
return Vector((this->b.y - this->a.y), -(this->b.x - this->a.x));
}
#ifdef SLIC3RXS
REGISTER_CLASS(Line, "Line");
@ -178,4 +184,18 @@ Line::to_SV_pureperl() const {
}
#endif
Pointf3
Linef3::intersect_plane(double z) const
{
return Pointf3(
this->a.x + (this->b.x - this->a.x) * (z - this->a.z) / (this->b.z - this->a.z),
this->a.y + (this->b.y - this->a.y) * (z - this->a.z) / (this->b.z - this->a.z),
z
);
}
#ifdef SLIC3RXS
REGISTER_CLASS(Linef3, "Linef3");
#endif
}

View File

@ -7,6 +7,7 @@
namespace Slic3r {
class Line;
class Linef3;
class Polyline;
class Line
@ -34,6 +35,7 @@ class Line
double orientation() const;
double direction() const;
Vector vector() const;
Vector normal() const;
#ifdef SLIC3RXS
void from_SV(SV* line_sv);
@ -45,6 +47,22 @@ class Line
typedef std::vector<Line> Lines;
class Linef3
{
public:
Pointf3 a;
Pointf3 b;
Linef3() {};
explicit Linef3(Pointf3 _a, Pointf3 _b): a(_a), b(_b) {};
Pointf3 intersect_plane(double z) const;
#ifdef SLIC3RXS
void from_SV(SV* line_sv);
void from_SV_check(SV* line_sv);
SV* to_SV_pureperl() const;
#endif
};
}
// start Boost

View File

@ -480,26 +480,31 @@ ModelObject::center_around_origin()
mesh.bounding_box(&bb);
}
// first align to origin on XY
double shift_x = -bb.min.x;
double shift_y = -bb.min.y;
// first align to origin on XYZ
Vectorf3 vector(-bb.min.x, -bb.min.y, -bb.min.z);
// then center it on XY
Sizef3 size = bb.size();
shift_x -= size.x/2;
shift_y -= size.y/2;
vector.x -= size.x/2;
vector.y -= size.y/2;
this->translate(shift_x, shift_y, 0);
this->origin_translation.translate(shift_x, shift_y);
this->translate(vector);
this->origin_translation.translate(vector);
if (!this->instances.empty()) {
for (ModelInstancePtrs::const_iterator i = this->instances.begin(); i != this->instances.end(); ++i) {
(*i)->offset.translate(-shift_x, -shift_y);
(*i)->offset.translate(-vector.x, -vector.y);
}
this->update_bounding_box();
}
}
void
ModelObject::translate(const Vectorf3 &vector)
{
this->translate(vector.x, vector.y, vector.z);
}
void
ModelObject::translate(coordf_t x, coordf_t y, coordf_t z)
{

View File

@ -99,7 +99,7 @@ class ModelObject
center_around_origin() method. Callers might want to apply the same translation
to new volumes before adding them to this object in order to preset alignment
when user expects that. */
Pointf origin_translation;
Pointf3 origin_translation;
// these should be private but we need to expose them via XS until all methods are ported
BoundingBoxf3 _bounding_box;
@ -126,6 +126,7 @@ class ModelObject
void raw_bounding_box(BoundingBoxf3* bb) const;
void instance_bounding_box(size_t instance_idx, BoundingBoxf3* bb) const;
void center_around_origin();
void translate(const Vectorf3 &vector);
void translate(coordf_t x, coordf_t y, coordf_t z);
void scale(const Pointf3 &versor);
size_t materials_count() const;

View File

@ -17,6 +17,8 @@ class MultiPoint
Points points;
operator Points() const;
MultiPoint() {};
explicit MultiPoint(const Points &_points): points(_points) {};
void scale(double factor);
void translate(double x, double y);
void translate(const Point &vector);

View File

@ -41,7 +41,7 @@ Point::translate(double x, double y)
}
void
Point::translate(const Point &vector)
Point::translate(const Vector &vector)
{
this->translate(vector.x, vector.y);
}
@ -160,6 +160,18 @@ Point::ccw(const Line &line) const
return this->ccw(line.a, line.b);
}
// returns the CCW angle between this-p1 and this-p2
// i.e. this assumes a CCW rotation from p1 to p2 around this
double
Point::ccw_angle(const Point &p1, const Point &p2) const
{
double angle = atan2(p1.x - this->x, p1.y - this->y)
- atan2(p2.x - this->x, p2.y - this->y);
// we only want to return only positive angles
return angle <= 0 ? angle + 2*PI : angle;
}
Point
Point::projection_onto(const MultiPoint &poly) const
{
@ -211,6 +223,12 @@ Point::negative() const
return Point(-this->x, -this->y);
}
Vector
Point::vector_to(const Point &point) const
{
return Vector(point.x - this->x, point.y - this->y);
}
Point
operator+(const Point& point1, const Point& point2)
{
@ -286,6 +304,18 @@ Pointf::rotate(double angle, const Pointf &center)
this->y = center.y + cos(angle) * (cur_y - center.y) + sin(angle) * (cur_x - center.x);
}
Pointf
Pointf::negative() const
{
return Pointf(-this->x, -this->y);
}
Vectorf
Pointf::vector_to(const Pointf &point) const
{
return Vectorf(point.x - this->x, point.y - this->y);
}
#ifdef SLIC3RXS
REGISTER_CLASS(Pointf, "Pointf");
@ -333,6 +363,12 @@ Pointf3::scale(double factor)
this->z *= factor;
}
void
Pointf3::translate(const Vectorf3 &vector)
{
this->translate(vector.x, vector.y, vector.z);
}
void
Pointf3::translate(double x, double y, double z)
{
@ -340,6 +376,27 @@ Pointf3::translate(double x, double y, double z)
this->z += z;
}
double
Pointf3::distance_to(const Pointf3 &point) const
{
double dx = ((double)point.x - this->x);
double dy = ((double)point.y - this->y);
double dz = ((double)point.z - this->z);
return sqrt(dx*dx + dy*dy + dz*dz);
}
Pointf3
Pointf3::negative() const
{
return Pointf3(-this->x, -this->y, -this->z);
}
Vectorf3
Pointf3::vector_to(const Pointf3 &point) const
{
return Vectorf3(point.x - this->x, point.y - this->y, point.z - this->z);
}
#ifdef SLIC3RXS
REGISTER_CLASS(Pointf3, "Pointf3");
#endif

View File

@ -14,6 +14,8 @@ class Point;
class Pointf;
class Pointf3;
typedef Point Vector;
typedef Pointf Vectorf;
typedef Pointf3 Vectorf3;
typedef std::vector<Point> Points;
typedef std::vector<Point*> PointPtrs;
typedef std::vector<const Point*> PointConstPtrs;
@ -36,7 +38,7 @@ class Point
std::string wkt() const;
void scale(double factor);
void translate(double x, double y);
void translate(const Point &vector);
void translate(const Vector &vector);
void rotate(double angle, const Point &center);
bool coincides_with(const Point &point) const;
bool coincides_with_epsilon(const Point &point) const;
@ -48,9 +50,11 @@ class Point
double distance_to(const Line &line) const;
double ccw(const Point &p1, const Point &p2) const;
double ccw(const Line &line) const;
double ccw_angle(const Point &p1, const Point &p2) const;
Point projection_onto(const MultiPoint &poly) const;
Point projection_onto(const Line &line) const;
Point negative() const;
Vector vector_to(const Point &point) const;
#ifdef SLIC3RXS
void from_SV(SV* point_sv);
@ -78,6 +82,8 @@ class Pointf
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, const Pointf &center);
Pointf negative() const;
Vectorf vector_to(const Pointf &point) const;
#ifdef SLIC3RXS
bool from_SV(SV* point_sv);
@ -92,7 +98,11 @@ class Pointf3 : public Pointf
coordf_t z;
explicit Pointf3(coordf_t _x = 0, coordf_t _y = 0, coordf_t _z = 0): Pointf(_x, _y), z(_z) {};
void scale(double factor);
void translate(const Vectorf3 &vector);
void translate(double x, double y, double z);
double distance_to(const Pointf3 &point) const;
Pointf3 negative() const;
Vectorf3 vector_to(const Pointf3 &point) const;
};
}

View File

@ -158,8 +158,12 @@ Polygon::contains(const Point &point) const
Polygons
Polygon::simplify(double tolerance) const
{
Polygon p = *this;
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
// repeat first point at the end in order to apply Douglas-Peucker
// on the whole polygon
Points points = this->points;
points.push_back(points.front());
Polygon p(MultiPoint::_douglas_peucker(points, tolerance));
p.points.pop_back();
Polygons pp;
pp.push_back(p);
@ -222,6 +226,58 @@ Polygon::wkt() const
return wkt.str();
}
// find all concave vertices (i.e. having an internal angle greater than the supplied angle) */
void
Polygon::concave_points(double angle, Points* points) const
{
angle = 2*PI - angle;
// check whether first point forms a concave angle
if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle)
points->push_back(this->points.front());
// check whether points 1..(n-1) form concave angles
for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) {
if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points->push_back(*p);
}
// check whether last point forms a concave angle
if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle)
points->push_back(this->points.back());
}
void
Polygon::concave_points(Points* points) const
{
this->concave_points(PI, points);
}
// find all convex vertices (i.e. having an internal angle smaller than the supplied angle) */
void
Polygon::convex_points(double angle, Points* points) const
{
angle = 2*PI - angle;
// check whether first point forms a convex angle
if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle)
points->push_back(this->points.front());
// check whether points 1..(n-1) form convex angles
for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) {
if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points->push_back(*p);
}
// check whether last point forms a convex angle
if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle)
points->push_back(this->points.back());
}
void
Polygon::convex_points(Points* points) const
{
this->convex_points(PI, points);
}
#ifdef SLIC3RXS
REGISTER_CLASS(Polygon, "Polygon");

View File

@ -19,6 +19,9 @@ class Polygon : public MultiPoint {
operator Polyline() const;
Point& operator[](Points::size_type idx);
const Point& operator[](Points::size_type idx) const;
Polygon() {};
explicit Polygon(const Points &points): MultiPoint(points) {};
Point last_point() const;
Lines lines() const;
void lines(Lines* lines) const;
@ -38,6 +41,10 @@ class Polygon : public MultiPoint {
void triangulate_convex(Polygons* polygons) const;
Point centroid() const;
std::string wkt() const;
void concave_points(double angle, Points* points) const;
void concave_points(Points* points) const;
void convex_points(double angle, Points* points) const;
void convex_points(Points* points) const;
#ifdef SLIC3RXS
void from_SV_check(SV* poly_sv);

View File

@ -1,7 +1,9 @@
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Flow.hpp"
#include "Geometry.hpp"
#include "SupportMaterial.hpp"
#include <algorithm>
namespace Slic3r {
@ -74,46 +76,20 @@ Print::get_object(size_t idx)
return objects.at(idx);
}
PrintObject*
Print::add_object(ModelObject *model_object, const BoundingBoxf3 &modobj_bbox)
{
PrintObject *object = new PrintObject(this, model_object, modobj_bbox);
objects.push_back(object);
// invalidate steps
this->invalidate_step(psSkirt);
this->invalidate_step(psBrim);
return object;
}
PrintObject*
Print::set_new_object(size_t idx, ModelObject *model_object, const BoundingBoxf3 &modobj_bbox)
{
if (idx >= this->objects.size()) throw "bad idx";
PrintObjectPtrs::iterator old_it = this->objects.begin() + idx;
// before deleting object, invalidate all of its steps in order to
// invalidate all of the dependent ones in Print
(*old_it)->invalidate_all_steps();
delete *old_it;
PrintObject *object = new PrintObject(this, model_object, modobj_bbox);
this->objects[idx] = object;
return object;
}
void
Print::delete_object(size_t idx)
{
PrintObjectPtrs::iterator i = this->objects.begin() + idx;
// before deleting object, invalidate all of its steps in order to
// invalidate all of the dependent ones in Print
(*i)->invalidate_all_steps();
// destroy object and remove it from our container
delete *i;
this->objects.erase(i);
// TODO: purge unused regions
this->state.invalidate(psSkirt);
this->state.invalidate(psBrim);
}
void
@ -173,6 +149,7 @@ bool
Print::invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys)
{
std::set<PrintStep> steps;
std::set<PrintObjectStep> osteps;
// this method only accepts PrintConfig option keys
for (std::vector<t_config_option_key>::const_iterator opt_key = opt_keys.begin(); opt_key != opt_keys.end(); ++opt_key) {
@ -223,6 +200,7 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
|| *opt_key == "output_filename_format"
|| *opt_key == "perimeter_acceleration"
|| *opt_key == "post_process"
|| *opt_key == "pressure_advance"
|| *opt_key == "retract_before_travel"
|| *opt_key == "retract_layer_change"
|| *opt_key == "retract_length"
@ -245,6 +223,12 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
|| *opt_key == "wipe"
|| *opt_key == "z_offset") {
// these options only affect G-code export, so nothing to invalidate
} else if (*opt_key == "first_layer_extrusion_width") {
osteps.insert(posPerimeters);
osteps.insert(posInfill);
osteps.insert(posSupportMaterial);
steps.insert(psSkirt);
steps.insert(psBrim);
} else {
// for legacy, if we can't handle this option let's invalidate all steps
return this->invalidate_all_steps();
@ -255,6 +239,11 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
for (std::set<PrintStep>::const_iterator step = steps.begin(); step != steps.end(); ++step) {
if (this->invalidate_step(*step)) invalidated = true;
}
for (std::set<PrintObjectStep>::const_iterator ostep = osteps.begin(); ostep != osteps.end(); ++ostep) {
FOREACH_OBJECT(this, object) {
if ((*object)->invalidate_step(*ostep)) invalidated = true;
}
}
return invalidated;
}
@ -290,6 +279,19 @@ Print::invalidate_all_steps()
return invalidated;
}
// returns true if an object step is done on all objects
// and there's at least one object
bool
Print::step_done(PrintObjectStep step) const
{
if (this->objects.empty()) return false;
FOREACH_OBJECT(this, object) {
if (!(*object)->state.is_done(step))
return false;
}
return true;
}
// returns 0-based indices of used extruders
std::set<size_t>
Print::extruders() const
@ -299,6 +301,7 @@ Print::extruders() const
FOREACH_REGION(this, region) {
extruders.insert((*region)->config.perimeter_extruder - 1);
extruders.insert((*region)->config.infill_extruder - 1);
extruders.insert((*region)->config.solid_infill_extruder - 1);
}
FOREACH_OBJECT(this, object) {
extruders.insert((*object)->config.support_material_extruder - 1);
@ -347,9 +350,23 @@ Print::add_model_object(ModelObject* model_object, int idx)
{
BoundingBoxf3 bb;
model_object->raw_bounding_box(&bb);
o = (idx != -1)
? this->set_new_object(idx, model_object, bb)
: this->add_object(model_object, bb);
if (idx != -1) {
// replacing existing object
PrintObjectPtrs::iterator old_it = this->objects.begin() + idx;
// before deleting object, invalidate all of its steps in order to
// invalidate all of the dependent ones in Print
(*old_it)->invalidate_all_steps();
delete *old_it;
this->objects[idx] = o = new PrintObject(this, model_object, bb);
} else {
o = new PrintObject(this, model_object, bb);
objects.push_back(o);
// invalidate steps
this->invalidate_step(psSkirt);
this->invalidate_step(psBrim);
}
}
for (ModelVolumePtrs::const_iterator v_i = model_object->volumes.begin(); v_i != model_object->volumes.end(); ++v_i) {
@ -446,7 +463,7 @@ Print::apply_config(DynamicPrintConfig config)
std::vector<int> &region_volumes = object->region_volumes[region_id];
for (std::vector<int>::const_iterator volume_id = region_volumes.begin(); volume_id != region_volumes.end(); ++volume_id) {
ModelVolume* volume = object->model_object()->volumes[*volume_id];
ModelVolume* volume = object->model_object()->volumes.at(*volume_id);
PrintRegionConfig new_config = this->_region_config_from_model_volume(*volume);
@ -615,6 +632,112 @@ Print::validate() const
}
}
// the bounding box of objects placed in copies position
// (without taking skirt/brim/support material into account)
BoundingBox
Print::bounding_box() const
{
BoundingBox bb;
FOREACH_OBJECT(this, object) {
for (Points::const_iterator copy = (*object)->_shifted_copies.begin(); copy != (*object)->_shifted_copies.end(); ++copy) {
bb.merge(*copy);
Point p = *copy;
p.translate((*object)->size);
bb.merge(p);
}
}
return bb;
}
// the total bounding box of extrusions, including skirt/brim/support material
// this methods needs to be called even when no steps were processed, so it should
// only use configuration values
BoundingBox
Print::total_bounding_box() const
{
// get objects bounding box
BoundingBox bb = this->bounding_box();
// we need to offset the objects bounding box by at least half the perimeters extrusion width
Flow perimeter_flow = this->objects.front()->get_layer(0)->get_region(0)->flow(frPerimeter);
double extra = perimeter_flow.width/2;
// consider support material
if (this->has_support_material()) {
extra = std::max(extra, SUPPORT_MATERIAL_MARGIN);
}
// consider brim and skirt
if (this->config.brim_width.value > 0) {
Flow brim_flow = this->brim_flow();
extra = std::max(extra, this->config.brim_width.value + brim_flow.width/2);
}
if (this->config.skirts.value > 0) {
Flow skirt_flow = this->skirt_flow();
extra = std::max(
extra,
this->config.brim_width.value
+ this->config.skirt_distance.value
+ this->config.skirts.value * skirt_flow.spacing()
+ skirt_flow.width/2
);
}
if (extra > 0)
bb.offset(scale_(extra));
return bb;
}
double
Print::skirt_first_layer_height() const
{
if (this->objects.empty()) CONFESS("skirt_first_layer_height() can't be called without PrintObjects");
return this->objects.front()->config.get_abs_value("first_layer_height");
}
Flow
Print::brim_flow() const
{
ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width;
if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width;
/* We currently use a random region's perimeter extruder.
While this works for most cases, we should probably consider all of the perimeter
extruders and take the one with, say, the smallest index.
The same logic should be applied to the code that selects the extruder during G-code
generation as well. */
return Flow::new_from_config_width(
frPerimeter,
width,
this->config.nozzle_diameter.get_at(this->regions.front()->config.perimeter_extruder-1),
this->skirt_first_layer_height(),
0
);
}
Flow
Print::skirt_flow() const
{
ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width;
if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width;
/* We currently use a random object's support material extruder.
While this works for most cases, we should probably consider all of the support material
extruders and take the one with, say, the smallest index;
The same logic should be applied to the code that selects the extruder during G-code
generation as well. */
return Flow::new_from_config_width(
frPerimeter,
width,
this->config.nozzle_diameter.get_at(this->objects.front()->config.support_material_extruder-1),
this->skirt_first_layer_height(),
0
);
}
PrintRegionConfig
Print::_region_config_from_model_volume(const ModelVolume &volume)
{

View File

@ -5,6 +5,7 @@
#include <set>
#include <vector>
#include <stdexcept>
#include "BoundingBox.hpp"
#include "Flow.hpp"
#include "PrintConfig.hpp"
#include "Point.hpp"
@ -75,8 +76,10 @@ class PrintObject
friend class Print;
public:
// vector of (vectors of volume ids), indexed by region_id
std::vector<std::vector<int> > region_volumes;
// map of (vectors of volume ids), indexed by region_id
/* (we use map instead of vector so that we don't have to worry about
resizing it and the [] operator adds new items automagically) */
std::map< size_t,std::vector<int> > region_volumes;
PrintObjectConfig config;
t_layer_height_ranges layer_height_ranges;
@ -108,17 +111,19 @@ class PrintObject
bool delete_all_copies();
bool set_copies(const Points &points);
bool reload_model_instances();
void bounding_box(BoundingBox* bb) const;
// adds region_id, too, if necessary
void add_region_volume(int region_id, int volume_id);
size_t layer_count();
size_t total_layer_count() const;
size_t layer_count() const;
void clear_layers();
Layer* get_layer(int idx);
Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
void delete_layer(int idx);
size_t support_layer_count();
size_t support_layer_count() const;
void clear_support_layers();
SupportLayer* get_support_layer(int idx);
SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
@ -129,6 +134,8 @@ class PrintObject
bool invalidate_step(PrintObjectStep step);
bool invalidate_all_steps();
void bridge_over_infill();
private:
Print* _print;
ModelObject* _model_object;
@ -165,8 +172,6 @@ class Print
// methods for handling objects
void clear_objects();
PrintObject* get_object(size_t idx);
PrintObject* add_object(ModelObject *model_object, const BoundingBoxf3 &modobj_bbox);
PrintObject* set_new_object(size_t idx, ModelObject *model_object, const BoundingBoxf3 &modobj_bbox);
void delete_object(size_t idx);
void reload_object(size_t idx);
@ -178,11 +183,17 @@ class Print
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
bool invalidate_step(PrintStep step);
bool invalidate_all_steps();
bool step_done(PrintObjectStep step) const;
void add_model_object(ModelObject* model_object, int idx = -1);
bool apply_config(DynamicPrintConfig config);
void init_extruders();
void validate() const;
BoundingBox bounding_box() const;
BoundingBox total_bounding_box() const;
double skirt_first_layer_height() const;
Flow brim_flow() const;
Flow skirt_flow() const;
std::set<size_t> extruders() const;
void _simplify_slices(double distance);

View File

@ -115,6 +115,24 @@ PrintConfigDef::build_def() {
Options["end_gcode"].full_width = true;
Options["end_gcode"].height = 120;
Options["external_fill_pattern"].type = coEnum;
Options["external_fill_pattern"].label = "Top/bottom fill pattern";
Options["external_fill_pattern"].category = "Infill";
Options["external_fill_pattern"].tooltip = "Fill pattern for top/bottom infill. This only affects the external visible layer, and not its adjacent solid shells.";
Options["external_fill_pattern"].cli = "external-fill-pattern=s";
Options["external_fill_pattern"].enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
Options["external_fill_pattern"].enum_values.push_back("rectilinear");
Options["external_fill_pattern"].enum_values.push_back("concentric");
Options["external_fill_pattern"].enum_values.push_back("hilbertcurve");
Options["external_fill_pattern"].enum_values.push_back("archimedeanchords");
Options["external_fill_pattern"].enum_values.push_back("octagramspiral");
Options["external_fill_pattern"].enum_labels.push_back("rectilinear");
Options["external_fill_pattern"].enum_labels.push_back("concentric");
Options["external_fill_pattern"].enum_labels.push_back("hilbertcurve (slow)");
Options["external_fill_pattern"].enum_labels.push_back("archimedeanchords (slow)");
Options["external_fill_pattern"].enum_labels.push_back("octagramspiral (slow)");
Options["external_fill_pattern"].aliases.push_back("solid_fill_pattern");
Options["external_perimeter_extrusion_width"].type = coFloatOrPercent;
Options["external_perimeter_extrusion_width"].label = "External perimeters";
Options["external_perimeter_extrusion_width"].category = "Extrusion Width";
@ -280,9 +298,9 @@ PrintConfigDef::build_def() {
Options["fill_pattern"].enum_labels.push_back("concentric");
Options["fill_pattern"].enum_labels.push_back("honeycomb");
Options["fill_pattern"].enum_labels.push_back("3D honeycomb");
Options["fill_pattern"].enum_labels.push_back("hilbertcurve (slow)");
Options["fill_pattern"].enum_labels.push_back("archimedeanchords (slow)");
Options["fill_pattern"].enum_labels.push_back("octagramspiral (slow)");
Options["fill_pattern"].enum_labels.push_back("hilbertcurve");
Options["fill_pattern"].enum_labels.push_back("archimedeanchords");
Options["fill_pattern"].enum_labels.push_back("octagramspiral");
Options["first_layer_acceleration"].type = coFloat;
Options["first_layer_acceleration"].label = "First layer";
@ -511,7 +529,7 @@ PrintConfigDef::build_def() {
Options["perimeter_extruder"].type = coInt;
Options["perimeter_extruder"].label = "Perimeter extruder";
Options["perimeter_extruder"].category = "Extruders";
Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters. First extruder is 1.";
Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters and brim. First extruder is 1.";
Options["perimeter_extruder"].cli = "perimeter-extruder=i";
Options["perimeter_extruder"].aliases.push_back("perimeters_extruder");
Options["perimeter_extruder"].min = 1;
@ -534,9 +552,10 @@ PrintConfigDef::build_def() {
Options["perimeter_speed"].min = 0;
Options["perimeters"].type = coInt;
Options["perimeters"].label = "Perimeters (minimum)";
Options["perimeters"].label = "Perimeters";
Options["perimeters"].category = "Layers and Perimeters";
Options["perimeters"].tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled.";
Options["perimeters"].sidetext = "(minimum)";
Options["perimeters"].cli = "perimeters=i";
Options["perimeters"].aliases.push_back("perimeter_offsets");
Options["perimeters"].min = 0;
@ -646,8 +665,8 @@ PrintConfigDef::build_def() {
Options["skirt_height"].cli = "skirt-height=i";
Options["skirts"].type = coInt;
Options["skirts"].label = "Loops";
Options["skirts"].tooltip = "Number of loops for this skirt, in other words its thickness. Set this to zero to disable skirt.";
Options["skirts"].label = "Loops (minimum)";
Options["skirts"].tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely.";
Options["skirts"].cli = "skirts=i";
Options["skirts"].min = 0;
@ -668,23 +687,6 @@ PrintConfigDef::build_def() {
Options["small_perimeter_speed"].cli = "small-perimeter-speed=s";
Options["small_perimeter_speed"].ratio_over = "perimeter_speed";
Options["solid_fill_pattern"].type = coEnum;
Options["solid_fill_pattern"].label = "Top/bottom fill pattern";
Options["solid_fill_pattern"].category = "Infill";
Options["solid_fill_pattern"].tooltip = "Fill pattern for top/bottom infill.";
Options["solid_fill_pattern"].cli = "solid-fill-pattern=s";
Options["solid_fill_pattern"].enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
Options["solid_fill_pattern"].enum_values.push_back("rectilinear");
Options["solid_fill_pattern"].enum_values.push_back("concentric");
Options["solid_fill_pattern"].enum_values.push_back("hilbertcurve");
Options["solid_fill_pattern"].enum_values.push_back("archimedeanchords");
Options["solid_fill_pattern"].enum_values.push_back("octagramspiral");
Options["solid_fill_pattern"].enum_labels.push_back("rectilinear");
Options["solid_fill_pattern"].enum_labels.push_back("concentric");
Options["solid_fill_pattern"].enum_labels.push_back("hilbertcurve (slow)");
Options["solid_fill_pattern"].enum_labels.push_back("archimedeanchords (slow)");
Options["solid_fill_pattern"].enum_labels.push_back("octagramspiral (slow)");
Options["solid_infill_below_area"].type = coFloat;
Options["solid_infill_below_area"].label = "Solid infill threshold area";
Options["solid_infill_below_area"].category = "Infill";
@ -693,6 +695,13 @@ PrintConfigDef::build_def() {
Options["solid_infill_below_area"].cli = "solid-infill-below-area=f";
Options["solid_infill_below_area"].min = 0;
Options["solid_infill_extruder"].type = coInt;
Options["solid_infill_extruder"].label = "Solid infill extruder";
Options["solid_infill_extruder"].category = "Extruders";
Options["solid_infill_extruder"].tooltip = "The extruder to use when printing solid infill.";
Options["solid_infill_extruder"].cli = "solid-infill-extruder=i";
Options["solid_infill_extruder"].min = 1;
Options["solid_infill_every_layers"].type = coInt;
Options["solid_infill_every_layers"].label = "Solid infill every";
Options["solid_infill_every_layers"].category = "Infill";
@ -771,9 +780,9 @@ PrintConfigDef::build_def() {
Options["support_material_enforce_layers"].min = 0;
Options["support_material_extruder"].type = coInt;
Options["support_material_extruder"].label = "Support material extruder";
Options["support_material_extruder"].label = "Support material/raft/skirt extruder";
Options["support_material_extruder"].category = "Extruders";
Options["support_material_extruder"].tooltip = "The extruder to use when printing support material. This affects brim and raft too.";
Options["support_material_extruder"].tooltip = "The extruder to use when printing support material, raft and skirt.";
Options["support_material_extruder"].cli = "support-material-extruder=i";
Options["support_material_extruder"].min = 1;
@ -785,7 +794,7 @@ PrintConfigDef::build_def() {
Options["support_material_extrusion_width"].cli = "support-material-extrusion-width=s";
Options["support_material_interface_extruder"].type = coInt;
Options["support_material_interface_extruder"].label = "Support material interface extruder";
Options["support_material_interface_extruder"].label = "Support material/raft interface extruder";
Options["support_material_interface_extruder"].category = "Extruders";
Options["support_material_interface_extruder"].tooltip = "The extruder to use when printing support material interface. This affects raft too.";
Options["support_material_interface_extruder"].cli = "support-material-interface-extruder=i";
@ -871,8 +880,7 @@ PrintConfigDef::build_def() {
Options["threads"].type = coInt;
Options["threads"].label = "Threads";
Options["threads"].tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors. Beware that more threads consume more memory.";
Options["threads"].sidetext = "(more speed but more memory usage)";
Options["threads"].tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors.";
Options["threads"].cli = "threads|j=i";
Options["threads"].readonly = true;
Options["threads"].min = 1;

View File

@ -93,6 +93,10 @@ class DynamicPrintConfig : public DynamicConfig
this->option("support_material_interface_extruder", true)->setInt(extruder);
}
}
if (!this->has("solid_infill_extruder") && this->has("infill_extruder"))
this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt());
if (this->has("spiral_vase") && this->opt<ConfigOptionBool>("spiral_vase", true)->value) {
{
// this should be actually done only on the spiral layers instead of all
@ -205,6 +209,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig
ConfigOptionInt bottom_solid_layers;
ConfigOptionFloat bridge_flow_ratio;
ConfigOptionFloat bridge_speed;
ConfigOptionEnum<InfillPattern> external_fill_pattern;
ConfigOptionFloatOrPercent external_perimeter_extrusion_width;
ConfigOptionFloatOrPercent external_perimeter_speed;
ConfigOptionBool external_perimeters_first;
@ -223,8 +228,8 @@ class PrintRegionConfig : public virtual StaticPrintConfig
ConfigOptionFloat perimeter_speed;
ConfigOptionInt perimeters;
ConfigOptionFloatOrPercent small_perimeter_speed;
ConfigOptionEnum<InfillPattern> solid_fill_pattern;
ConfigOptionFloat solid_infill_below_area;
ConfigOptionInt solid_infill_extruder;
ConfigOptionFloatOrPercent solid_infill_extrusion_width;
ConfigOptionInt solid_infill_every_layers;
ConfigOptionFloatOrPercent solid_infill_speed;
@ -237,6 +242,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig
this->bottom_solid_layers.value = 3;
this->bridge_flow_ratio.value = 1;
this->bridge_speed.value = 60;
this->external_fill_pattern.value = ipRectilinear;
this->external_perimeter_extrusion_width.value = 0;
this->external_perimeter_extrusion_width.percent = false;
this->external_perimeter_speed.value = 70;
@ -258,9 +264,9 @@ class PrintRegionConfig : public virtual StaticPrintConfig
this->perimeter_extrusion_width.percent = false;
this->perimeter_speed.value = 30;
this->perimeters.value = 3;
this->solid_infill_extruder.value = 1;
this->small_perimeter_speed.value = 30;
this->small_perimeter_speed.percent = false;
this->solid_fill_pattern.value = ipRectilinear;
this->solid_infill_below_area.value = 70;
this->solid_infill_extrusion_width.value = 0;
this->solid_infill_extrusion_width.percent = false;
@ -279,6 +285,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig
if (opt_key == "bottom_solid_layers") return &this->bottom_solid_layers;
if (opt_key == "bridge_flow_ratio") return &this->bridge_flow_ratio;
if (opt_key == "bridge_speed") return &this->bridge_speed;
if (opt_key == "external_fill_pattern") return &this->external_fill_pattern;
if (opt_key == "external_perimeter_extrusion_width") return &this->external_perimeter_extrusion_width;
if (opt_key == "external_perimeter_speed") return &this->external_perimeter_speed;
if (opt_key == "external_perimeters_first") return &this->external_perimeters_first;
@ -297,8 +304,8 @@ class PrintRegionConfig : public virtual StaticPrintConfig
if (opt_key == "perimeter_speed") return &this->perimeter_speed;
if (opt_key == "perimeters") return &this->perimeters;
if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed;
if (opt_key == "solid_fill_pattern") return &this->solid_fill_pattern;
if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area;
if (opt_key == "solid_infill_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;

View File

@ -1,5 +1,6 @@
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
namespace Slic3r {
@ -9,8 +10,6 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding
_model_object(model_object),
typed_slices(false)
{
region_volumes.resize(this->_print->regions.size());
// Compute the translation to be applied to our meshes so that we work with smaller coordinates
{
// Translate meshes so that our toolpath generation algorithms work with smaller
@ -112,17 +111,32 @@ PrintObject::reload_model_instances()
}
void
PrintObject::add_region_volume(int region_id, int volume_id)
PrintObject::bounding_box(BoundingBox* bb) const
{
if (region_id >= region_volumes.size()) {
region_volumes.resize(region_id + 1);
// since the object is aligned to origin, bounding box coincides with size
Points pp;
pp.push_back(Point(0,0));
pp.push_back(this->size);
*bb = BoundingBox(pp);
}
void
PrintObject::add_region_volume(int region_id, int volume_id)
{
region_volumes[region_id].push_back(volume_id);
}
/* This is the *total* layer count (including support layers)
this value is not supposed to be compared with Layer::id
since they have different semantics */
size_t
PrintObject::layer_count()
PrintObject::total_layer_count() const
{
return this->layer_count() + this->support_layer_count();
}
size_t
PrintObject::layer_count() const
{
return this->layers.size();
}
@ -157,7 +171,7 @@ PrintObject::delete_layer(int idx)
}
size_t
PrintObject::support_layer_count()
PrintObject::support_layer_count() const
{
return this->support_layers.size();
}
@ -203,6 +217,7 @@ PrintObject::invalidate_state_by_config_options(const std::vector<t_config_optio
|| *opt_key == "extra_perimeters"
|| *opt_key == "gap_fill_speed"
|| *opt_key == "overhangs"
|| *opt_key == "first_layer_extrusion_width"
|| *opt_key == "perimeter_extrusion_width"
|| *opt_key == "thin_walls"
|| *opt_key == "external_perimeters_first") {
@ -224,22 +239,25 @@ PrintObject::invalidate_state_by_config_options(const std::vector<t_config_optio
|| *opt_key == "support_material_pattern"
|| *opt_key == "support_material_spacing"
|| *opt_key == "support_material_threshold"
|| *opt_key == "dont_support_bridges") {
|| *opt_key == "dont_support_bridges"
|| *opt_key == "first_layer_extrusion_width") {
steps.insert(posSupportMaterial);
} else if (*opt_key == "interface_shells"
|| *opt_key == "infill_only_where_needed"
|| *opt_key == "infill_every_layers"
|| *opt_key == "solid_infill_every_layers"
|| *opt_key == "bottom_solid_layers"
|| *opt_key == "top_solid_layers"
|| *opt_key == "solid_infill_below_area"
|| *opt_key == "infill_extruder"
|| *opt_key == "solid_infill_extruder"
|| *opt_key == "infill_extrusion_width") {
steps.insert(posPrepareInfill);
} else if (*opt_key == "fill_angle"
} else if (*opt_key == "external_fill_pattern"
|| *opt_key == "fill_angle"
|| *opt_key == "fill_pattern"
|| *opt_key == "solid_fill_pattern"
|| *opt_key == "infill_every_layers"
|| *opt_key == "solid_infill_every_layers"
|| *opt_key == "top_infill_extrusion_width") {
|| *opt_key == "top_infill_extrusion_width"
|| *opt_key == "first_layer_extrusion_width") {
steps.insert(posInfill);
} else if (*opt_key == "fill_density"
|| *opt_key == "solid_infill_extrusion_width") {
@ -315,6 +333,93 @@ PrintObject::invalidate_all_steps()
return invalidated;
}
void
PrintObject::bridge_over_infill()
{
FOREACH_REGION(this->_print, region) {
size_t region_id = region - this->_print->regions.begin();
double fill_density = (*region)->config.fill_density.value;
if (fill_density == 100 || fill_density == 0) continue;
FOREACH_LAYER(this, layer_it) {
if (layer_it == this->layers.begin()) continue;
Layer* layer = *layer_it;
Layer* lower_layer = *(layer_it - 1);
LayerRegion* layerm = layer->get_region(region_id);
// compute the areas needing bridge math
Polygons internal_solid, lower_internal;
layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid);
FOREACH_LAYERREGION(lower_layer, lower_layerm_it)
(*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal);
ExPolygons to_bridge;
intersection(internal_solid, lower_internal, &to_bridge);
if (to_bridge.empty()) continue;
ExPolygons not_to_bridge;
diff(internal_solid, to_bridge, &not_to_bridge, true);
#ifdef SLIC3R_DEBUG
printf("Bridging %zu internal areas at layer %d\n", to_bridge.size(), layer->id());
#endif
// build the new collection of fill_surfaces
{
Surfaces new_surfaces;
for (Surfaces::const_iterator surface = layerm->fill_surfaces.surfaces.begin(); surface != layerm->fill_surfaces.surfaces.end(); ++surface) {
if (surface->surface_type != stInternalSolid)
new_surfaces.push_back(*surface);
}
for (ExPolygons::const_iterator ex = to_bridge.begin(); ex != to_bridge.end(); ++ex)
new_surfaces.push_back(Surface(stInternalBridge, *ex));
for (ExPolygons::const_iterator ex = not_to_bridge.begin(); ex != not_to_bridge.end(); ++ex)
new_surfaces.push_back(Surface(stInternalSolid, *ex));
layerm->fill_surfaces.surfaces = new_surfaces;
}
/*
# exclude infill from the layers below if needed
# see discussion at https://github.com/alexrj/Slic3r/issues/240
# Update: do not exclude any infill. Sparse infill is able to absorb the excess material.
if (0) {
my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height;
for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) {
Slic3r::debugf " skipping infill below those areas at layer %d\n", $i;
foreach my $lower_layerm (@{$self->get_layer($i)->regions}) {
my @new_surfaces = ();
# subtract the area from all types of surfaces
foreach my $group (@{$lower_layerm->fill_surfaces->group}) {
push @new_surfaces, map $group->[0]->clone(expolygon => $_),
@{diff_ex(
[ map $_->p, @$group ],
[ map @$_, @$to_bridge ],
)};
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALVOID,
), @{intersection_ex(
[ map $_->p, @$group ],
[ map @$_, @$to_bridge ],
)};
}
$lower_layerm->fill_surfaces->clear;
$lower_layerm->fill_surfaces->append($_) for @new_surfaces;
}
$excess -= $self->get_layer($i)->height;
}
}
*/
}
}
}
#ifdef SLIC3RXS
REGISTER_CLASS(PrintObject, "Print::Object");

View File

@ -53,8 +53,10 @@ PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_la
size_t extruder; // 1-based
if (role == frPerimeter || role == frExternalPerimeter) {
extruder = this->config.perimeter_extruder;
} else if (role == frInfill || role == frSolidInfill || role == frTopSolidInfill) {
} else if (role == frInfill) {
extruder = this->config.infill_extruder;
} else if (role == frSolidInfill || role == frTopSolidInfill) {
extruder = this->config.solid_infill_extruder;
} else {
CONFESS("Unknown role $role");
}

View File

@ -0,0 +1,11 @@
#ifndef slic3r_SupportMaterial_hpp_
#define slic3r_SupportMaterial_hpp_
namespace Slic3r {
// how much we extend support around the actual contact area
#define SUPPORT_MATERIAL_MARGIN 1.5
}
#endif

View File

@ -14,7 +14,8 @@ Surface::is_solid() const
return this->surface_type == stTop
|| this->surface_type == stBottom
|| this->surface_type == stBottomBridge
|| this->surface_type == stInternalSolid;
|| this->surface_type == stInternalSolid
|| this->surface_type == stInternalBridge;
}
bool
@ -25,6 +26,15 @@ Surface::is_external() const
|| this->surface_type == stBottomBridge;
}
bool
Surface::is_internal() const
{
return this->surface_type == stInternal
|| this->surface_type == stInternalBridge
|| this->surface_type == stInternalSolid
|| this->surface_type == stInternalVoid;
}
bool
Surface::is_bottom() const
{

View File

@ -24,6 +24,7 @@ class Surface
double area() const;
bool is_solid() const;
bool is_external() const;
bool is_internal() const;
bool is_bottom() const;
bool is_bridge() const;

View File

@ -68,6 +68,39 @@ SurfaceCollection::group(std::vector<SurfacesPtr> *retval)
}
}
template <class T>
bool
SurfaceCollection::any_internal_contains(const T &item) const
{
for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
if (surface->is_internal() && surface->expolygon.contains(item)) return true;
}
return false;
}
template bool SurfaceCollection::any_internal_contains<Line>(const Line &item) const;
template bool SurfaceCollection::any_internal_contains<Polyline>(const Polyline &item) const;
SurfacesPtr
SurfaceCollection::filter_by_type(SurfaceType type)
{
SurfacesPtr ss;
for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
if (surface->surface_type == type) ss.push_back(&*surface);
}
return ss;
}
void
SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons)
{
for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
if (surface->surface_type == type) {
Polygons pp = surface->expolygon;
polygons->insert(polygons->end(), pp.begin(), pp.end());
}
}
}
#ifdef SLIC3RXS
REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
#endif

View File

@ -15,6 +15,9 @@ class SurfaceCollection
operator ExPolygons() const;
void simplify(double tolerance);
void group(std::vector<SurfacesPtr> *retval);
template <class T> bool any_internal_contains(const T &item) const;
SurfacesPtr filter_by_type(SurfaceType type);
void filter_by_type(SurfaceType type, Polygons* polygons);
};
}

View File

@ -5,7 +5,7 @@ use warnings;
use List::Util qw(sum);
use Slic3r::XS;
use Test::More tests => 45;
use Test::More tests => 46;
{
my $square = [
@ -119,4 +119,29 @@ use Test::More tests => 45;
}
}
{
my @polylines = (
Slic3r::Polyline->new([59312736,4821067],[64321068,4821067],[64321068,4821067],[64321068,9321068],[59312736,9321068]),
Slic3r::Polyline->new([59312736,9321068],[9829401,9321068]),
Slic3r::Polyline->new([9829401,9321068],[4821067,9321068],[4821067,4821067],[9829401,4821067]),
Slic3r::Polyline->new([9829401,4821067],[59312736,4821067]),
);
my $loop = Slic3r::ExtrusionLoop->new;
$loop->append($_) for (
Slic3r::ExtrusionPath->new(polyline => $polylines[0], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1),
Slic3r::ExtrusionPath->new(polyline => $polylines[1], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1),
Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1),
Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1),
);
my $point = Slic3r::Point->new(4821067,9321068);
$loop->split_at_vertex($point) or $loop->split_at($point);
is_deeply [ map $_->role, @$loop ], [
Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER,
Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER,
Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER,
Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER,
Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER,
], 'order is correctly preserved after splitting';
}
__END__

View File

@ -26,7 +26,9 @@ my $loop = Slic3r::ExtrusionLoop->new_from_paths(
),
);
my $collection = Slic3r::ExtrusionPath::Collection->new($path);
my $collection = Slic3r::ExtrusionPath::Collection->new(
$path,
);
isa_ok $collection, 'Slic3r::ExtrusionPath::Collection', 'collection object with items in constructor';
ok !$collection->no_sort, 'no_sort is false by default';

View File

@ -4,7 +4,7 @@ use strict;
use warnings;
use Slic3r::XS;
use Test::More tests => 107;
use Test::More tests => 108;
foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) {
$config->set('layer_height', 0.3);
@ -182,6 +182,13 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) {
is $config->get('perimeter_extruder'), 3, 'defined extruder is not overwritten by default extruder';
}
{
my $config = Slic3r::Config->new;
$config->set('infill_extruder', 2);
$config->normalize;
is $config->get('solid_infill_extruder'), 2, 'undefined solid infill extruder is populated with infill extruder';
}
{
my $config = Slic3r::Config->new;
$config->set('spiral_vase', 1);

View File

@ -10,9 +10,9 @@ use Test::More tests => 4;
my $model = Slic3r::Model->new;
my $object = $model->_add_object;
isa_ok $object, 'Slic3r::Model::Object::Ref';
isa_ok $object->origin_translation, 'Slic3r::Pointf::Ref';
$object->origin_translation->translate(10,0);
is_deeply \@{$object->origin_translation}, [10,0], 'origin_translation is modified by ref';
isa_ok $object->origin_translation, 'Slic3r::Pointf3::Ref';
$object->origin_translation->translate(10,0,0);
is_deeply \@{$object->origin_translation}, [10,0,0], 'origin_translation is modified by ref';
my $lhr = [ [ 5, 10, 0.1 ] ];
$object->set_layer_height_ranges($lhr);

View File

@ -83,6 +83,7 @@ new_from_points(CLASS, points)
Clone<BoundingBoxf3> clone()
%code{% RETVAL = THIS; %};
void merge(BoundingBoxf3* bb) %code{% THIS->merge(*bb); %};
void merge_point(Pointf3* point) %code{% THIS->merge(*point); %};
void scale(double factor);
void translate(double x, double y, double z);
Clone<Pointf3> size();

View File

@ -23,6 +23,8 @@
%code{% THIS->apply(*other, true); %};
std::vector<std::string> diff(DynamicPrintConfig* other)
%code{% RETVAL = THIS->diff(*other); %};
bool equals(DynamicPrintConfig* other)
%code{% RETVAL = THIS->equals(*other); %};
void apply_static(FullPrintConfig* other)
%code{% THIS->apply(*other, true); %};
std::vector<std::string> get_keys()

View File

@ -28,6 +28,9 @@
%code{% THIS->clip_end(distance, &RETVAL); %};
bool has_overhang_point(Point* point)
%code{% RETVAL = THIS->has_overhang_point(*point); %};
bool is_perimeter();
bool is_infill();
bool is_solid_infill();
%{
SV*

View File

@ -23,7 +23,8 @@
void simplify(double tolerance);
double length();
bool is_perimeter();
bool is_fill();
bool is_infill();
bool is_solid_infill();
bool is_bridge();
std::string gcode(Extruder* extruder, double e, double F,
double xofs, double yofs, std::string extrusion_axis,

View File

@ -44,6 +44,7 @@
std::string unretract();
std::string lift();
std::string unlift();
Clone<Pointf3> get_position() const;
%{
SV*

View File

@ -69,6 +69,12 @@
%code%{ RETVAL = (int)(intptr_t)THIS; %};
void make_slices();
bool any_internal_region_slice_contains_line(Line* line)
%code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %};
bool any_internal_region_fill_surface_contains_line(Line* line)
%code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %};
bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline)
%code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %};
};
%name{Slic3r::Layer::Support} class SupportLayer {
@ -114,4 +120,11 @@
Ref<ExPolygonCollection> slices()
%code%{ RETVAL = &THIS->slices; %};
bool any_internal_region_slice_contains_line(Line* line)
%code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %};
bool any_internal_region_fill_surface_contains_line(Line* line)
%code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %};
bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline)
%code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %};
};

View File

@ -32,6 +32,7 @@
Clone<Point> point_at(double distance);
Polyline* as_polyline()
%code{% RETVAL = new Polyline(*THIS); %};
Clone<Point> normal();
%{
Line*
@ -65,3 +66,17 @@ Line::coincides_with(line_sv)
%}
};
%name{Slic3r::Linef3} class Linef3 {
Linef3(Pointf3* a, Pointf3* b)
%code{% RETVAL = new Linef3(*a, *b); %};
~Linef3();
Clone<Linef3> clone()
%code{% RETVAL = THIS; %};
Ref<Pointf3> a()
%code{% RETVAL = &THIS->a; %};
Ref<Pointf3> b()
%code{% RETVAL = &THIS->b; %};
Clone<Pointf3> intersect_plane(double z);
};

View File

@ -20,6 +20,8 @@
void clear_objects();
size_t objects_count()
%code%{ RETVAL = THIS->objects.size(); %};
Ref<ModelObject> get_object(int idx)
%code%{ RETVAL = THIS->objects.at(idx); %};
Ref<ModelMaterial> get_material(t_model_material_id material_id)
%code%{
@ -188,9 +190,9 @@ ModelMaterial::attributes()
void set_layer_height_ranges(t_layer_height_ranges ranges)
%code%{ THIS->layer_height_ranges = ranges; %};
Ref<Pointf> origin_translation()
Ref<Pointf3> origin_translation()
%code%{ RETVAL = &THIS->origin_translation; %};
void set_origin_translation(Pointf* point)
void set_origin_translation(Pointf3* point)
%code%{ THIS->origin_translation = *point; %};
bool needed_repair() const;

View File

@ -31,6 +31,8 @@
%code{% RETVAL = THIS->distance_to(*line); %};
double ccw(Point* p1, Point* p2)
%code{% RETVAL = THIS->ccw(*p1, *p2); %};
double ccw_angle(Point* p1, Point* p2)
%code{% RETVAL = THIS->ccw_angle(*p1, *p2); %};
Clone<Point> projection_onto_polygon(Polygon* polygon)
%code{% RETVAL = new Point(THIS->projection_onto(*polygon)); %};
Clone<Point> projection_onto_polyline(Polyline* polyline)
@ -93,10 +95,18 @@ Point::coincides_with(point_sv)
%code{% RETVAL = THIS->x; %};
double y()
%code{% RETVAL = THIS->y; %};
void set_x(double val)
%code{% THIS->x = val; %};
void set_y(double val)
%code{% THIS->y = val; %};
void translate(double x, double y);
void scale(double factor);
void rotate(double angle, Pointf* center)
%code{% THIS->rotate(angle, *center); %};
Clone<Pointf> negative()
%code{% RETVAL = THIS->negative(); %};
Clone<Pointf> vector_to(Pointf* point)
%code{% RETVAL = THIS->vector_to(*point); %};
};
%name{Slic3r::Pointf3} class Pointf3 {
@ -116,4 +126,12 @@ Point::coincides_with(point_sv)
%code{% THIS->y = val; %};
void set_z(double val)
%code{% THIS->z = val; %};
void translate(double x, double y, double z);
void scale(double factor);
double distance_to(Pointf3* point)
%code{% RETVAL = THIS->distance_to(*point); %};
Clone<Pointf3> negative()
%code{% RETVAL = THIS->negative(); %};
Clone<Pointf3> vector_to(Pointf3* point)
%code{% RETVAL = THIS->vector_to(*point); %};
};

View File

@ -47,6 +47,10 @@
THIS->bounding_box(RETVAL);
%};
std::string wkt();
Points concave_points(double angle)
%code{% THIS->concave_points(angle, &RETVAL); %};
Points convex_points(double angle)
%code{% THIS->convex_points(angle, &RETVAL); %};
%{
Polygon*

View File

@ -61,6 +61,11 @@ _constant()
%code%{ RETVAL = THIS->layer_height_ranges; %};
Ref<Point3> size()
%code%{ RETVAL = &THIS->size; %};
BoundingBox* bounding_box()
%code{%
RETVAL = new BoundingBox();
THIS->bounding_box(RETVAL);
%};
Ref<Point> _copies_shift()
%code%{ RETVAL = &THIS->_copies_shift; %};
@ -83,6 +88,7 @@ _constant()
void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges)
%code%{ THIS->layer_height_ranges = layer_height_ranges; %};
size_t total_layer_count();
size_t layer_count();
void clear_layers();
Ref<Layer> get_layer(int idx);
@ -106,6 +112,8 @@ _constant()
void set_step_started(PrintObjectStep step)
%code%{ THIS->state.set_started(step); %};
void bridge_over_infill();
int ptr()
%code%{ RETVAL = (int)(intptr_t)THIS; %};
};
@ -133,12 +141,6 @@ _constant()
%code%{ RETVAL = &THIS->objects; %};
void clear_objects();
Ref<PrintObject> get_object(int idx);
Ref<PrintObject> add_object(ModelObject* model_object,
BoundingBoxf3 *modobj_bbox)
%code%{ RETVAL = THIS->add_object(model_object, *modobj_bbox); %};
Ref<PrintObject> set_new_object(size_t idx, ModelObject* model_object,
BoundingBoxf3 *modobj_bbox)
%code%{ RETVAL = THIS->set_new_object(idx, model_object, *modobj_bbox); %};
void delete_object(int idx);
void reload_object(int idx);
size_t object_count()
@ -156,6 +158,8 @@ _constant()
bool invalidate_all_steps();
bool step_done(PrintStep step)
%code%{ RETVAL = THIS->state.is_done(step); %};
bool object_step_done(PrintObjectStep step)
%code%{ RETVAL = THIS->step_done(step); %};
void set_step_done(PrintStep step)
%code%{ THIS->state.set_done(step); %};
void set_step_started(PrintStep step)
@ -185,6 +189,11 @@ _constant()
croak("%s\n", e.what());
}
%};
Clone<BoundingBox> bounding_box();
Clone<BoundingBox> total_bounding_box();
double skirt_first_layer_height();
Clone<Flow> brim_flow();
Clone<Flow> skirt_flow();
%{
double

View File

@ -0,0 +1,16 @@
%module{Slic3r::XS};
#include <myinit.h>
#include "libslic3r/SupportMaterial.hpp"
%package{Slic3r::Print::SupportMaterial};
%{
SV*
MARGIN()
PROTOTYPE:
CODE:
RETVAL = newSVnv(SUPPORT_MATERIAL_MARGIN);
OUTPUT: RETVAL
%}

View File

@ -17,6 +17,7 @@
double area();
bool is_solid() const;
bool is_external() const;
bool is_internal() const;
bool is_bottom() const;
bool is_bridge() const;
%{

Some files were not shown because too many files have changed in this diff Show More