mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-17 03:37:54 -06:00
Partial work for background processing
This commit is contained in:
parent
97231327e0
commit
d9e7a50a6e
7 changed files with 164 additions and 64 deletions
|
@ -34,7 +34,9 @@ sub apply_config {
|
||||||
my ($self, $config) = @_;
|
my ($self, $config) = @_;
|
||||||
|
|
||||||
# options with single value
|
# options with single value
|
||||||
my @opt_keys = grep !$Slic3r::Config::Options->{$_}{multiline}, @{$config->get_keys};
|
my @opt_keys = grep $Slic3r::Config::Options->{$_}{cli} !~ /\@$/,
|
||||||
|
grep !$Slic3r::Config::Options->{$_}{multiline},
|
||||||
|
@{$config->get_keys};
|
||||||
$self->_single_set($_, $config->serialize($_)) for @opt_keys;
|
$self->_single_set($_, $config->serialize($_)) for @opt_keys;
|
||||||
|
|
||||||
# options with multiple values
|
# options with multiple values
|
||||||
|
|
|
@ -33,6 +33,7 @@ our $PROGRESS_BAR_EVENT : shared = Wx::NewEventType;
|
||||||
our $MESSAGE_DIALOG_EVENT : shared = Wx::NewEventType;
|
our $MESSAGE_DIALOG_EVENT : shared = Wx::NewEventType;
|
||||||
our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType;
|
our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType;
|
||||||
our $EXPORT_FAILED_EVENT : shared = Wx::NewEventType;
|
our $EXPORT_FAILED_EVENT : shared = Wx::NewEventType;
|
||||||
|
our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType;
|
||||||
|
|
||||||
use constant CANVAS_SIZE => [335,335];
|
use constant CANVAS_SIZE => [335,335];
|
||||||
use constant FILAMENT_CHOOSERS_SPACING => 3;
|
use constant FILAMENT_CHOOSERS_SPACING => 3;
|
||||||
|
@ -218,6 +219,14 @@ sub new {
|
||||||
$self->on_export_failed;
|
$self->on_export_failed;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
EVT_COMMAND($self, -1, $PROCESS_COMPLETED_EVENT, sub {
|
||||||
|
my ($self, $event) = @_;
|
||||||
|
|
||||||
|
Slic3r::debugf "Background processing completed.\n";
|
||||||
|
$self->{process_thread}->detach if $self->{process_thread};
|
||||||
|
$self->{process_thread} = undef;
|
||||||
|
});
|
||||||
|
|
||||||
$self->{canvas}->update_bed_size;
|
$self->{canvas}->update_bed_size;
|
||||||
$self->update;
|
$self->update;
|
||||||
|
|
||||||
|
@ -439,6 +448,9 @@ sub objects_loaded {
|
||||||
$self->{list}->Update;
|
$self->{list}->Update;
|
||||||
$self->{list}->Select($obj_idxs->[-1], 1);
|
$self->{list}->Select($obj_idxs->[-1], 1);
|
||||||
$self->object_list_changed;
|
$self->object_list_changed;
|
||||||
|
|
||||||
|
# TODO: start timer for new export thread
|
||||||
|
$self->start_background_process;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub remove {
|
sub remove {
|
||||||
|
@ -664,10 +676,60 @@ sub split_object {
|
||||||
$self->load_model_objects(@model_objects);
|
$self->load_model_objects(@model_objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub start_background_process {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return if !$Slic3r::have_threads;
|
||||||
|
return if !@{$self->{objects}};
|
||||||
|
|
||||||
|
if ($self->{process_thread}) {
|
||||||
|
warn "Can't start new process thread because one is already running\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# It looks like declaring a local $SIG{__WARN__} prevents the ugly
|
||||||
|
# "Attempt to free unreferenced scalar" warning...
|
||||||
|
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
|
||||||
|
|
||||||
|
# don't start process thread if config is not valid
|
||||||
|
eval {
|
||||||
|
# this will throw errors if config is not valid
|
||||||
|
$self->skeinpanel->config->validate;
|
||||||
|
$self->{print}->validate;
|
||||||
|
};
|
||||||
|
return if $@;
|
||||||
|
|
||||||
|
# apply extra variables
|
||||||
|
{
|
||||||
|
my $extra = $self->skeinpanel->extra_variables;
|
||||||
|
$self->{print}->placeholder_parser->set($_, $extra->{$_}) for keys %$extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
# start thread
|
||||||
|
$self->{process_thread} = threads->create(sub {
|
||||||
|
$self->{print}->process;
|
||||||
|
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, undef));
|
||||||
|
Slic3r::thread_cleanup();
|
||||||
|
});
|
||||||
|
Slic3r::debugf "Background processing started.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub stop_background_process {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
if ($self->{process_thread}) {
|
||||||
|
Slic3r::debugf "Killing background process.\n";
|
||||||
|
$self->{process_thread}->kill('KILL')->join;
|
||||||
|
$self->{process_thread} = undef;
|
||||||
|
} else {
|
||||||
|
Slic3r::debugf "No background process running.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub export_gcode {
|
sub export_gcode {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
if ($self->{export_thread}) {
|
if ($self->{export_gcode_output_file}) {
|
||||||
Wx::MessageDialog->new($self, "Another slicing job is currently running.", 'Error', wxOK | wxICON_ERROR)->ShowModal;
|
Wx::MessageDialog->new($self, "Another slicing job is currently running.", 'Error', wxOK | wxICON_ERROR)->ShowModal;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -912,6 +974,7 @@ sub update {
|
||||||
sub on_config_change {
|
sub on_config_change {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($opt_key, $value) = @_;
|
my ($opt_key, $value) = @_;
|
||||||
|
|
||||||
if ($opt_key eq 'extruders_count' && defined $value) {
|
if ($opt_key eq 'extruders_count' && defined $value) {
|
||||||
my $choices = $self->{preset_choosers}{filament};
|
my $choices = $self->{preset_choosers}{filament};
|
||||||
while (@$choices < $value) {
|
while (@$choices < $value) {
|
||||||
|
@ -937,6 +1000,17 @@ sub on_config_change {
|
||||||
}
|
}
|
||||||
$self->update if $opt_key eq 'print_center';
|
$self->update if $opt_key eq 'print_center';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return if !$self->skeinpanel->is_loaded;
|
||||||
|
# TODO: pause export thread
|
||||||
|
my $invalidated = $self->{print}->apply_config($self->skeinpanel->config);
|
||||||
|
if ($invalidated) {
|
||||||
|
# kill export thread
|
||||||
|
$self->stop_background_process;
|
||||||
|
|
||||||
|
# TODO: start timer for new export thread
|
||||||
|
$self->start_background_process;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub list_item_deselected {
|
sub list_item_deselected {
|
||||||
|
|
|
@ -31,6 +31,7 @@ sub new {
|
||||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||||
$self->{mode} = $params{mode};
|
$self->{mode} = $params{mode};
|
||||||
$self->{mode} = 'expert' if $self->{mode} !~ /^(?:simple|expert)$/;
|
$self->{mode} = 'expert' if $self->{mode} !~ /^(?:simple|expert)$/;
|
||||||
|
$self->{loaded} = 0;
|
||||||
|
|
||||||
$self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
|
$self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
|
||||||
$self->{tabpanel}->AddPage($self->{plater} = Slic3r::GUI::Plater->new($self->{tabpanel}), "Plater")
|
$self->{tabpanel}->AddPage($self->{plater} = Slic3r::GUI::Plater->new($self->{tabpanel}), "Plater")
|
||||||
|
@ -81,9 +82,15 @@ sub new {
|
||||||
$self->SetSizer($sizer);
|
$self->SetSizer($sizer);
|
||||||
$self->Layout;
|
$self->Layout;
|
||||||
|
|
||||||
|
$self->{loaded} = 1;
|
||||||
return $self;
|
return $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub is_loaded {
|
||||||
|
my ($self) = @_;
|
||||||
|
return $self->{loaded};
|
||||||
|
}
|
||||||
|
|
||||||
sub quick_slice {
|
sub quick_slice {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my %params = @_;
|
my %params = @_;
|
||||||
|
|
|
@ -50,10 +50,8 @@ sub apply_config {
|
||||||
if (@$print_diff) {
|
if (@$print_diff) {
|
||||||
$self->config->apply_dynamic($config);
|
$self->config->apply_dynamic($config);
|
||||||
|
|
||||||
my $res;
|
$invalidated = 1
|
||||||
$res = $self->invalidate_all_steps
|
if $self->invalidate_state_by_config_options($print_diff);
|
||||||
if !$self->invalidate_state_by_config_options($print_diff);
|
|
||||||
$invalidated = 1 if $res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# handle changes to object config defaults
|
# handle changes to object config defaults
|
||||||
|
@ -74,10 +72,8 @@ sub apply_config {
|
||||||
if (@$diff) {
|
if (@$diff) {
|
||||||
$object->config->apply($new);
|
$object->config->apply($new);
|
||||||
|
|
||||||
my $res;
|
$invalidated = 1
|
||||||
$res = $object->invalidate_all_steps
|
if $self->invalidate_state_by_config_options($diff);
|
||||||
if !$object->invalidate_state_by_config_options($diff);
|
|
||||||
$invalidated = 1 if $res;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,10 +127,8 @@ sub apply_config {
|
||||||
if (@$region_config_diff) {
|
if (@$region_config_diff) {
|
||||||
$region->config->apply($new);
|
$region->config->apply($new);
|
||||||
foreach my $o (@{$self->objects}) {
|
foreach my $o (@{$self->objects}) {
|
||||||
my $res;
|
$invalidated = 1
|
||||||
$res = $o->invalidate_all_steps
|
if $o->invalidate_state_by_config_options($region_config_diff);
|
||||||
if !$o->invalidate_state_by_config_options($region_config_diff);
|
|
||||||
$invalidated = 1 if $res;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,48 +5,39 @@ namespace Slic3r {
|
||||||
|
|
||||||
template <class StepClass>
|
template <class StepClass>
|
||||||
bool
|
bool
|
||||||
PrintState<StepClass>::started(StepClass step) const
|
PrintState<StepClass>::is_started(StepClass step) const
|
||||||
{
|
{
|
||||||
return this->_started.find(step) != this->_started.end();
|
return this->started.find(step) != this->started.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class StepClass>
|
template <class StepClass>
|
||||||
bool
|
bool
|
||||||
PrintState<StepClass>::done(StepClass step) const
|
PrintState<StepClass>::is_done(StepClass step) const
|
||||||
{
|
{
|
||||||
return this->_done.find(step) != this->_done.end();
|
return this->done.find(step) != this->done.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class StepClass>
|
template <class StepClass>
|
||||||
void
|
void
|
||||||
PrintState<StepClass>::set_started(StepClass step)
|
PrintState<StepClass>::set_started(StepClass step)
|
||||||
{
|
{
|
||||||
this->_started.insert(step);
|
this->started.insert(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class StepClass>
|
template <class StepClass>
|
||||||
void
|
void
|
||||||
PrintState<StepClass>::set_done(StepClass step)
|
PrintState<StepClass>::set_done(StepClass step)
|
||||||
{
|
{
|
||||||
this->_done.insert(step);
|
this->done.insert(step);
|
||||||
}
|
|
||||||
|
|
||||||
template <class StepClass>
|
|
||||||
void
|
|
||||||
PrintState<StepClass>::invalidate(StepClass step)
|
|
||||||
{
|
|
||||||
this->_started.erase(step);
|
|
||||||
this->_done.erase(step);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class StepClass>
|
template <class StepClass>
|
||||||
bool
|
bool
|
||||||
PrintState<StepClass>::invalidate_all()
|
PrintState<StepClass>::invalidate(StepClass step)
|
||||||
{
|
{
|
||||||
bool empty = this->_started.empty();
|
bool invalidated = this->started.erase(step) > 0;
|
||||||
this->_started.clear();
|
this->done.erase(step);
|
||||||
this->_done.clear();
|
return invalidated;
|
||||||
return !empty; // return true if we invalidated something
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template class PrintState<PrintStep>;
|
template class PrintState<PrintStep>;
|
||||||
|
@ -256,21 +247,23 @@ PrintObject::invalidate_state_by_config_options(const std::vector<t_config_optio
|
||||||
steps.insert(posPerimeters);
|
steps.insert(posPerimeters);
|
||||||
steps.insert(posInfill);
|
steps.insert(posInfill);
|
||||||
} else {
|
} else {
|
||||||
// for legacy, if we can't handle this option let's signal the caller to invalidate all steps
|
// for legacy, if we can't handle this option let's invalidate all steps
|
||||||
return false;
|
return this->invalidate_all_steps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::set<PrintObjectStep>::const_iterator step = steps.begin(); step != steps.end(); ++step)
|
bool invalidated = false;
|
||||||
this->invalidate_step(*step);
|
for (std::set<PrintObjectStep>::const_iterator step = steps.begin(); step != steps.end(); ++step) {
|
||||||
|
if (this->invalidate_step(*step)) invalidated = true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return invalidated;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
PrintObject::invalidate_step(PrintObjectStep step)
|
PrintObject::invalidate_step(PrintObjectStep step)
|
||||||
{
|
{
|
||||||
this->state.invalidate(step);
|
bool invalidated = this->state.invalidate(step);
|
||||||
|
|
||||||
// propagate to dependent steps
|
// propagate to dependent steps
|
||||||
if (step == posPerimeters) {
|
if (step == posPerimeters) {
|
||||||
|
@ -286,6 +279,21 @@ PrintObject::invalidate_step(PrintObjectStep step)
|
||||||
this->invalidate_step(posPerimeters);
|
this->invalidate_step(posPerimeters);
|
||||||
this->invalidate_step(posSupportMaterial);
|
this->invalidate_step(posSupportMaterial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return invalidated;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
PrintObject::invalidate_all_steps()
|
||||||
|
{
|
||||||
|
// make a copy because when invalidating steps the iterators are not working anymore
|
||||||
|
std::set<PrintObjectStep> steps = this->state.started;
|
||||||
|
|
||||||
|
bool invalidated = false;
|
||||||
|
for (std::set<PrintObjectStep>::const_iterator step = steps.begin(); step != steps.end(); ++step) {
|
||||||
|
if (this->invalidate_step(*step)) invalidated = true;
|
||||||
|
}
|
||||||
|
return invalidated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -403,21 +411,23 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
|
||||||
} else if (*opt_key == "brim_width") {
|
} else if (*opt_key == "brim_width") {
|
||||||
steps.insert(psBrim);
|
steps.insert(psBrim);
|
||||||
} else {
|
} else {
|
||||||
// for legacy, if we can't handle this option let's signal the caller to invalidate all steps
|
// for legacy, if we can't handle this option let's invalidate all steps
|
||||||
return false;
|
return this->invalidate_all_steps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::set<PrintStep>::const_iterator step = steps.begin(); step != steps.end(); ++step)
|
bool invalidated = false;
|
||||||
this->invalidate_step(*step);
|
for (std::set<PrintStep>::const_iterator step = steps.begin(); step != steps.end(); ++step) {
|
||||||
|
if (this->invalidate_step(*step)) invalidated = true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return invalidated;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
Print::invalidate_step(PrintStep step)
|
Print::invalidate_step(PrintStep step)
|
||||||
{
|
{
|
||||||
this->state.invalidate(step);
|
bool invalidated = this->state.invalidate(step);
|
||||||
|
|
||||||
// propagate to dependent steps
|
// propagate to dependent steps
|
||||||
if (step == psSkirt) {
|
if (step == psSkirt) {
|
||||||
|
@ -428,6 +438,21 @@ Print::invalidate_step(PrintStep step)
|
||||||
(*object)->invalidate_step(posSupportMaterial);
|
(*object)->invalidate_step(posSupportMaterial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return invalidated;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Print::invalidate_all_steps()
|
||||||
|
{
|
||||||
|
// make a copy because when invalidating steps the iterators are not working anymore
|
||||||
|
std::set<PrintStep> steps = this->state.started;
|
||||||
|
|
||||||
|
bool invalidated = false;
|
||||||
|
for (std::set<PrintStep>::const_iterator step = steps.begin(); step != steps.end(); ++step) {
|
||||||
|
if (this->invalidate_step(*step)) invalidated = true;
|
||||||
|
}
|
||||||
|
return invalidated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,16 +27,14 @@ enum PrintObjectStep {
|
||||||
template <class StepType>
|
template <class StepType>
|
||||||
class PrintState
|
class PrintState
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
std::set<StepType> _started, _done;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool started(StepType step) const;
|
std::set<StepType> started, done;
|
||||||
bool done(StepType step) const;
|
|
||||||
|
bool is_started(StepType step) const;
|
||||||
|
bool is_done(StepType step) const;
|
||||||
void set_started(StepType step);
|
void set_started(StepType step);
|
||||||
void set_done(StepType step);
|
void set_done(StepType step);
|
||||||
void invalidate(StepType step);
|
bool invalidate(StepType step);
|
||||||
bool invalidate_all();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A PrintRegion object represents a group of volumes to print
|
// A PrintRegion object represents a group of volumes to print
|
||||||
|
@ -108,7 +106,8 @@ class PrintObject
|
||||||
|
|
||||||
// methods for handling state
|
// methods for handling state
|
||||||
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
||||||
void invalidate_step(PrintObjectStep step);
|
bool invalidate_step(PrintObjectStep step);
|
||||||
|
bool invalidate_all_steps();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Print* _print;
|
Print* _print;
|
||||||
|
@ -155,7 +154,8 @@ class Print
|
||||||
|
|
||||||
// methods for handling state
|
// methods for handling state
|
||||||
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
||||||
void invalidate_step(PrintStep step);
|
bool invalidate_step(PrintStep step);
|
||||||
|
bool invalidate_all_steps();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void clear_regions();
|
void clear_regions();
|
||||||
|
|
|
@ -86,11 +86,10 @@ _constant()
|
||||||
void delete_support_layer(int idx);
|
void delete_support_layer(int idx);
|
||||||
|
|
||||||
bool invalidate_state_by_config_options(std::vector<std::string> opt_keys);
|
bool invalidate_state_by_config_options(std::vector<std::string> opt_keys);
|
||||||
void invalidate_step(PrintObjectStep step);
|
bool invalidate_step(PrintObjectStep step);
|
||||||
bool invalidate_all_steps()
|
bool invalidate_all_steps();
|
||||||
%code%{ RETVAL = THIS->state.invalidate_all(); %};
|
|
||||||
bool step_done(PrintObjectStep step)
|
bool step_done(PrintObjectStep step)
|
||||||
%code%{ RETVAL = THIS->state.done(step); %};
|
%code%{ RETVAL = THIS->state.is_done(step); %};
|
||||||
void set_step_done(PrintObjectStep step)
|
void set_step_done(PrintObjectStep step)
|
||||||
%code%{ THIS->state.set_done(step); %};
|
%code%{ THIS->state.set_done(step); %};
|
||||||
void set_step_started(PrintObjectStep step)
|
void set_step_started(PrintObjectStep step)
|
||||||
|
@ -141,11 +140,10 @@ _constant()
|
||||||
%code%{ RETVAL = THIS->regions.size(); %};
|
%code%{ RETVAL = THIS->regions.size(); %};
|
||||||
|
|
||||||
bool invalidate_state_by_config_options(std::vector<std::string> opt_keys);
|
bool invalidate_state_by_config_options(std::vector<std::string> opt_keys);
|
||||||
void invalidate_step(PrintStep step);
|
bool invalidate_step(PrintStep step);
|
||||||
bool invalidate_all_steps()
|
bool invalidate_all_steps();
|
||||||
%code%{ RETVAL = THIS->state.invalidate_all(); %};
|
|
||||||
bool step_done(PrintStep step)
|
bool step_done(PrintStep step)
|
||||||
%code%{ RETVAL = THIS->state.done(step); %};
|
%code%{ RETVAL = THIS->state.is_done(step); %};
|
||||||
void set_step_done(PrintStep step)
|
void set_step_done(PrintStep step)
|
||||||
%code%{ THIS->state.set_done(step); %};
|
%code%{ THIS->state.set_done(step); %};
|
||||||
void set_step_started(PrintStep step)
|
void set_step_started(PrintStep step)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue