Work in progress: Good bye, Perl Threads!

This commit is contained in:
bubnikv 2018-03-23 11:41:20 +01:00
parent 86b79f89ad
commit e931f75010
31 changed files with 833 additions and 1069 deletions

View File

@ -20,9 +20,7 @@ my %prereqs = qw(
POSIX 0 POSIX 0
Scalar::Util 0 Scalar::Util 0
Test::More 0 Test::More 0
Thread::Semaphore 0
IO::Scalar 0 IO::Scalar 0
threads 1.96
Time::HiRes 0 Time::HiRes 0
); );
my %recommends = qw( my %recommends = qw(
@ -44,8 +42,6 @@ if ($gui) {
); );
if ($^O eq 'MSWin32') { if ($^O eq 'MSWin32') {
$recommends{"Win32::TieRegistry"} = 0; $recommends{"Win32::TieRegistry"} = 0;
# we need an up-to-date Win32::API because older aren't thread-safe (GH #2517)
$prereqs{'Win32::API'} = 0.79;
} }
} }

View File

@ -1,5 +1,4 @@
# This package loads all the non-GUI Slic3r perl packages. # This package loads all the non-GUI Slic3r perl packages.
# In addition, it implements utility functions for file handling and threading.
package Slic3r; package Slic3r;
@ -22,22 +21,11 @@ sub debugf {
our $loglevel = 0; our $loglevel = 0;
# load threads before Moo as required by it
BEGIN { BEGIN {
# Test, whether the perl was compiled with ithreads support and ithreads actually work.
use Config;
use Moo;
my $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
die "Slic3r Prusa Edition requires working Perl threads.\n" if ! $have_threads;
die "threads.pm >= 1.96 is required, please update\n" if $threads::VERSION < 1.96;
die "Perl threading is broken with this Moo version: " . $Moo::VERSION . "\n" if $Moo::VERSION == 1.003000;
$debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1); $debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1);
print "Debugging output enabled\n" if $debug; print "Debugging output enabled\n" if $debug;
} }
warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n"
if $^V == v5.16;
use FindBin; use FindBin;
# Let the XS module know where the GUI resources reside. # Let the XS module know where the GUI resources reside.
@ -66,17 +54,11 @@ use Slic3r::Print::Object;
use Slic3r::Print::Simple; use Slic3r::Print::Simple;
use Slic3r::Surface; use Slic3r::Surface;
our $build = eval "use Slic3r::Build; 1"; our $build = eval "use Slic3r::Build; 1";
use Thread::Semaphore;
# Scaling between the float and integer coordinates. # Scaling between the float and integer coordinates.
# Floats are in mm. # Floats are in mm.
use constant SCALING_FACTOR => 0.000001; use constant SCALING_FACTOR => 0.000001;
# Keep track of threads we created. Perl worker threads shall not create further threads.
my @threads = ();
my $pause_sema = Thread::Semaphore->new;
my $paused = 0;
# Set the logging level at the Slic3r XS module. # Set the logging level at the Slic3r XS module.
$Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0; $Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0;
set_logging_level($Slic3r::loglevel); set_logging_level($Slic3r::loglevel);
@ -85,121 +67,6 @@ set_logging_level($Slic3r::loglevel);
# class instance in a thread safe manner. # class instance in a thread safe manner.
Slic3r::GCode::PlaceholderParser->new->evaluate_boolean_expression('1==1'); Slic3r::GCode::PlaceholderParser->new->evaluate_boolean_expression('1==1');
sub spawn_thread {
my ($cb) = @_;
@_ = ();
my $thread = threads->create(sub {
Slic3r::debugf "Starting thread %d...\n", threads->tid;
local $SIG{'KILL'} = sub {
Slic3r::debugf "Exiting thread %d...\n", threads->tid;
Slic3r::thread_cleanup();
threads->exit();
};
local $SIG{'STOP'} = sub {
$pause_sema->down;
$pause_sema->up;
};
$cb->();
});
push @threads, $thread->tid;
return $thread;
}
# call this at the very end of each thread (except the main one)
# so that it does not try to free existing objects.
# at that stage, existing objects are only those that we
# inherited at the thread creation (thus shared) and those
# that we are returning: destruction will be handled by the
# main thread in both cases.
# reminder: do not destroy inherited objects in other threads,
# as the main thread will still try to destroy them when they
# go out of scope; in other words, if you're undef()'ing an
# object in a thread, make sure the main thread still holds a
# reference so that it won't be destroyed in thread.
sub thread_cleanup {
# prevent destruction of shared objects
no warnings 'redefine';
*Slic3r::BridgeDetector::DESTROY = sub {};
*Slic3r::Config::DESTROY = sub {};
*Slic3r::Config::Full::DESTROY = sub {};
*Slic3r::Config::GCode::DESTROY = sub {};
*Slic3r::Config::Print::DESTROY = sub {};
*Slic3r::Config::PrintObject::DESTROY = sub {};
*Slic3r::Config::PrintRegion::DESTROY = sub {};
*Slic3r::Config::Static::DESTROY = sub {};
*Slic3r::ExPolygon::DESTROY = sub {};
*Slic3r::ExPolygon::Collection::DESTROY = sub {};
*Slic3r::ExtrusionLoop::DESTROY = sub {};
*Slic3r::ExtrusionMultiPath::DESTROY = sub {};
*Slic3r::ExtrusionPath::DESTROY = sub {};
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
*Slic3r::ExtrusionSimulator::DESTROY = sub {};
*Slic3r::Flow::DESTROY = sub {};
*Slic3r::GCode::DESTROY = sub {};
*Slic3r::GCode::PlaceholderParser::DESTROY = sub {};
*Slic3r::GCode::PreviewData::DESTROY = sub {};
*Slic3r::GCode::Sender::DESTROY = sub {};
*Slic3r::Geometry::BoundingBox::DESTROY = sub {};
*Slic3r::Geometry::BoundingBoxf::DESTROY = sub {};
*Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {};
*Slic3r::Layer::PerimeterGenerator::DESTROY = sub {};
*Slic3r::Line::DESTROY = sub {};
*Slic3r::Linef3::DESTROY = sub {};
*Slic3r::Model::DESTROY = sub {};
*Slic3r::Model::Object::DESTROY = sub {};
*Slic3r::Point::DESTROY = sub {};
*Slic3r::Pointf::DESTROY = sub {};
*Slic3r::Pointf3::DESTROY = sub {};
*Slic3r::Polygon::DESTROY = sub {};
*Slic3r::Polyline::DESTROY = sub {};
*Slic3r::Polyline::Collection::DESTROY = sub {};
*Slic3r::Print::DESTROY = sub {};
*Slic3r::Print::Object::DESTROY = sub {};
*Slic3r::Print::Region::DESTROY = sub {};
*Slic3r::Surface::DESTROY = sub {};
*Slic3r::Surface::Collection::DESTROY = sub {};
*Slic3r::Print::SupportMaterial2::DESTROY = sub {};
*Slic3r::TriangleMesh::DESTROY = sub {};
*Slic3r::GUI::AppConfig::DESTROY = sub {};
*Slic3r::GUI::PresetBundle::DESTROY = sub {};
return undef; # this prevents a "Scalars leaked" warning
}
sub _get_running_threads {
return grep defined($_), map threads->object($_), @threads;
}
sub kill_all_threads {
# Send SIGKILL to all the running threads to let them die.
foreach my $thread (_get_running_threads) {
Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid;
$thread->kill('KILL');
}
# unlock semaphore before we block on wait
# otherwise we'd get a deadlock if threads were paused
resume_all_threads();
# in any thread we wait for our children
foreach my $thread (_get_running_threads) {
Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid;
$thread->join; # block until threads are killed
Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid;
}
@threads = ();
}
sub pause_all_threads {
return if $paused;
$paused = 1;
$pause_sema->down;
$_->kill('STOP') for _get_running_threads;
}
sub resume_all_threads {
return unless $paused;
$paused = 0;
$pause_sema->up;
}
# Open a file by converting $filename to local file system locales. # Open a file by converting $filename to local file system locales.
sub open { sub open {
my ($fh, $mode, $filename) = @_; my ($fh, $mode, $filename) = @_;
@ -274,8 +141,4 @@ sub system_info
return $out; return $out;
} }
# this package declaration prevents an ugly fatal warning to be emitted when
# spawning a new thread
package GLUquadricObjPtr;
1; 1;

View File

