diff --git a/Build.PL b/Build.PL index e006d11eb..3b10235f8 100644 --- a/Build.PL +++ b/Build.PL @@ -20,9 +20,7 @@ my %prereqs = qw( POSIX 0 Scalar::Util 0 Test::More 0 - Thread::Semaphore 0 IO::Scalar 0 - threads 1.96 Time::HiRes 0 ); my %recommends = qw( @@ -44,8 +42,6 @@ if ($gui) { ); if ($^O eq 'MSWin32') { $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; } } diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index e55e24a7a..020edd70f 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -1,5 +1,4 @@ # This package loads all the non-GUI Slic3r perl packages. -# In addition, it implements utility functions for file handling and threading. package Slic3r; @@ -22,22 +21,11 @@ sub debugf { our $loglevel = 0; -# load threads before Moo as required by it 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); 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; # Let the XS module know where the GUI resources reside. @@ -66,17 +54,11 @@ use Slic3r::Print::Object; use Slic3r::Print::Simple; use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; -use Thread::Semaphore; # Scaling between the float and integer coordinates. # Floats are in mm. 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. $Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0; set_logging_level($Slic3r::loglevel); @@ -85,121 +67,6 @@ set_logging_level($Slic3r::loglevel); # class instance in a thread safe manner. 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. sub open { my ($fh, $mode, $filename) = @_; @@ -274,8 +141,4 @@ sub system_info return $out; } -# this package declaration prevents an ugly fatal warning to be emitted when -# spawning a new thread -package GLUquadricObjPtr; - 1; diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 27aa1a59b..dfad9db34 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -13,12 +13,6 @@ sub wkt { 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 { my $self = shift; return Slic3r::Geometry::Clipper::offset(\@$self, @_); diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 75a154281..72423b946 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -101,12 +101,6 @@ use constant GIMBALL_LOCK_THETA_MAX => 170; use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70; 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 { my ($class, $parent) = @_; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 4868ed7e2..98fdbcd3b 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -25,6 +25,10 @@ our $last_config; our $VALUE_CHANGE_EVENT = Wx::NewEventType; # 2) To inform about a preset selection change or a "modified" status change. 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 { my ($class, %params) = @_; @@ -48,6 +52,11 @@ sub new { $self->{lang_ch_event} = $params{lang_ch_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 $self->_init_tabpanel; $self->_init_menubar; @@ -56,12 +65,7 @@ sub new { # SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values # (SetAutoPop is not available on GTK.) 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; # initialize layout @@ -170,6 +174,24 @@ sub _init_tabpanel { for my $tab_name (qw(print filament printer)) { $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}) { $self->{plater}->on_select_preset(sub { @@ -388,7 +410,6 @@ sub on_plater_selection_changed { sub quick_slice { my ($self, %params) = @_; - my $progress_dialog; eval { # validate configuration 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}); } - my $sprint = Slic3r::Print::Simple->new( - print_center => $print_center, - status_cb => sub { - my ($percent, $message) = @_; - $progress_dialog->Update($percent, "$message…"); - }, - ); + my $sprint = Slic3r::Print::Simple->new(print_center => $print_center); + # The C++ slicing core will post a wxCommand message to the main window. + Slic3r::GUI::set_print_callback_event($sprint, $PROGRESS_BAR_EVENT); # keep model around my $model = Slic3r::Model->read_from_file($input_file); @@ -468,9 +485,9 @@ sub quick_slice { } # 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); - $progress_dialog->Pulse; + $self->{progress_dialog}->Pulse; { my @warnings = (); @@ -482,18 +499,17 @@ sub quick_slice { } else { $sprint->export_gcode; } - $sprint->status_cb(undef); Slic3r::GUI::warning_catcher($self)->($_) for @warnings; } - $progress_dialog->Destroy; - undef $progress_dialog; + $self->{progress_dialog}->Destroy; + undef $self->{progress_dialog}; my $message = $input_file_basename.L(" was successfully sliced."); wxTheApp->notify($message); Wx::MessageDialog->new($self, $message, L('Slicing Done!'), 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 { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d3513897f..b6450fbdd 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -8,7 +8,6 @@ use utf8; use File::Basename qw(basename dirname); use List::Util qw(sum first max); 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 :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 @@ -34,21 +33,16 @@ use constant TB_LAYER_EDITING => &Wx::NewId; 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. -our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType; -our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType; - -use constant FILAMENT_CHOOSERS_SPACING => 0; -use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds +our $SLICING_COMPLETED_EVENT = Wx::NewEventType; +our $PROCESS_COMPLETED_EVENT = Wx::NewEventType; my $PreventListEvents = 0; sub new { my ($class, $parent) = @_; 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( 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 @@ -63,14 +57,14 @@ sub new { # List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter. $self->{objects} = []; $self->{gcode_preview_data} = Slic3r::GCode::PreviewData->new; - - $self->{print}->set_status_cb(sub { - my ($percent, $message) = @_; - my $event = Wx::CommandEvent->new($PROGRESS_BAR_EVENT); - $event->SetString($message); - $event->SetInt($percent); - Wx::PostEvent($self, $event); - }); + $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); + + # The C++ slicing core will post a wxCommand message to the main window. + Slic3r::GUI::set_print_callback_event($self->{print}, $Slic3r::GUI::MainFrame::PROGRESS_BAR_EVENT); # Initialize preview notebook $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM); @@ -319,19 +313,9 @@ sub new { for grep defined($_), $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) = @_; - $self->on_progress_event($event->GetInt, $event->GetString); - }); - - 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); + $self->on_update_print_preview; }); EVT_COMMAND($self, -1, $PROCESS_COMPLETED_EVENT, sub { @@ -788,8 +772,7 @@ sub bed_centerf { } sub remove { - my $self = shift; - my ($obj_idx) = @_; + my ($self, $obj_idx) = @_; $self->stop_background_process; @@ -797,7 +780,7 @@ sub remove { $self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D}; $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) { ($obj_idx, undef) = $self->selected_object; return if ! defined $obj_idx; @@ -811,11 +794,10 @@ sub remove { $self->select_object(undef); $self->update; - $self->schedule_background_process; } sub reset { - my $self = shift; + my ($self) = @_; $self->stop_background_process; @@ -840,6 +822,7 @@ sub increase { return if ! defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; my $instance = $model_object->instances->[-1]; + $self->stop_background_process; for my $i (1..$copies) { $instance = $model_object->add_instance( offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}), @@ -849,15 +832,8 @@ sub increase { $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); - - # only autoarrange if user has autocentering enabled - $self->stop_background_process; - if (wxTheApp->{app_config}->get("autocenter")) { - $self->arrange; - } else { - $self->update; - } - $self->schedule_background_process; + # Only autoarrange if user has autocentering enabled. + wxTheApp->{app_config}->get("autocenter") ? $self->arrange : $self->update; } sub decrease { @@ -866,10 +842,9 @@ sub decrease { my ($obj_idx, $object) = $self->selected_object; return if ! defined $obj_idx; - $self->stop_background_process; - my $model_object = $self->{model}->objects->[$obj_idx]; if ($model_object->instances_count > $copies) { + $self->stop_background_process; for my $i (1..$copies) { $model_object->delete_last_instance; $self->{print}->objects->[$obj_idx]->delete_last_copy; @@ -877,10 +852,10 @@ sub decrease { $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); } elsif (defined $copies_asked) { # The "decrease" came from the "set number of copies" dialog. + $self->stop_background_process; $self->remove; } else { # The "decrease" came from the "-" button. Don't allow the object to disappear. - $self->resume_background_process; return; } @@ -889,24 +864,18 @@ sub decrease { $self->{list}->Select($obj_idx, 1); } $self->update; - $self->schedule_background_process; } sub set_number_of_copies { my ($self) = @_; - - $self->pause_background_process; - # get current number of copies 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 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; if ($diff == 0) { # no variation - $self->resume_background_process; } elsif ($diff > 0) { $self->increase($diff); } elsif ($diff < 0) { @@ -981,7 +950,6 @@ sub rotate { $self->selection_changed; # refresh info (size etc.) $self->update; - $self->schedule_background_process; } sub mirror { @@ -1011,7 +979,6 @@ sub mirror { $self->selection_changed; # refresh info (size etc.) $self->update; - $self->schedule_background_process; } sub changescale { @@ -1086,14 +1053,12 @@ sub changescale { $self->selection_changed(1); # refresh info (size, volume etc.) $self->update; - $self->schedule_background_process; } sub arrange { - my $self = shift; - - $self->pause_background_process; + my ($self) = @_; + $self->stop_background_process; 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); # 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; } - $self->pause_background_process; + $self->stop_background_process; my @model_objects = @{$current_model_object->split_object}; 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.")); - $self->resume_background_process; - return; + $self->schedule_background_process; + } 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 { my ($self) = @_; - - if (defined $self->{apply_config_timer}) { - $self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot - } + $self->{apply_config_timer}->Start(0.5 * 1000, 1); # 1 = one shot, every half a second. } # Executed asynchronously by a timer every PROCESS_DELAY (0.5 second). # The timer is started by schedule_background_process(), sub async_apply_config { my ($self) = @_; - - # pause process thread before applying new config - # since we don't want to touch data that is being used by the threads - $self->pause_background_process; - - # 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); + # Apply new config to the possibly running background task. + my $was_running = $self->{background_slicing_process}->running; + my $invalidated = $self->{background_slicing_process}->apply_config(wxTheApp->{preset_bundle}->full_config); + # Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile. $self->{canvas3D}->Refresh if ($self->{canvas3D}->layer_editing_enabled); - - # 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 the apply_config caused the calculation to stop, or it was not running yet: if ($invalidated) { - $self->{gcode_preview_data}->reset; - $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; - $self->{preview3D}->reload_print if $self->{preview3D}; + if ($was_running) { + # Hide the slicing results if the current slicing status is no more valid. + $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 { my ($self) = @_; - - return if !@{$self->{objects}}; - 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 + return if ! @{$self->{objects}} || $self->{background_slicing_process}->running; + # Don't start process thread if config is not valid. eval { # this will throw errors if config is not valid wxTheApp->{preset_bundle}->full_config->validate; @@ -1204,79 +1147,29 @@ sub start_background_process { if ($@) { $self->statusbar->SetStatusText($@); return; - } - + } # Copy the names of active presets into the placeholder parser. wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); - - # start thread - @_ = (); - $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"; + # Start the background process. + $self->{background_slicing_process}->start; } +# Stop the background processing sub stop_background_process { my ($self) = @_; - - $self->{apply_config_timer}->Stop if defined $self->{apply_config_timer}; + # Don't call async_apply_config() while stopped. + $self->{apply_config_timer}->Stop; $self->statusbar->SetCancelCallback(undef); $self->statusbar->StopBusy; $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->{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 { # explicitly cancel a previous thread and start a new one. my ($self) = @_; @@ -1371,79 +1264,22 @@ sub export_gcode { return $self->{export_gcode_output_file}; } -# This gets called only if we have threads. -sub on_process_completed { - my ($self, $error) = @_; - - $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; +# This message should be called by the background process synchronously. +sub on_update_print_preview { + my ($self) = @_; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $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. # This gets called also if we don't have threads. -sub on_export_completed { +sub on_process_completed { my ($self, $result) = @_; $self->statusbar->SetCancelCallback(undef); $self->statusbar->StopBusy; $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 $send_gcode = 0; my $do_print = 0; @@ -1470,7 +1306,7 @@ sub on_export_completed { # Send $self->{send_gcode_file} to OctoPrint. if ($send_gcode) { 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; @@ -1636,27 +1472,15 @@ sub reset_thumbnail { # (i.e. when an object is added/removed/moved/rotated/scaled) sub update { my ($self, $force_autocenter) = @_; - if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) { $self->{model}->center_instances_around_point($self->bed_centerf); } - - my $running = $self->pause_background_process; - 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->stop_background_process; + $self->{print}->reload_model_instances(); $self->{canvas}->reload_scene if $self->{canvas}; $self->{canvas3D}->reload_scene if $self->{canvas3D}; $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. @@ -1679,7 +1503,7 @@ sub on_extruders_change { $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; # insert new choice into sizer $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); - $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); + $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, 0); # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; @@ -1854,7 +1678,7 @@ sub object_settings_dialog { model_object => $model_object, config => wxTheApp->{preset_bundle}->full_config, ); - $self->pause_background_process; + $self->stop_background_process; $dlg->ShowModal; # update thumbnail since parts may have changed @@ -1866,13 +1690,12 @@ sub object_settings_dialog { # update print if ($dlg->PartsChanged || $dlg->PartSettingsChanged) { - $self->stop_background_process; $self->{print}->reload_object($obj_idx); $self->schedule_background_process; $self->{canvas}->reload_scene if $self->{canvas}; $self->{canvas3D}->reload_scene if $self->{canvas3D}; } else { - $self->resume_background_process; + $self->schedule_background_process; } } diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 96a252a08..f6edcbe24 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -152,12 +152,6 @@ __PACKAGE__->mk_accessors(qw( _simulation_mode )); -# make OpenGL::Array thread-safe -{ - no warnings 'redefine'; - *OpenGL::Array::CLONE_SKIP = sub { 1 }; -} - sub new { my ($class, $parent, $print) = @_; diff --git a/lib/Slic3r/Point.pm b/lib/Slic3r/Point.pm index 535a97194..1134138ea 100644 --- a/lib/Slic3r/Point.pm +++ b/lib/Slic3r/Point.pm @@ -7,11 +7,6 @@ sub new_scale { return $class->new(map Slic3r::Geometry::scale($_), @_); } -sub dump_perl { - my $self = shift; - return sprintf "[%s,%s]", @$self; -} - package Slic3r::Pointf; use strict; use warnings; diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 9cc142409..a42b5d1c4 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -10,9 +10,4 @@ sub new_scale { return $class->new(map [ Slic3r::Geometry::scale($_->[X]), Slic3r::Geometry::scale($_->[Y]) ], @points); } -sub dump_perl { - my $self = shift; - return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self; -} - 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 8300fdbed..5a0ddd38f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -15,82 +15,16 @@ use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection o union JT_ROUND JT_SQUARE); 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 { my $self = shift; return $self->bounding_box->size; } -# Slicing process, running at a background thread. -sub process { - 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); - } - +sub run_post_process_scripts { + my ($self, $output_file) = @_; # run post-processing scripts if (@{$self->config->post_process}) { - $self->status_cb->(95, "Running post-processing scripts"); +# $self->status_cb->(95, "Running post-processing scripts"); $self->config->setenv; for my $script (@{$self->config->post_process}) { # Ignore empty post processing script lines. @@ -205,67 +139,4 @@ EOF 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; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index e275fdde5..7370881ea 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -21,95 +21,4 @@ sub support_layers { 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; diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm index 4fe3eb820..9dbc7fefa 100644 --- a/lib/Slic3r/Print/Simple.pm +++ b/lib/Slic3r/Print/Simple.pm @@ -38,11 +38,6 @@ has 'duplicate_grid' => ( default => sub { [1,1] }, ); -has 'status_cb' => ( - is => 'rw', - default => sub { sub {} }, -); - has 'print_center' => ( is => 'rw', 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 { my ($self) = @_; - - $self->_before_export; - $self->_print->export_gcode(output_file => $self->output_file); - $self->_after_export; + $self->_print->validate; + $self->_print->export_gcode($self->output_file // ''); } sub export_svg { my ($self) = @_; - - $self->_before_export; - + $self->_print->validate; $self->_print->export_svg(output_file => $self->output_file); - - $self->_after_export; } 1; diff --git a/slic3r.pl b/slic3r.pl index d9bed0ab6..0d58647c0 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -202,10 +202,6 @@ if (@ARGV) { # slicing from command line duplicate_grid => $opt{duplicate_grid} // [1,1], print_center => $opt{print_center} // Slic3r::Pointf->new(100,100), dont_arrange => $opt{dont_arrange} // 0, - status_cb => sub { - my ($percent, $message) = @_; - printf "=> %s\n", $message; - }, output_file => $opt{output}, ); diff --git a/t/threads.t b/t/threads.t deleted file mode 100644 index 7fede3328..000000000 --- a/t/threads.t +++ /dev/null @@ -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__ diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 63b99a835..0a76721a4 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -171,6 +171,8 @@ add_library(libslic3r STATIC add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/AppConfig.cpp ${LIBDIR}/slic3r/GUI/AppConfig.hpp + ${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.cpp + ${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.hpp ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp @@ -331,6 +333,7 @@ set(XS_XSP_FILES ${XSP_DIR}/Geometry.xsp ${XSP_DIR}/GUI.xsp ${XSP_DIR}/GUI_AppConfig.xsp + ${XSP_DIR}/GUI_BackgroundSlicingProcess.xsp ${XSP_DIR}/GUI_3DScene.xsp ${XSP_DIR}/GUI_Preset.xsp ${XSP_DIR}/GUI_Tab.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 47a584343..5105ee0ad 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -239,23 +239,16 @@ sub new { ); } -package Slic3r::GUI::_3DScene::GLShader; -sub CLONE_SKIP { 1 } - package Slic3r::GUI::_3DScene::GLVolume::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -sub CLONE_SKIP { 1 } - package Slic3r::GUI::PresetCollection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -sub CLONE_SKIP { 1 } - package main; for my $class (qw( Slic3r::BridgeDetector diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index a3f5e231f..a74afdaec 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -5,11 +5,13 @@ #include "Flow.hpp" #include "Geometry.hpp" #include "SupportMaterial.hpp" +#include "GCode.hpp" #include "GCode/WipeTowerPrusaMM.hpp" #include #include #include #include +#include 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() { // 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() { - 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. m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true); if (! m_tool_ordering.has_wipe_tower()) @@ -1153,9 +1216,4 @@ std::string Print::output_filepath(const std::string &path) return path; } -void Print::set_status(int percent, const std::string &message) -{ - printf("Print::status %d => %s\n", percent, message.c_str()); -} - } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index c56e64c6c..dfde2650a 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "BoundingBox.hpp" #include "Flow.hpp" #include "PrintConfig.hpp" @@ -23,6 +24,7 @@ namespace Slic3r { class Print; class PrintObject; class ModelObject; +class GCodePreviewData; // Print step IDs for keeping track of the print state. enum PrintStep { @@ -190,23 +192,26 @@ public: // (layer height, first layer height, raft settings, print nozzle diameter etc). SlicingParameters slicing_parameters() const; +private: + void slice(); + void make_perimeters(); + void prepare_infill(); + void infill(); + void generate_support_material(); + void _slice(); std::string _fix_slicing_errors(); void _simplify_slices(double distance); - void _prepare_infill(); bool has_support_material() const; void detect_surfaces_type(); void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); - void _make_perimeters(); - void _infill(); void clip_fill_surfaces(); void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); -private: Print* _print; ModelObject* _model_object; Points _copies; // Slic3r::Point objects in scaled G-code coordinates @@ -232,7 +237,6 @@ public: PrintObjectPtrs objects; PrintRegionPtrs regions; PlaceholderParser placeholder_parser; - // TODO: status_cb std::string estimated_print_time; double total_used_filament, total_extruded_volume, total_cost, total_weight; std::map filament_stats; @@ -283,13 +287,11 @@ public: bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object) const; - void _make_skirt(); - void _make_brim(); + void process(); + void export_gcode(const std::string &path_template, GCodePreviewData *preview_data); // Wipe tower support. 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. // Cache it here, so it does not need to be recalculated during the G-code generation. ToolOrdering m_tool_ordering; @@ -301,8 +303,14 @@ public: std::string output_filename(); std::string output_filepath(const std::string &path); + typedef std::function 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. - 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. void cancel() { m_canceled = true; } // 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 &opt_keys); 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? - tbb::atomic m_canceled; + tbb::atomic 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) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index d7ee1e591..298fe9207 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -111,6 +111,7 @@ PrintConfigDef::PrintConfigDef() "with cooling (use a fan) before tweaking this."); def->cli = "bridge-flow-ratio=f"; def->min = 0; + def->max = 2; def->default_value = new ConfigOptionFloat(1); def = this->add("bridge_speed", coFloat); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index c61fc102b..dd2c4e377 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -31,6 +31,8 @@ #include #endif +#define PARALLEL_FOR_CANCEL do { if (this->print()->canceled()) return; } while (0) + namespace Slic3r { 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); } +// 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 ®ion = **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(0, this->layers.size() - 1), + [this, ®ion, region_id](const tbb::blocked_range& 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(0, this->layers.size()), + [this](const tbb::blocked_range& 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(0, this->layers.size()), + [this](const tbb::blocked_range& 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() { for (Layer *l : this->layers) @@ -282,105 +583,6 @@ bool PrintObject::has_support_material() const || 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). // 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. @@ -427,6 +629,7 @@ void PrintObject::detect_surfaces_type() (this->config.support_material.value && this->config.support_material_contact_distance.value == 0) ? stBottom : stBottomBridge; 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; Layer *layer = this->layers[idx_layer]; LayerRegion *layerm = layer->get_region(idx_region); @@ -564,6 +767,7 @@ void PrintObject::detect_surfaces_type() tbb::blocked_range(0, this->layers.size()), [this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range& range) { 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); layerm->slices_to_fill_surfaces_clipped(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -590,6 +794,7 @@ void PrintObject::process_external_surfaces() tbb::blocked_range(0, this->layers.size()), [this, region_id](const tbb::blocked_range& range) { 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; 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 size_t num_regions = this->_print->regions.size(); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + PARALLEL_FOR_CANCEL; const Layer &layer = *this->layers[idx_layer]; DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer]; // 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& range) { const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + PARALLEL_FOR_CANCEL; Layer &layer = *this->layers[idx_layer]; LayerRegion &layerm = *layer.regions[idx_region]; 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()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { PROFILE_BLOCK(discover_vertical_shells_region_layer); - + PARALLEL_FOR_CANCEL; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; ++ debug_idx; @@ -1265,6 +1472,7 @@ end: tbb::blocked_range(0, this->layers.size()), [this](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + PARALLEL_FOR_CANCEL; Layer *layer = this->layers[layer_id]; // Apply size compensation and perform clipping of multi-part objects. float delta = float(scale_(this->config.xy_size_compensation.value)); @@ -1348,6 +1556,7 @@ std::string PrintObject::_fix_slicing_errors() tbb::blocked_range(0, buggy_layers.size()), [this, &buggy_layers](const tbb::blocked_range& range) { 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]; Layer *layer = this->layers[idx_layer]; assert(layer->slicing_errors); @@ -1424,6 +1633,7 @@ void PrintObject::_simplify_slices(double distance) tbb::blocked_range(0, this->layers.size()), [this, distance](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + PARALLEL_FOR_CANCEL; Layer *layer = this->layers[layer_idx]; for (size_t region_idx = 0; region_idx < layer->regions.size(); ++ region_idx) 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"; } -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 ®ion = **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(0, this->layers.size() - 1), - [this, ®ion, region_id](const tbb::blocked_range& 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(0, this->layers.size()), - [this](const tbb::blocked_range& 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(0, this->layers.size()), - [this](const tbb::blocked_range& 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, // 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. diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index d7c9a590a..58d553966 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -56,6 +56,7 @@ REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2"); REGISTER_CLASS(TriangleMesh, "TriangleMesh"); REGISTER_CLASS(AppConfig, "GUI::AppConfig"); +REGISTER_CLASS(BackgroundSlicingProcess, "GUI::BackgroundSlicingProcess"); REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader"); REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume"); REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection"); diff --git a/xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp new file mode 100644 index 000000000..900fdfa43 --- /dev/null +++ b/xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -0,0 +1,138 @@ +#include "BackgroundSlicingProcess.hpp" +#include "GUI.hpp" + +#include "../../libslic3r/Print.hpp" + +#include +#include + +//#undef NDEBUG +#include +#include + +namespace Slic3r { + +namespace GUI { + extern wxPanel *g_wxPlater; +}; + +void BackgroundSlicingProcess::thread_proc() +{ + std::unique_lock 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 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 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 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 diff --git a/xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp new file mode 100644 index 000000000..988138d2e --- /dev/null +++ b/xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -0,0 +1,82 @@ +#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_ +#define slic3r_GUI_BackgroundSlicingProcess_hpp_ + +#include +#include +#include +#include + +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_ */ diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 0410b7969..270a5d198 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -47,6 +47,8 @@ #include "Preferences.hpp" #include "PresetBundle.hpp" +#include "../../libslic3r/Print.hpp" + namespace Slic3r { namespace GUI { #if __APPLE__ @@ -172,6 +174,7 @@ void break_to_debugger() wxApp *g_wxApp = nullptr; wxFrame *g_wxMainFrame = nullptr; wxNotebook *g_wxTabPanel = nullptr; +wxPanel *g_wxPlater = nullptr; AppConfig *g_AppConfig = nullptr; PresetBundle *g_PresetBundle= nullptr; @@ -197,6 +200,11 @@ void set_tab_panel(wxNotebook *tab_panel) g_wxTabPanel = tab_panel; } +void set_plater(wxPanel *plater) +{ + g_wxPlater = plater; +} + void set_app_config(AppConfig *app_config) { g_AppConfig = app_config; @@ -507,6 +515,18 @@ void warning_catcher(wxWindow* parent, wxString message){ 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(){ return g_wxApp; } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 084b6de46..506454e56 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -11,6 +11,7 @@ class wxFrame; class wxWindow; class wxMenuBar; class wxNotebook; +class wxPanel; class wxComboCtrl; class wxString; class wxArrayString; @@ -23,6 +24,7 @@ namespace Slic3r { class PresetBundle; class PresetCollection; +class Print; class AppConfig; class DynamicPrintConfig; class TabIface; @@ -73,6 +75,7 @@ void break_to_debugger(); void set_wxapp(wxApp *app); void set_main_frame(wxFrame *main_frame); void set_tab_panel(wxNotebook *tab_panel); +void set_plater(wxPanel *plater); void set_app_config(AppConfig *app_config); 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 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 bool load_language(); // save language at application config diff --git a/xs/t/20_print.t b/xs/t/20_print.t index e535cdd8c..0ef194ecf 100644 --- a/xs/t/20_print.t +++ b/xs/t/20_print.t @@ -10,8 +10,6 @@ use Test::More tests => 5; my $print = Slic3r::Print->new; isa_ok $print, 'Slic3r::Print'; 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'; } diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index d306f12ce..a7f3bd554 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -32,6 +32,9 @@ void set_main_frame(SV *ui) void set_tab_panel(SV *ui) %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) %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"), (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) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; diff --git a/xs/xsp/GUI_BackgroundSlicingProcess.xsp b/xs/xsp/GUI_BackgroundSlicingProcess.xsp new file mode 100644 index 000000000..d12c7f8e3 --- /dev/null +++ b/xs/xsp/GUI_BackgroundSlicingProcess.xsp @@ -0,0 +1,23 @@ + +%module{Slic3r::XS}; + +%{ +#include +#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(); +}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index cbc04a804..4ad4f74b6 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -27,7 +27,6 @@ _constant() %} - %name{Slic3r::Print::Region} class PrintRegion { // owned by Print, no constructor/destructor @@ -39,16 +38,9 @@ _constant() %code%{ RETVAL = THIS->flow(role, layer_height, bridge, first_layer, width, *object); %}; }; - %name{Slic3r::Print::Object} class PrintObject { // owned by Print, no constructor/destructor - void add_region_volume(int region_id, int volume_id); - std::vector 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() %code%{ RETVAL = THIS->print()->regions.size(); %}; @@ -67,57 +59,22 @@ _constant() Points _shifted_copies() %code%{ RETVAL = THIS->_shifted_copies; %}; - void set_shifted_copies(Points value) - %code%{ THIS->_shifted_copies = value; %}; bool add_copy(Pointf* point) %code%{ RETVAL = THIS->add_copy(*point); %}; bool delete_last_copy(); - bool delete_all_copies(); - bool set_copies(Points copies); bool reload_model_instances(); void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) %code%{ THIS->layer_height_ranges = layer_height_ranges; %}; - void set_layer_height_profile(std::vector profile) - %code%{ THIS->layer_height_profile = profile; %}; - size_t total_layer_count(); size_t layer_count(); - void clear_layers(); Ref get_layer(int idx); - Ref add_layer(int id, coordf_t height, coordf_t print_z, - coordf_t slice_z); size_t support_layer_count(); - void clear_support_layers(); Ref get_support_layer(int idx); bool step_done(PrintObjectStep 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 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) %code%{ @@ -129,25 +86,16 @@ _constant() %}; void reset_layer_height_profile(); - - int ptr() - %code%{ RETVAL = (int)(intptr_t)THIS; %}; }; - %name{Slic3r::Print} class Print { Print(); ~Print(); Ref config() %code%{ RETVAL = &THIS->config; %}; - Ref default_object_config() - %code%{ RETVAL = &THIS->default_object_config; %}; - Ref default_region_config() - %code%{ RETVAL = &THIS->default_region_config; %}; Ref placeholder_parser() %code%{ RETVAL = &THIS->placeholder_parser; %}; - // TODO: status_cb Ref skirt() %code%{ RETVAL = &THIS->skirt; %}; Ref brim() @@ -176,20 +124,7 @@ _constant() %code%{ RETVAL = THIS->state.is_done(step); %}; bool object_step_done(PrintObjectStep step) %code%{ RETVAL = THIS->step_done(step); %}; - void set_step_done(PrintStep step) - %code%{ THIS->state.set_done(step); %}; - void set_step_started(PrintStep step) - %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(extruder_id, 0)); - THIS->filament_stats[extruder_id] += length; - %}; SV* filament_stats() %code%{ HV* hv = newHV(); @@ -203,7 +138,6 @@ _constant() RETVAL = newRV_noinc((SV*)hv); } %}; - void _simplify_slices(double distance); double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object); @@ -220,7 +154,6 @@ _constant() bool apply_config(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config(*config); %}; bool has_infinite_skirt(); - bool has_skirt(); std::vector extruders() const; int validate() %code%{ std::string err = THIS->validate(); @@ -230,16 +163,33 @@ _constant() %}; Clone bounding_box(); Clone total_bounding_box(); - double skirt_first_layer_height(); - Clone brim_flow(); - Clone skirt_flow(); - void _make_skirt(); - void _make_brim(); + void set_callback_event(int evt) %code%{ + %}; - bool has_wipe_tower(); - void _clear_wipe_tower(); - void _make_wipe_tower(); + void process() %code%{ + try { + 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()); + } + %}; %{ diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 87a8d8d86..67693685a 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -217,6 +217,8 @@ Clone O_OBJECT_SLIC3R_T AppConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T +BackgroundSlicingProcess* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T GLShader* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 0214a158d..461a12e74 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -196,6 +196,8 @@ %typemap{Clone}{simple}; %typemap{AppConfig*}; %typemap{Ref}{simple}; +%typemap{BackgroundSlicingProcess*}; +%typemap{Ref}{simple}; %typemap{GLShader*}; %typemap{Ref}{simple}; %typemap{GLVolume*};