@ -13,12 +13,6 @@ sub wkt {
join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self; join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self;
} }
sub dump_perl {
my $self = shift;
return sprintf "[%s]",
join ',', map "[$_]", map { join ',', map "[$_->[0],$_->[1]]", @$_ } @$self;
}
sub offset { sub offset {
my $self = shift; my $self = shift;
return Slic3r::Geometry::Clipper::offset(\@$self, @_); return Slic3r::Geometry::Clipper::offset(\@$self, @_);

View File

@ -101,12 +101,6 @@ use constant GIMBALL_LOCK_THETA_MAX => 170;
use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70; use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70;
use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22; use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22;
# make OpenGL::Array thread-safe
{
no warnings 'redefine';
*OpenGL::Array::CLONE_SKIP = sub { 1 };
}
sub new { sub new {
my ($class, $parent) = @_; my ($class, $parent) = @_;

View File

@ -25,6 +25,10 @@ our $last_config;
our $VALUE_CHANGE_EVENT = Wx::NewEventType; our $VALUE_CHANGE_EVENT = Wx::NewEventType;
# 2) To inform about a preset selection change or a "modified" status change. # 2) To inform about a preset selection change or a "modified" status change.
our $PRESETS_CHANGED_EVENT = Wx::NewEventType; our $PRESETS_CHANGED_EVENT = Wx::NewEventType;
# 3) To update the status bar with the progress information.
our $PROGRESS_BAR_EVENT = Wx::NewEventType;
# 4) To display an error dialog box from a thread on the UI thread.
our $ERROR_EVENT = Wx::NewEventType;
sub new { sub new {
my ($class, %params) = @_; my ($class, %params) = @_;
@ -48,6 +52,11 @@ sub new {
$self->{lang_ch_event} = $params{lang_ch_event}; $self->{lang_ch_event} = $params{lang_ch_event};
$self->{preferences_event} = $params{preferences_event}; $self->{preferences_event} = $params{preferences_event};
# initialize status bar
$self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1);
$self->{statusbar}->SetStatusText(L("Version ").$Slic3r::VERSION.L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"));
$self->SetStatusBar($self->{statusbar});
# initialize tabpanel and menubar # initialize tabpanel and menubar
$self->_init_tabpanel; $self->_init_tabpanel;
$self->_init_menubar; $self->_init_menubar;
@ -57,11 +66,6 @@ sub new {
# (SetAutoPop is not available on GTK.) # (SetAutoPop is not available on GTK.)
eval { Wx::ToolTip::SetAutoPop(32767) }; eval { Wx::ToolTip::SetAutoPop(32767) };
# initialize status bar
$self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1);
$self->{statusbar}->SetStatusText(L("Version ").$Slic3r::VERSION.L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"));
$self->SetStatusBar($self->{statusbar});
$self->{loaded} = 1; $self->{loaded} = 1;
# initialize layout # initialize layout
@ -171,6 +175,24 @@ sub _init_tabpanel {
$self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name"); $self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name");
} }
# Update progress bar with an event sent by the slicing core.
EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub {
my ($self, $event) = @_;
if (defined $self->{progress_dialog}) {
# If a progress dialog is open, update it.
$self->{progress_dialog}->Update($event->GetInt, $event->GetString . "…");
} else {
# Otherwise update the main window status bar.
$self->{statusbar}->SetProgress($event->GetInt);
$self->{statusbar}->SetStatusText($event->GetString . "…");
}
});
EVT_COMMAND($self, -1, $ERROR_EVENT, sub {
my ($self, $event) = @_;
Slic3r::GUI::show_error($self, $event->GetString);
});
if ($self->{plater}) { if ($self->{plater}) {
$self->{plater}->on_select_preset(sub { $self->{plater}->on_select_preset(sub {
my ($group, $name) = @_; my ($group, $name) = @_;
@ -388,7 +410,6 @@ sub on_plater_selection_changed {
sub quick_slice { sub quick_slice {
my ($self, %params) = @_; my ($self, %params) = @_;
my $progress_dialog;
eval { eval {
# validate configuration # validate configuration
my $config = wxTheApp->{preset_bundle}->full_config(); my $config = wxTheApp->{preset_bundle}->full_config();
@ -429,13 +450,9 @@ sub quick_slice {
$print_center = Slic3r::Pointf->new_unscale(@{$bed_shape->bounding_box->center}); $print_center = Slic3r::Pointf->new_unscale(@{$bed_shape->bounding_box->center});
} }
my $sprint = Slic3r::Print::Simple->new( my $sprint = Slic3r::Print::Simple->new(print_center => $print_center);
print_center => $print_center, # The C++ slicing core will post a wxCommand message to the main window.
status_cb => sub { Slic3r::GUI::set_print_callback_event($sprint, $PROGRESS_BAR_EVENT);
my ($percent, $message) = @_;
$progress_dialog->Update($percent, "$message…");
},
);
# keep model around # keep model around
my $model = Slic3r::Model->read_from_file($input_file); my $model = Slic3r::Model->read_from_file($input_file);
@ -468,9 +485,9 @@ sub quick_slice {
} }
# show processbar dialog # show processbar dialog
$progress_dialog = Wx::ProgressDialog->new(L('Slicing…'), L("Processing ").$input_file_basename."…", $self->{progress_dialog} = Wx::ProgressDialog->new(L('Slicing…'), L("Processing ").$input_file_basename."…",
100, $self, 0); 100, $self, 0);
$progress_dialog->Pulse; $self->{progress_dialog}->Pulse;
{ {
my @warnings = (); my @warnings = ();
@ -482,18 +499,17 @@ sub quick_slice {
} else { } else {
$sprint->export_gcode; $sprint->export_gcode;
} }
$sprint->status_cb(undef);
Slic3r::GUI::warning_catcher($self)->($_) for @warnings; Slic3r::GUI::warning_catcher($self)->($_) for @warnings;
} }
$progress_dialog->Destroy; $self->{progress_dialog}->Destroy;
undef $progress_dialog; undef $self->{progress_dialog};
my $message = $input_file_basename.L(" was successfully sliced."); my $message = $input_file_basename.L(" was successfully sliced.");
wxTheApp->notify($message); wxTheApp->notify($message);
Wx::MessageDialog->new($self, $message, L('Slicing Done!'), Wx::MessageDialog->new($self, $message, L('Slicing Done!'),
wxOK | wxICON_INFORMATION)->ShowModal; wxOK | wxICON_INFORMATION)->ShowModal;
}; };
Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog }); Slic3r::GUI::catch_error($self, sub { $self->{progress_dialog}->Destroy if $self->{progress_dialog} });
} }
sub reslice_now { sub reslice_now {

View File

@ -8,7 +8,6 @@ use utf8;
use File::Basename qw(basename dirname); use File::Basename qw(basename dirname);
use List::Util qw(sum first max); use List::Util qw(sum first max);
use Slic3r::Geometry qw(X Y Z scale unscale deg2rad rad2deg); use Slic3r::Geometry qw(X Y Z scale unscale deg2rad rad2deg);
use threads::shared qw(shared_clone);
use Wx qw(:button :colour :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc use Wx qw(:button :colour :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
:panel :sizer :toolbar :window wxTheApp :notebook :combobox wxNullBitmap); :panel :sizer :toolbar :window wxTheApp :notebook :combobox wxNullBitmap);
use Wx::Event qw(EVT_BUTTON EVT_TOGGLEBUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED use Wx::Event qw(EVT_BUTTON EVT_TOGGLEBUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED
@ -34,21 +33,16 @@ use constant TB_LAYER_EDITING => &Wx::NewId;
use Wx::Locale gettext => 'L'; use Wx::Locale gettext => 'L';
# package variables to avoid passing lexicals to threads
our $PROGRESS_BAR_EVENT : shared = Wx::NewEventType;
our $ERROR_EVENT : shared = Wx::NewEventType;
# Emitted from the worker thread when the G-code export is finished. # Emitted from the worker thread when the G-code export is finished.
our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType; our $SLICING_COMPLETED_EVENT = Wx::NewEventType;
our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType; our $PROCESS_COMPLETED_EVENT = Wx::NewEventType;
use constant FILAMENT_CHOOSERS_SPACING => 0;
use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds
my $PreventListEvents = 0; my $PreventListEvents = 0;
sub new { sub new {
my ($class, $parent) = @_; my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
Slic3r::GUI::set_plater($self);
$self->{config} = Slic3r::Config::new_from_defaults_keys([qw( $self->{config} = Slic3r::Config::new_from_defaults_keys([qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height
serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile
@ -63,14 +57,14 @@ sub new {
# List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter. # List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter.
$self->{objects} = []; $self->{objects} = [];
$self->{gcode_preview_data} = Slic3r::GCode::PreviewData->new; $self->{gcode_preview_data} = Slic3r::GCode::PreviewData->new;
$self->{background_slicing_process} = Slic3r::GUI::BackgroundSlicingProcess->new;
$self->{background_slicing_process}->set_print($self->{print});
$self->{background_slicing_process}->set_gcode_preview_data($self->{gcode_preview_data});
$self->{background_slicing_process}->set_sliced_event($SLICING_COMPLETED_EVENT);
$self->{background_slicing_process}->set_finished_event($PROCESS_COMPLETED_EVENT);
$self->{print}->set_status_cb(sub { # The C++ slicing core will post a wxCommand message to the main window.
my ($percent, $message) = @_; Slic3r::GUI::set_print_callback_event($self->{print}, $Slic3r::GUI::MainFrame::PROGRESS_BAR_EVENT);
my $event = Wx::CommandEvent->new($PROGRESS_BAR_EVENT);
$event->SetString($message);
$event->SetInt($percent);
Wx::PostEvent($self, $event);
});
# Initialize preview notebook # Initialize preview notebook
$self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM); $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM);
@ -319,19 +313,9 @@ sub new {
for grep defined($_), for grep defined($_),
$self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list};
EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub { EVT_COMMAND($self, -1, $SLICING_COMPLETED_EVENT, sub {
my ($self, $event) = @_; my ($self, $event) = @_;
$self->on_progress_event($event->GetInt, $event->GetString); $self->on_update_print_preview;
});
EVT_COMMAND($self, -1, $ERROR_EVENT, sub {
my ($self, $event) = @_;
Slic3r::GUI::show_error($self, $event->GetString);
});
EVT_COMMAND($self, -1, $EXPORT_COMPLETED_EVENT, sub {
my ($self, $event) = @_;
$self->on_export_completed($event->GetInt);
}); });
EVT_COMMAND($self, -1, $PROCESS_COMPLETED_EVENT, sub { EVT_COMMAND($self, -1, $PROCESS_COMPLETED_EVENT, sub {
@ -788,8 +772,7 @@ sub bed_centerf {
} }
sub remove { sub remove {
my $self = shift; my ($self, $obj_idx) = @_;
my ($obj_idx) = @_;
$self->stop_background_process; $self->stop_background_process;
@ -797,7 +780,7 @@ sub remove {
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D}; $self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
$self->{preview3D}->enabled(0) if $self->{preview3D}; $self->{preview3D}->enabled(0) if $self->{preview3D};
# if no object index is supplied, remove the selected one # If no object index is supplied, remove the selected one.
if (! defined $obj_idx) { if (! defined $obj_idx) {
($obj_idx, undef) = $self->selected_object; ($obj_idx, undef) = $self->selected_object;
return if ! defined $obj_idx; return if ! defined $obj_idx;
@ -811,11 +794,10 @@ sub remove {
$self->select_object(undef); $self->select_object(undef);
$self->update; $self->update;
$self->schedule_background_process;
} }
sub reset { sub reset {
my $self = shift; my ($self) = @_;
$self->stop_background_process; $self->stop_background_process;
@ -840,6 +822,7 @@ sub increase {
return if ! defined $obj_idx; return if ! defined $obj_idx;
my $model_object = $self->{model}->objects->[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx];
my $instance = $model_object->instances->[-1]; my $instance = $model_object->instances->[-1];
$self->stop_background_process;
for my $i (1..$copies) { for my $i (1..$copies) {
$instance = $model_object->add_instance( $instance = $model_object->add_instance(
offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}), offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}),
@ -849,15 +832,8 @@ sub increase {
$self->{print}->objects->[$obj_idx]->add_copy($instance->offset); $self->{print}->objects->[$obj_idx]->add_copy($instance->offset);
} }
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
# Only autoarrange if user has autocentering enabled.
# only autoarrange if user has autocentering enabled wxTheApp->{app_config}->get("autocenter") ? $self->arrange : $self->update;
$self->stop_background_process;
if (wxTheApp->{app_config}->get("autocenter")) {
$self->arrange;
} else {
$self->update;
}
$self->schedule_background_process;
} }
sub decrease { sub decrease {
@ -866,10 +842,9 @@ sub decrease {
my ($obj_idx, $object) = $self->selected_object; my ($obj_idx, $object) = $self->selected_object;
return if ! defined $obj_idx; return if ! defined $obj_idx;
$self->stop_background_process;
my $model_object = $self->{model}->objects->[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx];
if ($model_object->instances_count > $copies) { if ($model_object->instances_count > $copies) {
$self->stop_background_process;
for my $i (1..$copies) { for my $i (1..$copies) {
$model_object->delete_last_instance; $model_object->delete_last_instance;
$self->{print}->objects->[$obj_idx]->delete_last_copy; $self->{print}->objects->[$obj_idx]->delete_last_copy;
@ -877,10 +852,10 @@ sub decrease {
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
} elsif (defined $copies_asked) { } elsif (defined $copies_asked) {
# The "decrease" came from the "set number of copies" dialog. # The "decrease" came from the "set number of copies" dialog.
$self->stop_background_process;
$self->remove; $self->remove;
} else { } else {
# The "decrease" came from the "-" button. Don't allow the object to disappear. # The "decrease" came from the "-" button. Don't allow the object to disappear.
$self->resume_background_process;
return; return;
} }
@ -889,24 +864,18 @@ sub decrease {
$self->{list}->Select($obj_idx, 1); $self->{list}->Select($obj_idx, 1);
} }
$self->update; $self->update;
$self->schedule_background_process;
} }
sub set_number_of_copies { sub set_number_of_copies {
my ($self) = @_; my ($self) = @_;
$self->pause_background_process;
# get current number of copies # get current number of copies
my ($obj_idx, $object) = $self->selected_object; my ($obj_idx, $object) = $self->selected_object;
my $model_object = $self->{model}->objects->[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx];
# prompt user # prompt user
my $copies = Wx::GetNumberFromUser("", L("Enter the number of copies of the selected object:"), L("Copies"), $model_object->instances_count, 0, 1000, $self); my $copies = Wx::GetNumberFromUser("", L("Enter the number of copies of the selected object:"), L("Copies"), $model_object->instances_count, 0, 1000, $self);
my $diff = $copies - $model_object->instances_count; my $diff = $copies - $model_object->instances_count;
if ($diff == 0) { if ($diff == 0) {
# no variation # no variation
$self->resume_background_process;
} elsif ($diff > 0) { } elsif ($diff > 0) {
$self->increase($diff); $self->increase($diff);
} elsif ($diff < 0) { } elsif ($diff < 0) {
@ -981,7 +950,6 @@ sub rotate {
$self->selection_changed; # refresh info (size etc.) $self->selection_changed; # refresh info (size etc.)
$self->update; $self->update;
$self->schedule_background_process;
} }
sub mirror { sub mirror {
@ -1011,7 +979,6 @@ sub mirror {
$self->selection_changed; # refresh info (size etc.) $self->selection_changed; # refresh info (size etc.)
$self->update; $self->update;
$self->schedule_background_process;
} }
sub changescale { sub changescale {
@ -1086,14 +1053,12 @@ sub changescale {
$self->selection_changed(1); # refresh info (size, volume etc.) $self->selection_changed(1); # refresh info (size, volume etc.)
$self->update; $self->update;
$self->schedule_background_process;
} }
sub arrange { sub arrange {
my $self = shift; my ($self) = @_;
$self->pause_background_process;
$self->stop_background_process;
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape);
my $success = $self->{model}->arrange_objects(wxTheApp->{preset_bundle}->full_config->min_object_distance, $bb); my $success = $self->{model}->arrange_objects(wxTheApp->{preset_bundle}->full_config->min_object_distance, $bb);
# ignore arrange failures on purpose: user has visual feedback and we don't need to warn him # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
@ -1118,84 +1083,62 @@ sub split_object {
return; return;
} }
$self->pause_background_process; $self->stop_background_process;
my @model_objects = @{$current_model_object->split_object}; my @model_objects = @{$current_model_object->split_object};
if (@model_objects == 1) { if (@model_objects == 1) {
$self->resume_background_process;
Slic3r::GUI::warning_catcher($self)->(L("The selected object couldn't be split because it contains only one part.")); Slic3r::GUI::warning_catcher($self)->(L("The selected object couldn't be split because it contains only one part."));
$self->resume_background_process; $self->schedule_background_process;
return; } else {
$_->center_around_origin for (@model_objects);
$self->remove($obj_idx);
$current_object = $obj_idx = undef;
# load all model objects at once, otherwise the plate would be rearranged after each one
# causing original positions not to be kept
$self->load_model_objects(@model_objects);
} }
$_->center_around_origin for (@model_objects);
$self->remove($obj_idx);
$current_object = $obj_idx = undef;
# load all model objects at once, otherwise the plate would be rearranged after each one
# causing original positions not to be kept
$self->load_model_objects(@model_objects);
} }
# Trigger $self->async_apply_config() after 500ms.
# The call is delayed to avoid restarting the background processing during typing into an edit field.
sub schedule_background_process { sub schedule_background_process {
my ($self) = @_; my ($self) = @_;
$self->{apply_config_timer}->Start(0.5 * 1000, 1); # 1 = one shot, every half a second.
if (defined $self->{apply_config_timer}) {
$self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot
}
} }
# Executed asynchronously by a timer every PROCESS_DELAY (0.5 second). # Executed asynchronously by a timer every PROCESS_DELAY (0.5 second).
# The timer is started by schedule_background_process(), # The timer is started by schedule_background_process(),
sub async_apply_config { sub async_apply_config {
my ($self) = @_; my ($self) = @_;
# Apply new config to the possibly running background task.
# pause process thread before applying new config my $was_running = $self->{background_slicing_process}->running;
# since we don't want to touch data that is being used by the threads my $invalidated = $self->{background_slicing_process}->apply_config(wxTheApp->{preset_bundle}->full_config);
$self->pause_background_process; # Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
# apply new config
my $invalidated = $self->{print}->apply_config(wxTheApp->{preset_bundle}->full_config);
# Just redraw the 3D canvas without reloading the scene.
# $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled);
$self->{canvas3D}->Refresh if ($self->{canvas3D}->layer_editing_enabled); $self->{canvas3D}->Refresh if ($self->{canvas3D}->layer_editing_enabled);
# If the apply_config caused the calculation to stop, or it was not running yet:
# Hide the slicing results if the current slicing status is no more valid.
$self->{"print_info_box_show"}->(0) if $invalidated;
if (wxTheApp->{app_config}->get("background_processing")) {
if ($invalidated) {
# kill current thread if any
$self->stop_background_process;
} else {
$self->resume_background_process;
}
# schedule a new process thread in case it wasn't running
$self->start_background_process;
}
# Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared.
# Otherwise they will be just refreshed.
if ($invalidated) { if ($invalidated) {
$self->{gcode_preview_data}->reset; if ($was_running) {
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; # Hide the slicing results if the current slicing status is no more valid.
$self->{preview3D}->reload_print if $self->{preview3D}; $self->{"print_info_box_show"}->(0);
}
if (wxTheApp->{app_config}->get("background_processing")) {
$self->{background_slicing_process}->start;
}
if ($was_running) {
# Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared.
# Otherwise they will be just refreshed.
$self->{gcode_preview_data}->reset;
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D};
}
} }
} }
# Background processing is started either by the "Slice now" button, by the "Export G-code button" or by async_apply_config().
sub start_background_process { sub start_background_process {
my ($self) = @_; my ($self) = @_;
return if ! @{$self->{objects}} || $self->{background_slicing_process}->running;
return if !@{$self->{objects}}; # Don't start process thread if config is not valid.
return if $self->{process_thread};
# It looks like declaring a local $SIG{__WARN__} prevents the ugly
# "Attempt to free unreferenced scalar" warning...
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
# don't start process thread if config is not valid
eval { eval {
# this will throw errors if config is not valid # this will throw errors if config is not valid
wxTheApp->{preset_bundle}->full_config->validate; wxTheApp->{preset_bundle}->full_config->validate;
@ -1205,78 +1148,28 @@ sub start_background_process {
$self->statusbar->SetStatusText($@); $self->statusbar->SetStatusText($@);
return; return;
} }
# Copy the names of active presets into the placeholder parser. # Copy the names of active presets into the placeholder parser.
wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser);
# Start the background process.
# start thread $self->{background_slicing_process}->start;
@_ = ();
$self->{process_thread} = Slic3r::spawn_thread(sub {
eval {
$self->{print}->process;
};
my $event = Wx::CommandEvent->new($PROCESS_COMPLETED_EVENT);
if ($@) {
Slic3r::debugf "Background process error: $@\n";
$event->SetInt(0);
$event->SetString($@);
} else {
$event->SetInt(1);
}
Wx::PostEvent($self, $event);
Slic3r::thread_cleanup();
});
Slic3r::debugf "Background processing started.\n";
} }
# Stop the background processing
sub stop_background_process { sub stop_background_process {
my ($self) = @_; my ($self) = @_;
# Don't call async_apply_config() while stopped.
$self->{apply_config_timer}->Stop if defined $self->{apply_config_timer}; $self->{apply_config_timer}->Stop;
$self->statusbar->SetCancelCallback(undef); $self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy; $self->statusbar->StopBusy;
$self->statusbar->SetStatusText(""); $self->statusbar->SetStatusText("");
# Stop the background task.
$self->{background_slicing_process}->stop;
# Update the UI with the slicing results.
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D}; $self->{preview3D}->reload_print if $self->{preview3D};
if ($self->{process_thread}) {
Slic3r::debugf "Killing background process.\n";
Slic3r::kill_all_threads();
$self->{process_thread} = undef;
} else {
Slic3r::debugf "No background process running.\n";
}
# if there's an export process, kill that one as well
if ($self->{export_thread}) {
Slic3r::debugf "Killing background export process.\n";
Slic3r::kill_all_threads();
$self->{export_thread} = undef;
}
}
sub pause_background_process {
my ($self) = @_;
if ($self->{process_thread} || $self->{export_thread}) {
Slic3r::pause_all_threads();
return 1;
} elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) {
$self->{apply_config_timer}->Stop;
return 1;
}
return 0;
}
sub resume_background_process {
my ($self) = @_;
if ($self->{process_thread} || $self->{export_thread}) {
Slic3r::resume_all_threads();
}
} }
# Called by the "Slice now" button, which is visible only if the background processing is disabled.
sub reslice { sub reslice {
# explicitly cancel a previous thread and start a new one. # explicitly cancel a previous thread and start a new one.
my ($self) = @_; my ($self) = @_;
@ -1371,79 +1264,22 @@ sub export_gcode {
return $self->{export_gcode_output_file}; return $self->{export_gcode_output_file};
} }
# This gets called only if we have threads. # This message should be called by the background process synchronously.
sub on_process_completed { sub on_update_print_preview {
my ($self, $error) = @_; my ($self) = @_;
$self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy;
$self->statusbar->SetStatusText($error // "");
Slic3r::debugf "Background processing completed.\n";
$self->{process_thread}->detach if $self->{process_thread};
$self->{process_thread} = undef;
# if we're supposed to perform an explicit export let's display the error in a dialog
if ($error && $self->{export_gcode_output_file}) {
$self->{export_gcode_output_file} = undef;
Slic3r::GUI::show_error($self, $error);
}
return if $error;
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D}; $self->{preview3D}->reload_print if $self->{preview3D};
# if we have an export filename, start a new thread for exporting G-code
if ($self->{export_gcode_output_file}) {
@_ = ();
# workaround for "Attempt to free un referenced scalar..."
our $_thread_self = $self;
$self->{export_thread} = Slic3r::spawn_thread(sub {
eval {
$_thread_self->{print}->export_gcode(output_file => $_thread_self->{export_gcode_output_file}, gcode_preview_data => $_thread_self->{gcode_preview_data});
};
my $export_completed_event = Wx::CommandEvent->new($EXPORT_COMPLETED_EVENT);
if ($@) {
{
my $error_event = Wx::CommandEvent->new($ERROR_EVENT);
$error_event->SetString($@);
Wx::PostEvent($_thread_self, $error_event);
}
$export_completed_event->SetInt(0);
$export_completed_event->SetString($@);
} else {
$export_completed_event->SetInt(1);
}
Wx::PostEvent($_thread_self, $export_completed_event);
Slic3r::thread_cleanup();
});
Slic3r::debugf "Background G-code export started.\n";
}
}
# This gets called also if we have no threads.
sub on_progress_event {
my ($self, $percent, $message) = @_;
$self->statusbar->SetProgress($percent);
$self->statusbar->SetStatusText("$message…");
} }
# Called when the G-code export finishes, either successfully or with an error. # Called when the G-code export finishes, either successfully or with an error.
# This gets called also if we don't have threads. # This gets called also if we don't have threads.
sub on_export_completed { sub on_process_completed {
my ($self, $result) = @_; my ($self, $result) = @_;
$self->statusbar->SetCancelCallback(undef); $self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy; $self->statusbar->StopBusy;
$self->statusbar->SetStatusText(""); $self->statusbar->SetStatusText("");
Slic3r::debugf "Background export process completed.\n";
$self->{export_thread}->detach if $self->{export_thread};
$self->{export_thread} = undef;
my $message; my $message;
my $send_gcode = 0; my $send_gcode = 0;
my $do_print = 0; my $do_print = 0;
@ -1470,7 +1306,7 @@ sub on_export_completed {
# Send $self->{send_gcode_file} to OctoPrint. # Send $self->{send_gcode_file} to OctoPrint.
if ($send_gcode) { if ($send_gcode) {
my $op = Slic3r::OctoPrint->new($self->{config}); my $op = Slic3r::OctoPrint->new($self->{config});
$op->send_gcode($self->GetId(), $PROGRESS_BAR_EVENT, $ERROR_EVENT, $self->{send_gcode_file}); $op->send_gcode($self->GetId(), $Slic3r::GUI::MainFrame::PROGRESS_BAR_EVENT, $Slic3r::GUI::MainFrame::ERROR_EVENT, $self->{send_gcode_file});
} }
$self->{print_file} = undef; $self->{print_file} = undef;
@ -1636,27 +1472,15 @@ sub reset_thumbnail {
# (i.e. when an object is added/removed/moved/rotated/scaled) # (i.e. when an object is added/removed/moved/rotated/scaled)
sub update { sub update {
my ($self, $force_autocenter) = @_; my ($self, $force_autocenter) = @_;
if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) { if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) {
$self->{model}->center_instances_around_point($self->bed_centerf); $self->{model}->center_instances_around_point($self->bed_centerf);
} }
$self->stop_background_process;
my $running = $self->pause_background_process; $self->{print}->reload_model_instances();
my $invalidated = $self->{print}->reload_model_instances();
# The mere fact that no steps were invalidated when reloading model instances
# doesn't mean that all steps were done: for example, validation might have
# failed upon previous instance move, so we have no running thread and no steps
# are invalidated on this move, thus we need to schedule a new run.
if ($invalidated || !$running) {
$self->schedule_background_process;
} else {
$self->resume_background_process;
}
$self->{canvas}->reload_scene if $self->{canvas}; $self->{canvas}->reload_scene if $self->{canvas};
$self->{canvas3D}->reload_scene if $self->{canvas3D}; $self->{canvas3D}->reload_scene if $self->{canvas3D};
$self->{preview3D}->reload_print if $self->{preview3D}; $self->{preview3D}->reload_print if $self->{preview3D};
$self->schedule_background_process;
} }
# When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder.
@ -1679,7 +1503,7 @@ sub on_extruders_change {
$choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
# insert new choice into sizer # insert new choice into sizer
$self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0);
$self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, 0);
# setup the listener # setup the listener
EVT_COMBOBOX($choice, $choice, sub { EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_; my ($choice) = @_;
@ -1854,7 +1678,7 @@ sub object_settings_dialog {
model_object => $model_object, model_object => $model_object,
config => wxTheApp->{preset_bundle}->full_config, config => wxTheApp->{preset_bundle}->full_config,
); );
$self->pause_background_process; $self->stop_background_process;
$dlg->ShowModal; $dlg->ShowModal;
# update thumbnail since parts may have changed # update thumbnail since parts may have changed
@ -1866,13 +1690,12 @@ sub object_settings_dialog {
# update print # update print
if ($dlg->PartsChanged || $dlg->PartSettingsChanged) { if ($dlg->PartsChanged || $dlg->PartSettingsChanged) {
$self->stop_background_process;
$self->{print}->reload_object($obj_idx); $self->{print}->reload_object($obj_idx);
$self->schedule_background_process; $self->schedule_background_process;
$self->{canvas}->reload_scene if $self->{canvas}; $self->{canvas}->reload_scene if $self->{canvas};
$self->{canvas3D}->reload_scene if $self->{canvas3D}; $self->{canvas3D}->reload_scene if $self->{canvas3D};
} else { } else {
$self->resume_background_process; $self->schedule_background_process;
} }
} }

View File

@ -152,12 +152,6 @@ __PACKAGE__->mk_accessors(qw(
_simulation_mode _simulation_mode
)); ));
# make OpenGL::Array thread-safe
{
no warnings 'redefine';
*OpenGL::Array::CLONE_SKIP = sub { 1 };
}
sub new { sub new {
my ($class, $parent, $print) = @_; my ($class, $parent, $print) = @_;

View File

@ -7,11 +7,6 @@ sub new_scale {
return $class->new(map Slic3r::Geometry::scale($_), @_); return $class->new(map Slic3r::Geometry::scale($_), @_);
} }
sub dump_perl {
my $self = shift;
return sprintf "[%s,%s]", @$self;
}
package Slic3r::Pointf; package Slic3r::Pointf;
use strict; use strict;
use warnings; use warnings;

View File

@ -10,9 +10,4 @@ sub new_scale {
return $class->new(map [ Slic3r::Geometry::scale($_->[X]), Slic3r::Geometry::scale($_->[Y]) ], @points); 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; 1;

View File

@ -15,82 +15,16 @@ use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection o
union JT_ROUND JT_SQUARE); union JT_ROUND JT_SQUARE);
use Slic3r::Print::State ':steps'; use Slic3r::Print::State ':steps';
our $status_cb;
sub set_status_cb {
my ($class, $cb) = @_;
$status_cb = $cb;
}
sub status_cb {
return $status_cb // sub {};
}
sub size { sub size {
my $self = shift; my $self = shift;
return $self->bounding_box->size; return $self->bounding_box->size;
} }
# Slicing process, running at a background thread. sub run_post_process_scripts {
sub process { my ($self, $output_file) = @_;
my ($self) = @_;
Slic3r::trace(3, "Staring the slicing process.");
$_->make_perimeters for @{$self->objects};
$self->status_cb->(70, "Infilling layers");
$_->infill for @{$self->objects};
$_->generate_support_material for @{$self->objects};
$self->make_skirt;
$self->make_brim; # must come after make_skirt
$self->make_wipe_tower;
# time to make some statistics
if (0) {
eval "use Devel::Size";
print "MEMORY USAGE:\n";
printf " meshes = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->meshes), @{$self->objects})/1024/1024;
printf " layer slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->layers}, @{$self->objects})/1024/1024;
printf " region slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " perimeters = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->perimeters), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024;
}
if (0) {
eval "use Slic3r::Test::SectionCut";
Slic3r::Test::SectionCut->new(print => $self)->export_svg("section_cut.svg");
}
Slic3r::trace(3, "Slicing process finished.")
}
# G-code export process, running at a background thread.
# The export_gcode may die for various reasons (fails to process output_filename_format,
# write error into the G-code, cannot execute post-processing scripts).
# It is up to the caller to show an error message.
sub export_gcode {
my $self = shift;
my %params = @_;
# prerequisites
$self->process;
# output everything to a G-code file
# The following call may die if the output_filename_format template substitution fails.
my $output_file = $self->output_filepath($params{output_file} // '');
$self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : ""));
# The following line may die for multiple reasons.
my $gcode = Slic3r::GCode->new;
if (defined $params{gcode_preview_data}) {
$gcode->do_export_w_preview($self, $output_file, $params{gcode_preview_data});
} else {
$gcode->do_export($self, $output_file);
}
# run post-processing scripts # run post-processing scripts
if (@{$self->config->post_process}) { if (@{$self->config->post_process}) {
$self->status_cb->(95, "Running post-processing scripts"); # $self->status_cb->(95, "Running post-processing scripts");
$self->config->setenv; $self->config->setenv;
for my $script (@{$self->config->post_process}) { for my $script (@{$self->config->post_process}) {
# Ignore empty post processing script lines. # Ignore empty post processing script lines.
@ -205,67 +139,4 @@ EOF
print "Done.\n" unless $params{quiet}; print "Done.\n" unless $params{quiet};
} }
sub make_skirt {
my $self = shift;
# prerequisites
$_->make_perimeters for @{$self->objects};
$_->infill for @{$self->objects};
$_->generate_support_material for @{$self->objects};
return if $self->step_done(STEP_SKIRT);
$self->set_step_started(STEP_SKIRT);
$self->skirt->clear;
if ($self->has_skirt) {
$self->status_cb->(88, "Generating skirt");
$self->_make_skirt();
}
$self->set_step_done(STEP_SKIRT);
}
sub make_brim {
my $self = shift;
# prerequisites
$_->make_perimeters for @{$self->objects};
$_->infill for @{$self->objects};
$_->generate_support_material for @{$self->objects};
$self->make_skirt;
return if $self->step_done(STEP_BRIM);
$self->set_step_started(STEP_BRIM);
# since this method must be idempotent, we clear brim paths *before*
# checking whether we need to generate them
$self->brim->clear;
if ($self->config->brim_width > 0) {
$self->status_cb->(88, "Generating brim");
$self->_make_brim;
}
$self->set_step_done(STEP_BRIM);
}
sub make_wipe_tower {
my $self = shift;
# prerequisites
$_->make_perimeters for @{$self->objects};
$_->infill for @{$self->objects};
$_->generate_support_material for @{$self->objects};
$self->make_skirt;
$self->make_brim;
return if $self->step_done(STEP_WIPE_TOWER);
$self->set_step_started(STEP_WIPE_TOWER);
$self->_clear_wipe_tower;
if ($self->has_wipe_tower) {
# $self->status_cb->(95, "Generating wipe tower");
$self->_make_wipe_tower;
}
$self->set_step_done(STEP_WIPE_TOWER);
}
1; 1;

View File

@ -21,95 +21,4 @@ sub support_layers {
return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ]; return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ];
} }
# 1) Decides Z positions of the layers,
# 2) Initializes layers and their regions
# 3) Slices the object meshes
# 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
# 5) Applies size compensation (offsets the slices in XY plane)
# 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
# Resulting expolygons of layer regions are marked as Internal.
#
# this should be idempotent
sub slice {
my $self = shift;
return if $self->step_done(STEP_SLICE);
$self->set_step_started(STEP_SLICE);
$self->print->status_cb->(10, "Processing triangulated mesh");
$self->_slice;
my $warning = $self->_fix_slicing_errors;
warn $warning if (defined($warning) && $warning ne '');
# simplify slices if required
$self->_simplify_slices(scale($self->print->config->resolution))
if ($self->print->config->resolution);
die "No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"
if !@{$self->layers};
$self->set_step_done(STEP_SLICE);
}
# 1) Merges typed region slices into stInternal type.
# 2) Increases an "extra perimeters" counter at region slices where needed.
# 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
sub make_perimeters {
my ($self) = @_;
# prerequisites
$self->slice;
if (! $self->step_done(STEP_PERIMETERS)) {
$self->print->status_cb->(20, "Generating perimeters");
$self->_make_perimeters;
}
}
sub prepare_infill {
my ($self) = @_;
# prerequisites
$self->make_perimeters;
return if $self->step_done(STEP_PREPARE_INFILL);
$self->set_step_started(STEP_PREPARE_INFILL);
$self->print->status_cb->(30, "Preparing infill");
$self->_prepare_infill;
$self->set_step_done(STEP_PREPARE_INFILL);
}
sub infill {
my ($self) = @_;
# prerequisites
$self->prepare_infill;
$self->_infill;
}
sub generate_support_material {
my $self = shift;
# prerequisites
$self->slice;
return if $self->step_done(STEP_SUPPORTMATERIAL);
$self->set_step_started(STEP_SUPPORTMATERIAL);
$self->clear_support_layers;
if (($self->config->support_material || $self->config->raft_layers > 0) && scalar(@{$self->layers}) > 1) {
$self->print->status_cb->(85, "Generating support material");
# New supports, C++ implementation.
$self->_generate_support_material;
}
$self->set_step_done(STEP_SUPPORTMATERIAL);
my $stats = sprintf "Weight: %.1fg, Cost: %.1f" , $self->print->total_weight, $self->print->total_cost;
$self->print->status_cb->(85, $stats);
}
1; 1;

View File

@ -38,11 +38,6 @@ has 'duplicate_grid' => (
default => sub { [1,1] }, default => sub { [1,1] },
); );
has 'status_cb' => (
is => 'rw',
default => sub { sub {} },
);
has 'print_center' => ( has 'print_center' => (
is => 'rw', is => 'rw',
default => sub { Slic3r::Pointf->new(100,100) }, default => sub { Slic3r::Pointf->new(100,100) },
@ -90,35 +85,16 @@ sub set_model {
} }
} }
sub _before_export {
my ($self) = @_;
$self->_print->set_status_cb($self->status_cb);
$self->_print->validate;
}
sub _after_export {
my ($self) = @_;
$self->_print->set_status_cb(undef);
}
sub export_gcode { sub export_gcode {
my ($self) = @_; my ($self) = @_;
$self->_print->validate;
$self->_before_export; $self->_print->export_gcode($self->output_file // '');
$self->_print->export_gcode(output_file => $self->output_file);
$self->_after_export;
} }
sub export_svg { sub export_svg {
my ($self) = @_; my ($self) = @_;
$self->_print->validate;
$self->_before_export;
$self->_print->export_svg(output_file => $self->output_file); $self->_print->export_svg(output_file => $self->output_file);
$self->_after_export;
} }
1; 1;

View File

@ -202,10 +202,6 @@ if (@ARGV) { # slicing from command line
duplicate_grid => $opt{duplicate_grid} // [1,1], duplicate_grid => $opt{duplicate_grid} // [1,1],
print_center => $opt{print_center} // Slic3r::Pointf->new(100,100), print_center => $opt{print_center} // Slic3r::Pointf->new(100,100),
dont_arrange => $opt{dont_arrange} // 0, dont_arrange => $opt{dont_arrange} // 0,
status_cb => sub {
my ($percent, $message) = @_;
printf "=> %s\n", $message;
},
output_file => $opt{output}, output_file => $opt{output},
); );

View File

@ -1,35 +0,0 @@
use Test::More tests => 2;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use List::Util qw(first);
use Slic3r;
use Slic3r::Test;
{
my $print = Slic3r::Test::init_print('20mm_cube');
{
my $thread = threads->create(sub { Slic3r::thread_cleanup(); return 1; });
ok $thread->join, "print survives thread spawning";
}
}
{
my $thread = threads->create(sub {
{
my $print = Slic3r::Test::init_print('20mm_cube');
Slic3r::Test::gcode($print);
}
Slic3r::thread_cleanup();
return 1;
});
ok $thread->join, "process print in a separate thread";
}
__END__

View File

@ -171,6 +171,8 @@ add_library(libslic3r STATIC
add_library(libslic3r_gui STATIC add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/AppConfig.cpp ${LIBDIR}/slic3r/GUI/AppConfig.cpp
${LIBDIR}/slic3r/GUI/AppConfig.hpp ${LIBDIR}/slic3r/GUI/AppConfig.hpp
${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.cpp
${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.hpp
${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.cpp
${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/3DScene.hpp
${LIBDIR}/slic3r/GUI/GLShader.cpp ${LIBDIR}/slic3r/GUI/GLShader.cpp
@ -331,6 +333,7 @@ set(XS_XSP_FILES
${XSP_DIR}/Geometry.xsp ${XSP_DIR}/Geometry.xsp
${XSP_DIR}/GUI.xsp ${XSP_DIR}/GUI.xsp
${XSP_DIR}/GUI_AppConfig.xsp ${XSP_DIR}/GUI_AppConfig.xsp
${XSP_DIR}/GUI_BackgroundSlicingProcess.xsp
${XSP_DIR}/GUI_3DScene.xsp ${XSP_DIR}/GUI_3DScene.xsp
${XSP_DIR}/GUI_Preset.xsp ${XSP_DIR}/GUI_Preset.xsp
${XSP_DIR}/GUI_Tab.xsp ${XSP_DIR}/GUI_Tab.xsp

View File

@ -239,23 +239,16 @@ sub new {
); );
} }
package Slic3r::GUI::_3DScene::GLShader;
sub CLONE_SKIP { 1 }
package Slic3r::GUI::_3DScene::GLVolume::Collection; package Slic3r::GUI::_3DScene::GLVolume::Collection;
use overload use overload
'@{}' => sub { $_[0]->arrayref }, '@{}' => sub { $_[0]->arrayref },
'fallback' => 1; 'fallback' => 1;
sub CLONE_SKIP { 1 }
package Slic3r::GUI::PresetCollection; package Slic3r::GUI::PresetCollection;
use overload use overload
'@{}' => sub { $_[0]->arrayref }, '@{}' => sub { $_[0]->arrayref },
'fallback' => 1; 'fallback' => 1;
sub CLONE_SKIP { 1 }
package main; package main;
for my $class (qw( for my $class (qw(
Slic3r::BridgeDetector Slic3r::BridgeDetector

View File

@ -5,11 +5,13 @@
#include "Flow.hpp" #include "Flow.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "SupportMaterial.hpp" #include "SupportMaterial.hpp"
#include "GCode.hpp"
#include "GCode/WipeTowerPrusaMM.hpp" #include "GCode/WipeTowerPrusaMM.hpp"
#include <algorithm> #include <algorithm>
#include <unordered_set> #include <unordered_set>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace Slic3r {
@ -793,6 +795,71 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
} }
} }
// Slicing process, running at a background thread.
void Print::process()
{
BOOST_LOG_TRIVIAL(info) << "Staring the slicing process.";
for (PrintObject *obj : this->objects)
obj->make_perimeters();
this->set_status(70, "Infilling layers");
for (PrintObject *obj : this->objects)
obj->infill();
for (PrintObject *obj : this->objects)
obj->generate_support_material();
if (! this->state.is_done(psSkirt)) {
this->state.set_started(psSkirt);
this->skirt.clear();
if (this->has_skirt()) {
this->set_status(88, "Generating skirt");
this->_make_skirt();
}
this->state.set_done(psSkirt);
}
if (! this->state.is_done(psBrim)) {
this->state.set_started(psBrim);
this->brim.clear();
if (this->config.brim_width > 0) {
this->set_status(88, "Generating brim");
this->_make_brim();
}
this->state.set_done(psBrim);
}
if (! this->state.is_done(psWipeTower)) {
this->state.set_started(psWipeTower);
this->_clear_wipe_tower();
if (this->has_wipe_tower()) {
//this->set_status(95, "Generating wipe tower");
this->_make_wipe_tower();
}
this->state.set_done(psWipeTower);
}
BOOST_LOG_TRIVIAL(info) << "Slicing process finished.";
}
// G-code export process, running at a background thread.
// The export_gcode may die for various reasons (fails to process output_filename_format,
// write error into the G-code, cannot execute post-processing scripts).
// It is up to the caller to show an error message.
void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
{
// prerequisites
this->process();
// output everything to a G-code file
// The following call may die if the output_filename_format template substitution fails.
std::string path = this->output_filepath(path_template);
std::string message = "Exporting G-code";
if (! path.empty()) {
message += " to ";
message += path;
}
this->set_status(90, message);
// The following line may die for multiple reasons.
GCode gcode;
gcode.do_export(this, path.c_str(), preview_data);
}
void Print::_make_skirt() void Print::_make_skirt()
{ {
// First off we need to decide how tall the skirt must be. // First off we need to decide how tall the skirt must be.
@ -978,10 +1045,6 @@ void Print::_clear_wipe_tower()
void Print::_make_wipe_tower() void Print::_make_wipe_tower()
{ {
this->_clear_wipe_tower();
if (! this->has_wipe_tower())
return;
// Let the ToolOrdering class know there will be initial priming extrusions at the start of the print. // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true); m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
if (! m_tool_ordering.has_wipe_tower()) if (! m_tool_ordering.has_wipe_tower())
@ -1153,9 +1216,4 @@ std::string Print::output_filepath(const std::string &path)
return path; return path;
} }
void Print::set_status(int percent, const std::string &message)
{
printf("Print::status %d => %s\n", percent, message.c_str());
}
} }

View File

@ -5,6 +5,7 @@
#include <set> #include <set>
#include <vector> #include <vector>
#include <string> #include <string>
#include <functional>
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "Flow.hpp" #include "Flow.hpp"
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
@ -23,6 +24,7 @@ namespace Slic3r {
class Print; class Print;
class PrintObject; class PrintObject;
class ModelObject; class ModelObject;
class GCodePreviewData;
// Print step IDs for keeping track of the print state. // Print step IDs for keeping track of the print state.
enum PrintStep { enum PrintStep {
@ -190,23 +192,26 @@ public:
// (layer height, first layer height, raft settings, print nozzle diameter etc). // (layer height, first layer height, raft settings, print nozzle diameter etc).
SlicingParameters slicing_parameters() const; SlicingParameters slicing_parameters() const;
private:
void slice();
void make_perimeters();
void prepare_infill();
void infill();
void generate_support_material();
void _slice(); void _slice();
std::string _fix_slicing_errors(); std::string _fix_slicing_errors();
void _simplify_slices(double distance); void _simplify_slices(double distance);
void _prepare_infill();
bool has_support_material() const; bool has_support_material() const;
void detect_surfaces_type(); void detect_surfaces_type();
void process_external_surfaces(); void process_external_surfaces();
void discover_vertical_shells(); void discover_vertical_shells();
void bridge_over_infill(); void bridge_over_infill();
void _make_perimeters();
void _infill();
void clip_fill_surfaces(); void clip_fill_surfaces();
void discover_horizontal_shells(); void discover_horizontal_shells();
void combine_infill(); void combine_infill();
void _generate_support_material(); void _generate_support_material();
private:
Print* _print; Print* _print;
ModelObject* _model_object; ModelObject* _model_object;
Points _copies; // Slic3r::Point objects in scaled G-code coordinates Points _copies; // Slic3r::Point objects in scaled G-code coordinates
@ -232,7 +237,6 @@ public:
PrintObjectPtrs objects; PrintObjectPtrs objects;
PrintRegionPtrs regions; PrintRegionPtrs regions;
PlaceholderParser placeholder_parser; PlaceholderParser placeholder_parser;
// TODO: status_cb
std::string estimated_print_time; std::string estimated_print_time;
double total_used_filament, total_extruded_volume, total_cost, total_weight; double total_used_filament, total_extruded_volume, total_cost, total_weight;
std::map<size_t, float> filament_stats; std::map<size_t, float> filament_stats;
@ -283,13 +287,11 @@ public:
bool has_support_material() const; bool has_support_material() const;
void auto_assign_extruders(ModelObject* model_object) const; void auto_assign_extruders(ModelObject* model_object) const;
void _make_skirt(); void process();
void _make_brim(); void export_gcode(const std::string &path_template, GCodePreviewData *preview_data);
// Wipe tower support. // Wipe tower support.
bool has_wipe_tower() const; bool has_wipe_tower() const;
void _clear_wipe_tower();
void _make_wipe_tower();
// Tool ordering of a non-sequential print has to be known to calculate the wipe tower. // Tool ordering of a non-sequential print has to be known to calculate the wipe tower.
// Cache it here, so it does not need to be recalculated during the G-code generation. // Cache it here, so it does not need to be recalculated during the G-code generation.
ToolOrdering m_tool_ordering; ToolOrdering m_tool_ordering;
@ -301,8 +303,14 @@ public:
std::string output_filename(); std::string output_filename();
std::string output_filepath(const std::string &path); std::string output_filepath(const std::string &path);
typedef std::function<void(int, const std::string&)> status_callback_type;
void set_status_callback(status_callback_type cb) { m_status_callback = cb; }
void reset_status_callback() { m_status_callback = nullptr; }
// Calls a registered callback to update the status. // Calls a registered callback to update the status.
void set_status(int percent, const std::string &message); void set_status(int percent, const std::string &message) {
if (m_status_callback) m_status_callback(percent, message);
else printf("%d => %s\n", percent, message.c_str());
}
// Cancel the running computation. Stop execution of all the background threads. // Cancel the running computation. Stop execution of all the background threads.
void cancel() { m_canceled = true; } void cancel() { m_canceled = true; }
// Cancel the running computation. Stop execution of all the background threads. // Cancel the running computation. Stop execution of all the background threads.
@ -314,8 +322,14 @@ private:
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys); bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume); PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
void _make_skirt();
void _make_brim();
void _clear_wipe_tower();
void _make_wipe_tower();
// Has the calculation been canceled? // Has the calculation been canceled?
tbb::atomic<bool> m_canceled; tbb::atomic<bool> m_canceled;
status_callback_type m_status_callback;
}; };
#define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator) #define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator)

View File

@ -111,6 +111,7 @@ PrintConfigDef::PrintConfigDef()
"with cooling (use a fan) before tweaking this."); "with cooling (use a fan) before tweaking this.");
def->cli = "bridge-flow-ratio=f"; def->cli = "bridge-flow-ratio=f";
def->min = 0; def->min = 0;
def->max = 2;
def->default_value = new ConfigOptionFloat(1); def->default_value = new ConfigOptionFloat(1);
def = this->add("bridge_speed", coFloat); def = this->add("bridge_speed", coFloat);

View File

@ -31,6 +31,8 @@
#include <cassert> #include <cassert>
#endif #endif
#define PARALLEL_FOR_CANCEL do { if (this->print()->canceled()) return; } while (0)
namespace Slic3r { namespace Slic3r {
PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) : PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) :
@ -104,6 +106,305 @@ bool PrintObject::reload_model_instances()
return this->set_copies(copies); return this->set_copies(copies);
} }
// 1) Decides Z positions of the layers,
// 2) Initializes layers and their regions
// 3) Slices the object meshes
// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
// 5) Applies size compensation (offsets the slices in XY plane)
// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
// Resulting expolygons of layer regions are marked as Internal.
//
// this should be idempotent
void PrintObject::slice()
{
if (this->state.is_done(posSlice))
return;
this->state.set_started(posSlice);
this->_print->set_status(10, "Processing triangulated mesh");
this->_slice();
// Fix the model.
//FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend.
std::string warning = this->_fix_slicing_errors();
if (! warning.empty())
BOOST_LOG_TRIVIAL(info) << warning;
// Simplify slices if required.
if (this->_print->config.resolution)
this->_simplify_slices(scale_(this->_print->config.resolution));
if (this->layers.empty())
throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
this->state.set_done(posSlice);
}
// 1) Merges typed region slices into stInternal type.
// 2) Increases an "extra perimeters" counter at region slices where needed.
// 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
void PrintObject::make_perimeters()
{
// prerequisites
this->slice();
if (this->state.is_done(posPerimeters))
return;
this->state.set_started(posPerimeters);
this->_print->set_status(20, "Generating perimeters");
BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
// merge slices if they were split into types
if (this->typed_slices) {
FOREACH_LAYER(this, layer_it)
(*layer_it)->merge_slices();
this->typed_slices = false;
this->state.invalidate(posPrepareInfill);
}
// compare each layer to the one below, and mark those slices needing
// one additional inner perimeter, like the top of domed objects-
// this algorithm makes sure that at least one perimeter is overlapping
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
// inside the object - infill_only_where_needed should be the method of choice for printing
// hollow objects
FOREACH_REGION(this->_print, region_it) {
size_t region_id = region_it - this->_print->regions.begin();
const PrintRegion &region = **region_it;
if (!region.config.extra_perimeters
|| region.config.perimeters == 0
|| region.config.fill_density == 0
|| this->layer_count() < 2)
continue;
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size() - 1),
[this, &region, region_id](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
LayerRegion &layerm = *this->layers[layer_idx]->regions[region_id];
const LayerRegion &upper_layerm = *this->layers[layer_idx+1]->regions[region_id];
const Polygons upper_layerm_polygons = upper_layerm.slices;
// Filter upper layer polygons in intersection_ppl by their bounding boxes?
// my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
const double total_loop_length = total_length(upper_layerm_polygons);
const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
for (Surface &slice : layerm.slices.surfaces) {
for (;;) {
// compute the total thickness of perimeters
const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
+ (region.config.perimeters-1 + slice.extra_perimeters) * perimeter_spacing;
// define a critical area where we don't want the upper slice to fall into
// (it should either lay over our perimeters or outside this area)
const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5);
const Polygons critical_area = diff(
offset(slice.expolygon, float(- perimeters_thickness)),
offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth))
);
// check whether a portion of the upper slices falls inside the critical area
const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area);
// only add an additional loop if at least 30% of the slice loop would benefit from it
if (total_length(intersection) <= total_loop_length*0.3)
break;
/*
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"extra.svg",
no_arrows => 1,
expolygons => union_ex($critical_area),
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
);
}
*/
++ slice.extra_perimeters;
}
#ifdef DEBUG
if (slice.extra_perimeters > 0)
printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx);
#endif
}
}
});
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end";
}
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
this->layers[layer_idx]->make_perimeters();
}
}
);
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
/*
simplify slices (both layer and region slices),
we only need the max resolution for perimeters
### This makes this method not-idempotent, so we keep it disabled for now.
###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
*/
this->state.set_done(posPerimeters);
}
void PrintObject::prepare_infill()
{
if (this->state.is_done(posPrepareInfill))
return;
this->state.set_started(posPrepareInfill);
this->_print->set_status(30, "Preparing infill");
// This will assign a type (top/bottom/internal) to $layerm->slices.
// Then the classifcation of $layerm->slices is transfered onto
// the $layerm->fill_surfaces by clipping $layerm->fill_surfaces
// by the cummulative area of the previous $layerm->fill_surfaces.
this->detect_surfaces_type();
// Decide what surfaces are to be filled.
// Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured.
// Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID.
BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces...";
for (auto *layer : this->layers)
for (auto *region : layer->regions)
region->prepare_fill_surfaces();
// this will detect bridges and reverse bridges
// and rearrange top/bottom/internal surfaces
// It produces enlarged overlapping bridging areas.
//
// 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap.
// 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap.
// 3) Clip the internal surfaces by the grown top/bottom surfaces.
// 4) Merge surfaces with the same style. This will mostly get rid of the overlaps.
//FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties.
this->process_external_surfaces();
// Add solid fills to ensure the shell vertical thickness.
this->discover_vertical_shells();
// Debugging output.
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Detect, which fill surfaces are near external layers.
// They will be split in internal and internal-solid surfaces.
// The purpose is to add a configurable number of solid layers to support the TOP surfaces
// and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces
// to close these surfaces reliably.
//FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters?
this->discover_horizontal_shells();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("7_discover_horizontal_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
// so it acts as an internal support. It maintains all other infill types intact.
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
//FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
// Likely the sparse infill will not be anchored correctly, so it will not work as intended.
// Also one wishes the perimeters to be supported by a full infill.
this->clip_fill_surfaces();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final");
layerm->export_region_fill_surfaces_to_svg_debug("8_clip_surfaces-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// the following step needs to be done before combination because it may need
// to remove only half of the combined infill
this->bridge_over_infill();
// combine fill surfaces to honor the "infill every N layers" option
this->combine_infill();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("9_prepare_infill-final");
layerm->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
} // for each region
for (const Layer *layer : this->layers) {
layer->export_region_slices_to_svg_debug("9_prepare_infill-final");
layer->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
this->state.set_done(posPrepareInfill);
}
void PrintObject::infill()
{
// prerequisites
this->prepare_infill();
if (! this->state.is_done(posInfill)) {
this->state.set_started(posInfill);
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
this->layers[layer_idx]->make_fills();
}
}
);
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end";
/* we could free memory now, but this would make this step not idempotent
### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers};
*/
this->state.set_done(posInfill);
}
}
void PrintObject::generate_support_material()
{
if (! this->state.is_done(posSupportMaterial)) {
this->state.set_started(posSupportMaterial);
this->clear_support_layers();
if ((this->config.support_material || this->config.raft_layers > 0) && this->layers.size() > 1) {
this->_print->set_status(85, "Generating support material");
this->_generate_support_material();
}
this->state.set_done(posSupportMaterial);
char stats[128];
//FIXME this does not belong here! Why should the status bar be updated with the object weight
// at the end of object's support.?
sprintf(stats, "Weight: %.1lfg, Cost: %.1lf", this->_print->total_weight, this->_print->total_cost);
this->_print->set_status(85, stats);
}
}
void PrintObject::clear_layers() void PrintObject::clear_layers()
{ {
for (Layer *l : this->layers) for (Layer *l : this->layers)
@ -282,105 +583,6 @@ bool PrintObject::has_support_material() const
|| this->config.support_material_enforce_layers > 0; || this->config.support_material_enforce_layers > 0;
} }
void PrintObject::_prepare_infill()
{
// This will assign a type (top/bottom/internal) to $layerm->slices.
// Then the classifcation of $layerm->slices is transfered onto
// the $layerm->fill_surfaces by clipping $layerm->fill_surfaces
// by the cummulative area of the previous $layerm->fill_surfaces.
this->detect_surfaces_type();
// Decide what surfaces are to be filled.
// Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured.
// Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID.
BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces...";
for (auto *layer : this->layers)
for (auto *region : layer->regions)
region->prepare_fill_surfaces();
// this will detect bridges and reverse bridges
// and rearrange top/bottom/internal surfaces
// It produces enlarged overlapping bridging areas.
//
// 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap.
// 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap.
// 3) Clip the internal surfaces by the grown top/bottom surfaces.
// 4) Merge surfaces with the same style. This will mostly get rid of the overlaps.
//FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties.
this->process_external_surfaces();
// Add solid fills to ensure the shell vertical thickness.
this->discover_vertical_shells();
// Debugging output.
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Detect, which fill surfaces are near external layers.
// They will be split in internal and internal-solid surfaces.
// The purpose is to add a configurable number of solid layers to support the TOP surfaces
// and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces
// to close these surfaces reliably.
//FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters?
this->discover_horizontal_shells();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("7_discover_horizontal_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
// so it acts as an internal support. It maintains all other infill types intact.
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
//FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
// Likely the sparse infill will not be anchored correctly, so it will not work as intended.
// Also one wishes the perimeters to be supported by a full infill.
this->clip_fill_surfaces();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final");
layerm->export_region_fill_surfaces_to_svg_debug("8_clip_surfaces-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// the following step needs to be done before combination because it may need
// to remove only half of the combined infill
this->bridge_over_infill();
// combine fill surfaces to honor the "infill every N layers" option
this->combine_infill();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("9_prepare_infill-final");
layerm->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
} // for each region
for (const Layer *layer : this->layers) {
layer->export_region_slices_to_svg_debug("9_prepare_infill-final");
layer->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
// This function analyzes slices of a region (SurfaceCollection slices). // This function analyzes slices of a region (SurfaceCollection slices).
// Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface. // Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
// Initially all slices are of type stInternal. // Initially all slices are of type stInternal.
@ -427,6 +629,7 @@ void PrintObject::detect_surfaces_type()
(this->config.support_material.value && this->config.support_material_contact_distance.value == 0) ? (this->config.support_material.value && this->config.support_material_contact_distance.value == 0) ?
stBottom : stBottomBridge; stBottom : stBottomBridge;
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PARALLEL_FOR_CANCEL;
// BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << idx_region << " and layer " << layer->print_z; // BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << idx_region << " and layer " << layer->print_z;
Layer *layer = this->layers[idx_layer]; Layer *layer = this->layers[idx_layer];
LayerRegion *layerm = layer->get_region(idx_region); LayerRegion *layerm = layer->get_region(idx_region);
@ -564,6 +767,7 @@ void PrintObject::detect_surfaces_type()
tbb::blocked_range<size_t>(0, this->layers.size()), tbb::blocked_range<size_t>(0, this->layers.size()),
[this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range<size_t>& range) { [this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range<size_t>& range) {
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PARALLEL_FOR_CANCEL;
LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region); LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region);
layerm->slices_to_fill_surfaces_clipped(); layerm->slices_to_fill_surfaces_clipped();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
@ -590,6 +794,7 @@ void PrintObject::process_external_surfaces()
tbb::blocked_range<size_t>(0, this->layers.size()), tbb::blocked_range<size_t>(0, this->layers.size()),
[this, region_id](const tbb::blocked_range<size_t>& range) { [this, region_id](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
// BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << this->layers[layer_idx]->print_z; // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << this->layers[layer_idx]->print_z;
this->layers[layer_idx]->get_region(region_id)->process_external_surfaces((layer_idx == 0) ? NULL : this->layers[layer_idx - 1]); this->layers[layer_idx]->get_region(region_id)->process_external_surfaces((layer_idx == 0) ? NULL : this->layers[layer_idx - 1]);
} }
@ -638,6 +843,7 @@ void PrintObject::discover_vertical_shells()
const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
const size_t num_regions = this->_print->regions.size(); const size_t num_regions = this->_print->regions.size();
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PARALLEL_FOR_CANCEL;
const Layer &layer = *this->layers[idx_layer]; const Layer &layer = *this->layers[idx_layer];
DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer]; DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
// Simulate single set of perimeters over all merged regions. // Simulate single set of perimeters over all merged regions.
@ -720,6 +926,7 @@ void PrintObject::discover_vertical_shells()
[this, idx_region, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) { [this, idx_region, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PARALLEL_FOR_CANCEL;
Layer &layer = *this->layers[idx_layer]; Layer &layer = *this->layers[idx_layer];
LayerRegion &layerm = *layer.regions[idx_region]; LayerRegion &layerm = *layer.regions[idx_region];
float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f;
@ -748,7 +955,7 @@ void PrintObject::discover_vertical_shells()
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PROFILE_BLOCK(discover_vertical_shells_region_layer); PROFILE_BLOCK(discover_vertical_shells_region_layer);
PARALLEL_FOR_CANCEL;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0; static size_t debug_idx = 0;
++ debug_idx; ++ debug_idx;
@ -1265,6 +1472,7 @@ end:
tbb::blocked_range<size_t>(0, this->layers.size()), tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) { [this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
PARALLEL_FOR_CANCEL;
Layer *layer = this->layers[layer_id]; Layer *layer = this->layers[layer_id];
// Apply size compensation and perform clipping of multi-part objects. // Apply size compensation and perform clipping of multi-part objects.
float delta = float(scale_(this->config.xy_size_compensation.value)); float delta = float(scale_(this->config.xy_size_compensation.value));
@ -1348,6 +1556,7 @@ std::string PrintObject::_fix_slicing_errors()
tbb::blocked_range<size_t>(0, buggy_layers.size()), tbb::blocked_range<size_t>(0, buggy_layers.size()),
[this, &buggy_layers](const tbb::blocked_range<size_t>& range) { [this, &buggy_layers](const tbb::blocked_range<size_t>& range) {
for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) { for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) {
PARALLEL_FOR_CANCEL;
size_t idx_layer = buggy_layers[buggy_layer_idx]; size_t idx_layer = buggy_layers[buggy_layer_idx];
Layer *layer = this->layers[idx_layer]; Layer *layer = this->layers[idx_layer];
assert(layer->slicing_errors); assert(layer->slicing_errors);
@ -1424,6 +1633,7 @@ void PrintObject::_simplify_slices(double distance)
tbb::blocked_range<size_t>(0, this->layers.size()), tbb::blocked_range<size_t>(0, this->layers.size()),
[this, distance](const tbb::blocked_range<size_t>& range) { [this, distance](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
Layer *layer = this->layers[layer_idx]; Layer *layer = this->layers[layer_idx];
for (size_t region_idx = 0; region_idx < layer->regions.size(); ++ region_idx) for (size_t region_idx = 0; region_idx < layer->regions.size(); ++ region_idx)
layer->regions[region_idx]->slices.simplify(distance); layer->regions[region_idx]->slices.simplify(distance);
@ -1433,137 +1643,6 @@ void PrintObject::_simplify_slices(double distance)
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end";
} }
void PrintObject::_make_perimeters()
{
if (this->state.is_done(posPerimeters)) return;
this->state.set_started(posPerimeters);
BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
// merge slices if they were split into types
if (this->typed_slices) {
FOREACH_LAYER(this, layer_it)
(*layer_it)->merge_slices();
this->typed_slices = false;
this->state.invalidate(posPrepareInfill);
}
// compare each layer to the one below, and mark those slices needing
// one additional inner perimeter, like the top of domed objects-
// this algorithm makes sure that at least one perimeter is overlapping
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
// inside the object - infill_only_where_needed should be the method of choice for printing
// hollow objects
FOREACH_REGION(this->_print, region_it) {
size_t region_id = region_it - this->_print->regions.begin();
const PrintRegion &region = **region_it;
if (!region.config.extra_perimeters
|| region.config.perimeters == 0
|| region.config.fill_density == 0
|| this->layer_count() < 2)
continue;
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size() - 1),
[this, &region, region_id](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
LayerRegion &layerm = *this->layers[layer_idx]->regions[region_id];
const LayerRegion &upper_layerm = *this->layers[layer_idx+1]->regions[region_id];
const Polygons upper_layerm_polygons = upper_layerm.slices;
// Filter upper layer polygons in intersection_ppl by their bounding boxes?
// my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
const double total_loop_length = total_length(upper_layerm_polygons);
const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
for (Surface &slice : layerm.slices.surfaces) {
for (;;) {
// compute the total thickness of perimeters
const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
+ (region.config.perimeters-1 + slice.extra_perimeters) * perimeter_spacing;
// define a critical area where we don't want the upper slice to fall into
// (it should either lay over our perimeters or outside this area)
const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5);
const Polygons critical_area = diff(
offset(slice.expolygon, float(- perimeters_thickness)),
offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth))
);
// check whether a portion of the upper slices falls inside the critical area
const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area);
// only add an additional loop if at least 30% of the slice loop would benefit from it
if (total_length(intersection) <= total_loop_length*0.3)
break;
/*
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"extra.svg",
no_arrows => 1,
expolygons => union_ex($critical_area),
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
);
}
*/
++ slice.extra_perimeters;
}
#ifdef DEBUG
if (slice.extra_perimeters > 0)
printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx);
#endif
}
}
});
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end";
}
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
this->layers[layer_idx]->make_perimeters();
}
);
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
/*
simplify slices (both layer and region slices),
we only need the max resolution for perimeters
### This makes this method not-idempotent, so we keep it disabled for now.
###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
*/
this->state.set_done(posPerimeters);
}
void PrintObject::_infill()
{
if (this->state.is_done(posInfill)) return;
this->state.set_started(posInfill);
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
this->layers[layer_idx]->make_fills();
}
);
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end";
/* we could free memory now, but this would make this step not idempotent
### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers};
*/
this->state.set_done(posInfill);
}
// Only active if config->infill_only_where_needed. This step trims the sparse infill, // Only active if config->infill_only_where_needed. This step trims the sparse infill,
// so it acts as an internal support. It maintains all other infill types intact. // so it acts as an internal support. It maintains all other infill types intact.
// Here the internal surfaces and perimeters have to be supported by the sparse infill. // Here the internal surfaces and perimeters have to be supported by the sparse infill.

View File

@ -56,6 +56,7 @@ REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2"); REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2");
REGISTER_CLASS(TriangleMesh, "TriangleMesh"); REGISTER_CLASS(TriangleMesh, "TriangleMesh");
REGISTER_CLASS(AppConfig, "GUI::AppConfig"); REGISTER_CLASS(AppConfig, "GUI::AppConfig");
REGISTER_CLASS(BackgroundSlicingProcess, "GUI::BackgroundSlicingProcess");
REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader"); REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader");
REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume"); REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume");
REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection"); REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection");

View File

@ -0,0 +1,138 @@
#include "BackgroundSlicingProcess.hpp"
#include "GUI.hpp"
#include "../../libslic3r/Print.hpp"
#include <wx/event.h>
#include <wx/panel.h>
//#undef NDEBUG
#include <cassert>
#include <stdexcept>
namespace Slic3r {
namespace GUI {
extern wxPanel *g_wxPlater;
};
void BackgroundSlicingProcess::thread_proc()
{
std::unique_lock<std::mutex> lck(m_mutex);
// Let the caller know we are ready to run the background processing task.
m_state = STATE_IDLE;
lck.unlock();
m_condition.notify_one();
for (;;) {
assert(m_state == STATE_IDLE);
// Wait until a new task is ready to be executed, or this thread should be finished.
lck.lock();
m_condition.wait(lck, [this](){ return m_state == STATE_STARTED || m_state == STATE_EXIT; });
if (m_state == STATE_EXIT)
// Exiting this thread.
break;
// Process the background slicing task.
m_state = STATE_RUNNING;
lck.unlock();
std::string error;
try {
assert(m_print != nullptr);
m_print->process();
if (m_print->canceled())
return;
printf("PReparing m_event_sliced_id command\n");
wxCommandEvent evt(m_event_sliced_id);
printf("Issuing m_event_sliced_id command\n");
wxQueueEvent(GUI::g_wxPlater, evt.Clone());
GUI::g_wxPlater->ProcessWindowEvent(evt);
//GUI::g_wxPlater->ProcessEvent(evt);
printf("Done with m_event_sliced_id command\n");
m_print->export_gcode(m_output_path, m_gcode_preview_data);
} catch (std::exception &ex) {
error = ex.what();
} catch (...) {
error = "Unknown C++ exception.";
}
lck.lock();
m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED;
wxCommandEvent evt(m_event_finished_id);
evt.SetString(error);
evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0));
wxQueueEvent(GUI::g_wxPlater, evt.Clone());
lck.unlock();
// Let the UI thread wake up if it is waiting for the background task to finish.
m_condition.notify_one();
// Let the UI thread see the result.
}
m_state = STATE_EXITED;
lck.unlock();
// End of the background processing thread. The UI thread should join m_thread now.
}
void BackgroundSlicingProcess::join_background_thread()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// Worker thread has not been started yet.
assert(! m_thread.joinable());
} else {
assert(m_state == STATE_IDLE);
assert(m_thread.joinable());
// Notify the worker thread to exit.
m_state = STATE_EXIT;
lck.unlock();
m_condition.notify_one();
// Wait until the worker thread exits.
m_thread.join();
}
}
bool BackgroundSlicingProcess::start()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// The worker thread is not running yet. Start it.
assert(! m_thread.joinable());
m_thread = std::thread([this]{this->thread_proc();});
// Wait until the worker thread is ready to execute the background processing task.
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
}
assert(m_state == STATE_IDLE || this->running());
if (this->running())
// The background processing thread is already running.
return false;
if (! this->idle())
throw std::runtime_error("Cannot start a background task, the worker thread is not idle.");
m_state = STATE_STARTED;
lck.unlock();
m_condition.notify_one();
return true;
}
bool BackgroundSlicingProcess::stop()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL)
return false;
assert(this->running());
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
m_print->cancel();
// Wait until the background processing stops by being canceled.
lck.unlock();
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
}
return true;
}
// Apply config over the print. Returns false, if the new config values caused any of the already
// processed steps to be invalidated, therefore the task will need to be restarted.
bool BackgroundSlicingProcess::apply_config(DynamicPrintConfig *config)
{
/*
// apply new config
my $invalidated = $self->{print}->apply_config(wxTheApp->{preset_bundle}->full_config);
*/
return true;
}
}; // namespace Slic3r

View File

@ -0,0 +1,82 @@
#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_
#define slic3r_GUI_BackgroundSlicingProcess_hpp_
#include <string>
#include <condition_variable>
#include <mutex>
#include <thread>
namespace Slic3r {
class DynamicPrintConfig;
class GCodePreviewData;
class Print;
// Support for the GUI background processing (Slicing and G-code generation).
// As of now this class is not declared in Slic3r::GUI due to the Perl bindings limits.
class BackgroundSlicingProcess
{
public:
BackgroundSlicingProcess() {}
~BackgroundSlicingProcess() { this->stop(); this->join_background_thread(); }
void set_print(Print *print) { m_print = print; }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
void set_sliced_event(int event_id) { m_event_sliced_id = event_id; }
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
// Start the background processing. Returns false if the background processing was already running.
bool start();
// Cancel the background processing. Returns false if the background processing was not running.
// A stopped background processing may be restarted with start().
bool stop();
// Apply config over the print. Returns false, if the new config values caused any of the already
// processed steps to be invalidated, therefore the task will need to be restarted.
bool apply_config(DynamicPrintConfig *config);
enum State {
// m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
STATE_INITIAL = 0,
// m_thread is waiting for the task to execute.
STATE_IDLE,
STATE_STARTED,
// m_thread is executing a task.
STATE_RUNNING,
// m_thread finished executing a task, and it is waiting until the UI thread picks up the results.
STATE_FINISHED,
// m_thread finished executing a task, the task has been canceled by the UI thread, therefore the UI thread will not be notified.
STATE_CANCELED,
// m_thread exited the loop and it is going to finish. The UI thread should join on m_thread.
STATE_EXIT,
STATE_EXITED,
};
State state() const { return m_state; }
bool idle() const { return m_state == STATE_IDLE; }
bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; }
private:
void thread_proc();
void start_background_thread();
void join_background_thread();
Print *m_print = nullptr;
GCodePreviewData *m_gcode_preview_data = nullptr;
std::string m_output_path;
// Thread, on which the background processing is executed. The thread will always be present
// and ready to execute the slicing process.
std::thread m_thread;
// Mutex and condition variable to synchronize m_thread with the UI thread.
std::mutex m_mutex;
std::condition_variable m_condition;
State m_state = STATE_INITIAL;
// wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue.
int m_event_sliced_id = 0;
// wxWidgets command ID to be sent to the platter to inform that the task finished.
int m_event_finished_id = 0;
};
}; // namespace Slic3r
#endif /* slic3r_GUI_BackgroundSlicingProcess_hpp_ */

View File

@ -47,6 +47,8 @@
#include "Preferences.hpp" #include "Preferences.hpp"
#include "PresetBundle.hpp" #include "PresetBundle.hpp"
#include "../../libslic3r/Print.hpp"
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
#if __APPLE__ #if __APPLE__
@ -172,6 +174,7 @@ void break_to_debugger()
wxApp *g_wxApp = nullptr; wxApp *g_wxApp = nullptr;
wxFrame *g_wxMainFrame = nullptr; wxFrame *g_wxMainFrame = nullptr;
wxNotebook *g_wxTabPanel = nullptr; wxNotebook *g_wxTabPanel = nullptr;
wxPanel *g_wxPlater = nullptr;
AppConfig *g_AppConfig = nullptr; AppConfig *g_AppConfig = nullptr;
PresetBundle *g_PresetBundle= nullptr; PresetBundle *g_PresetBundle= nullptr;
@ -197,6 +200,11 @@ void set_tab_panel(wxNotebook *tab_panel)
g_wxTabPanel = tab_panel; g_wxTabPanel = tab_panel;
} }
void set_plater(wxPanel *plater)
{
g_wxPlater = plater;
}
void set_app_config(AppConfig *app_config) void set_app_config(AppConfig *app_config)
{ {
g_AppConfig = app_config; g_AppConfig = app_config;
@ -507,6 +515,18 @@ void warning_catcher(wxWindow* parent, wxString message){
msg->ShowModal(); msg->ShowModal();
} }
// Assign a Lambda to the print object to emit a wxWidgets Command with the provided ID
// to deliver a progress status message.
void set_print_callback_event(Print *print, int id)
{
print->set_status_callback([id](int percent, const std::string &message){
wxCommandEvent event(id);
event.SetInt(percent);
event.SetString(message);
wxQueueEvent(g_wxMainFrame, event.Clone());
});
}
wxApp* get_app(){ wxApp* get_app(){
return g_wxApp; return g_wxApp;
} }

View File

@ -11,6 +11,7 @@ class wxFrame;
class wxWindow; class wxWindow;
class wxMenuBar; class wxMenuBar;
class wxNotebook; class wxNotebook;
class wxPanel;
class wxComboCtrl; class wxComboCtrl;
class wxString; class wxString;
class wxArrayString; class wxArrayString;
@ -23,6 +24,7 @@ namespace Slic3r {
class PresetBundle; class PresetBundle;
class PresetCollection; class PresetCollection;
class Print;
class AppConfig; class AppConfig;
class DynamicPrintConfig; class DynamicPrintConfig;
class TabIface; class TabIface;
@ -73,6 +75,7 @@ void break_to_debugger();
void set_wxapp(wxApp *app); void set_wxapp(wxApp *app);
void set_main_frame(wxFrame *main_frame); void set_main_frame(wxFrame *main_frame);
void set_tab_panel(wxNotebook *tab_panel); void set_tab_panel(wxNotebook *tab_panel);
void set_plater(wxPanel *plater);
void set_app_config(AppConfig *app_config); void set_app_config(AppConfig *app_config);
void set_preset_bundle(PresetBundle *preset_bundle); void set_preset_bundle(PresetBundle *preset_bundle);
@ -98,6 +101,10 @@ void show_error(wxWindow* parent, wxString message);
void show_info(wxWindow* parent, wxString message, wxString title); void show_info(wxWindow* parent, wxString message, wxString title);
void warning_catcher(wxWindow* parent, wxString message); void warning_catcher(wxWindow* parent, wxString message);
// Assign a Lambda to the print object to emit a wxWidgets Command with the provided ID
// to deliver a progress status message.
void set_print_callback_event(Print *print, int id);
// load language saved at application config // load language saved at application config
bool load_language(); bool load_language();
// save language at application config // save language at application config

View File

@ -10,8 +10,6 @@ use Test::More tests => 5;
my $print = Slic3r::Print->new; my $print = Slic3r::Print->new;
isa_ok $print, 'Slic3r::Print'; isa_ok $print, 'Slic3r::Print';
isa_ok $print->config, 'Slic3r::Config::Static::Ref'; isa_ok $print->config, 'Slic3r::Config::Static::Ref';
isa_ok $print->default_object_config, 'Slic3r::Config::Static::Ref';
isa_ok $print->default_region_config, 'Slic3r::Config::Static::Ref';
isa_ok $print->placeholder_parser, 'Slic3r::GCode::PlaceholderParser::Ref'; isa_ok $print->placeholder_parser, 'Slic3r::GCode::PlaceholderParser::Ref';
} }

View File

@ -33,6 +33,9 @@ void set_main_frame(SV *ui)
void set_tab_panel(SV *ui) void set_tab_panel(SV *ui)
%code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %}; %code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %};
void set_plater(SV *ui)
%code%{ Slic3r::GUI::set_plater((wxPanel*)wxPli_sv_2_object(aTHX_ ui, "Wx::Panel")); %};
void add_debug_menu(SV *ui, int event_language_change) void add_debug_menu(SV *ui, int event_language_change)
%code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %}; %code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %};
@ -65,5 +68,8 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz
(wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"),
(wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %}; (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %};
void set_print_callback_event(Print *print, int id)
%code%{ Slic3r::GUI::set_print_callback_event(print, id); %};
std::string fold_utf8_to_ascii(const char *src) std::string fold_utf8_to_ascii(const char *src)
%code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %};

View File

@ -0,0 +1,23 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
%}
%name{Slic3r::GUI::BackgroundSlicingProcess} class BackgroundSlicingProcess {
BackgroundSlicingProcess();
~BackgroundSlicingProcess();
void set_print(Print *print);
void set_gcode_preview_data(GCodePreviewData *gpd);
void set_sliced_event(int event_id);
void set_finished_event(int event_id);
bool start();
bool stop();
bool apply_config(DynamicPrintConfig *config);
bool running();
};

View File

@ -27,7 +27,6 @@ _constant()
%} %}
%name{Slic3r::Print::Region} class PrintRegion { %name{Slic3r::Print::Region} class PrintRegion {
// owned by Print, no constructor/destructor // owned by Print, no constructor/destructor
@ -39,16 +38,9 @@ _constant()
%code%{ RETVAL = THIS->flow(role, layer_height, bridge, first_layer, width, *object); %}; %code%{ RETVAL = THIS->flow(role, layer_height, bridge, first_layer, width, *object); %};
}; };
%name{Slic3r::Print::Object} class PrintObject { %name{Slic3r::Print::Object} class PrintObject {
// owned by Print, no constructor/destructor // owned by Print, no constructor/destructor
void add_region_volume(int region_id, int volume_id);
std::vector<int> get_region_volumes(int region_id)
%code%{
if (0 <= region_id && region_id < THIS->region_volumes.size())
RETVAL = THIS->region_volumes[region_id];
%};
int region_count() int region_count()
%code%{ RETVAL = THIS->print()->regions.size(); %}; %code%{ RETVAL = THIS->print()->regions.size(); %};
@ -67,57 +59,22 @@ _constant()
Points _shifted_copies() Points _shifted_copies()
%code%{ RETVAL = THIS->_shifted_copies; %}; %code%{ RETVAL = THIS->_shifted_copies; %};
void set_shifted_copies(Points value)
%code%{ THIS->_shifted_copies = value; %};
bool add_copy(Pointf* point) bool add_copy(Pointf* point)
%code%{ RETVAL = THIS->add_copy(*point); %}; %code%{ RETVAL = THIS->add_copy(*point); %};
bool delete_last_copy(); bool delete_last_copy();
bool delete_all_copies();
bool set_copies(Points copies);
bool reload_model_instances(); bool reload_model_instances();
void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges)
%code%{ THIS->layer_height_ranges = layer_height_ranges; %}; %code%{ THIS->layer_height_ranges = layer_height_ranges; %};
void set_layer_height_profile(std::vector<double> profile)
%code%{ THIS->layer_height_profile = profile; %};
size_t total_layer_count();
size_t layer_count(); size_t layer_count();
void clear_layers();
Ref<Layer> get_layer(int idx); Ref<Layer> get_layer(int idx);
Ref<Layer> add_layer(int id, coordf_t height, coordf_t print_z,
coordf_t slice_z);
size_t support_layer_count(); size_t support_layer_count();
void clear_support_layers();
Ref<SupportLayer> get_support_layer(int idx); Ref<SupportLayer> get_support_layer(int idx);
bool step_done(PrintObjectStep step) bool step_done(PrintObjectStep step)
%code%{ RETVAL = THIS->state.is_done(step); %}; %code%{ RETVAL = THIS->state.is_done(step); %};
void set_step_done(PrintObjectStep step)
%code%{ THIS->state.set_done(step); %};
void set_step_started(PrintObjectStep step)
%code%{ THIS->state.set_started(step); %};
void _slice();
std::string _fix_slicing_errors();
void _simplify_slices(double distance);
void _prepare_infill();
void detect_surfaces_type();
void process_external_surfaces();
void _make_perimeters();
void _infill();
void _generate_support_material();
std::vector<double> get_layer_height_min_max()
%code%{
SlicingParameters slicing_params = THIS->slicing_parameters();
RETVAL.push_back(slicing_params.min_layer_height);
RETVAL.push_back(slicing_params.max_layer_height);
RETVAL.push_back(slicing_params.first_print_layer_height);
RETVAL.push_back(slicing_params.first_object_layer_height);
RETVAL.push_back(slicing_params.layer_height);
%};
void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action) void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action)
%code%{ %code%{
@ -129,25 +86,16 @@ _constant()
%}; %};
void reset_layer_height_profile(); void reset_layer_height_profile();
int ptr()
%code%{ RETVAL = (int)(intptr_t)THIS; %};
}; };
%name{Slic3r::Print} class Print { %name{Slic3r::Print} class Print {
Print(); Print();
~Print(); ~Print();
Ref<StaticPrintConfig> config() Ref<StaticPrintConfig> config()
%code%{ RETVAL = &THIS->config; %}; %code%{ RETVAL = &THIS->config; %};
Ref<StaticPrintConfig> default_object_config()
%code%{ RETVAL = &THIS->default_object_config; %};
Ref<StaticPrintConfig> default_region_config()
%code%{ RETVAL = &THIS->default_region_config; %};
Ref<PlaceholderParser> placeholder_parser() Ref<PlaceholderParser> placeholder_parser()
%code%{ RETVAL = &THIS->placeholder_parser; %}; %code%{ RETVAL = &THIS->placeholder_parser; %};
// TODO: status_cb
Ref<ExtrusionEntityCollection> skirt() Ref<ExtrusionEntityCollection> skirt()
%code%{ RETVAL = &THIS->skirt; %}; %code%{ RETVAL = &THIS->skirt; %};
Ref<ExtrusionEntityCollection> brim() Ref<ExtrusionEntityCollection> brim()
@ -176,20 +124,7 @@ _constant()
%code%{ RETVAL = THIS->state.is_done(step); %}; %code%{ RETVAL = THIS->state.is_done(step); %};
bool object_step_done(PrintObjectStep step) bool object_step_done(PrintObjectStep step)
%code%{ RETVAL = THIS->step_done(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)
%code%{ THIS->state.set_started(step); %};
void clear_filament_stats()
%code%{
THIS->filament_stats.clear();
%};
void set_filament_stats(int extruder_id, float length)
%code%{
THIS->filament_stats.insert(std::pair<size_t,float>(extruder_id, 0));
THIS->filament_stats[extruder_id] += length;
%};
SV* filament_stats() SV* filament_stats()
%code%{ %code%{
HV* hv = newHV(); HV* hv = newHV();
@ -203,7 +138,6 @@ _constant()
RETVAL = newRV_noinc((SV*)hv); RETVAL = newRV_noinc((SV*)hv);
} }
%}; %};
void _simplify_slices(double distance);
double max_allowed_layer_height() const; double max_allowed_layer_height() const;
bool has_support_material() const; bool has_support_material() const;
void auto_assign_extruders(ModelObject* model_object); void auto_assign_extruders(ModelObject* model_object);
@ -220,7 +154,6 @@ _constant()
bool apply_config(DynamicPrintConfig* config) bool apply_config(DynamicPrintConfig* config)
%code%{ RETVAL = THIS->apply_config(*config); %}; %code%{ RETVAL = THIS->apply_config(*config); %};
bool has_infinite_skirt(); bool has_infinite_skirt();
bool has_skirt();
std::vector<unsigned int> extruders() const; std::vector<unsigned int> extruders() const;
int validate() %code%{ int validate() %code%{
std::string err = THIS->validate(); std::string err = THIS->validate();
@ -230,16 +163,33 @@ _constant()
%}; %};
Clone<BoundingBox> bounding_box(); Clone<BoundingBox> bounding_box();
Clone<BoundingBox> total_bounding_box(); Clone<BoundingBox> total_bounding_box();
double skirt_first_layer_height();
Clone<Flow> brim_flow();
Clone<Flow> skirt_flow();
void _make_skirt(); void set_callback_event(int evt) %code%{
void _make_brim(); %};
bool has_wipe_tower(); void process() %code%{
void _clear_wipe_tower(); try {
void _make_wipe_tower(); THIS->process();
} catch (std::exception& e) {
croak(e.what());
}
%};
void export_gcode_with_preview_data(char *path_template, GCodePreviewData *preview_data) %code%{
try {
THIS->export_gcode(path_template, preview_data);
} catch (std::exception& e) {
croak(e.what());
}
%};
void export_gcode(char *path_template) %code%{
try {
THIS->export_gcode(path_template, nullptr);
} catch (std::exception& e) {
croak(e.what());
}
%};
%{ %{

View File

@ -217,6 +217,8 @@ Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
AppConfig* O_OBJECT_SLIC3R AppConfig* O_OBJECT_SLIC3R
Ref<AppConfig> O_OBJECT_SLIC3R_T Ref<AppConfig> O_OBJECT_SLIC3R_T
BackgroundSlicingProcess* O_OBJECT_SLIC3R
Ref<BackgroundSlicingProcess> O_OBJECT_SLIC3R_T
GLShader* O_OBJECT_SLIC3R GLShader* O_OBJECT_SLIC3R
Ref<GLShader> O_OBJECT_SLIC3R_T Ref<GLShader> O_OBJECT_SLIC3R_T

View File

@ -196,6 +196,8 @@
%typemap{Clone<ModelInstancePtrs>}{simple}; %typemap{Clone<ModelInstancePtrs>}{simple};
%typemap{AppConfig*}; %typemap{AppConfig*};
%typemap{Ref<AppConfig>}{simple}; %typemap{Ref<AppConfig>}{simple};
%typemap{BackgroundSlicingProcess*};
%typemap{Ref<BackgroundSlicingProcess>}{simple};
%typemap{GLShader*}; %typemap{GLShader*};
%typemap{Ref<GLShader>}{simple}; %typemap{Ref<GLShader>}{simple};
%typemap{GLVolume*}; %typemap{GLVolume*};