PrusaSlicer-NonPlainar/src/libslic3r/PrintBase.hpp
Lukas Matena dbc51a140d Fixed erroneous use of logical OR where bitwise OR was meant
Also, PrintBase::SlicingStatus enum had two entries assigned to 0 - removed the one that was never used
2019-09-25 12:18:30 +02:00

434 lines
20 KiB
C++

#ifndef slic3r_PrintBase_hpp_
#define slic3r_PrintBase_hpp_
#include "libslic3r.h"
#include <set>
#include <vector>
#include <string>
#include <functional>
// tbb/mutex.h includes Windows, which in turn defines min/max macros. Convince Windows.h to not define these min/max macros.
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "tbb/mutex.h"
#include "Model.hpp"
#include "PlaceholderParser.hpp"
#include "PrintConfig.hpp"
namespace Slic3r {
class CanceledException : public std::exception {
public:
const char* what() const throw() { return "Background processing has been canceled"; }
};
class PrintStateBase {
public:
enum State {
INVALID,
STARTED,
DONE,
};
typedef size_t TimeStamp;
// A new unique timestamp is being assigned to the step every time the step changes its state.
struct StateWithTimeStamp
{
StateWithTimeStamp() : state(INVALID), timestamp(0) {}
State state;
TimeStamp timestamp;
};
protected:
//FIXME last timestamp is shared between Print & SLAPrint,
// and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp
// is not synchronized!
static size_t g_last_timestamp;
};
// To be instantiated over PrintStep or PrintObjectStep enums.
template <class StepType, size_t COUNT>
class PrintState : public PrintStateBase
{
public:
PrintState() {}
StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const {
tbb::mutex::scoped_lock lock(mtx);
StateWithTimeStamp state = m_state[step];
return state;
}
bool is_started(StepType step, tbb::mutex &mtx) const {
return this->state_with_timestamp(step, mtx).state == STARTED;
}
bool is_done(StepType step, tbb::mutex &mtx) const {
return this->state_with_timestamp(step, mtx).state == DONE;
}
StateWithTimeStamp state_with_timestamp_unguarded(StepType step) const {
return m_state[step];
}
bool is_started_unguarded(StepType step) const {
return this->state_with_timestamp_unguarded(step).state == STARTED;
}
bool is_done_unguarded(StepType step) const {
return this->state_with_timestamp_unguarded(step).state == DONE;
}
// Set the step as started. Block on mutex while the Print / PrintObject / PrintRegion objects are being
// modified by the UI thread.
// This is necessary to block until the Print::apply() updates its state, which may
// influence the processing step being entered.
template<typename ThrowIfCanceled>
bool set_started(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) {
tbb::mutex::scoped_lock lock(mtx);
// If canceled, throw before changing the step state.
throw_if_canceled();
if (m_state[step].state == DONE)
return false;
m_state[step].state = STARTED;
m_state[step].timestamp = ++ g_last_timestamp;
return true;
}
// Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being
// modified by the UI thread.
template<typename ThrowIfCanceled>
TimeStamp set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) {
tbb::mutex::scoped_lock lock(mtx);
// If canceled, throw before changing the step state.
throw_if_canceled();
assert(m_state[step].state != DONE);
m_state[step].state = DONE;
m_state[step].timestamp = ++ g_last_timestamp;
return m_state[step].timestamp;
}
// Make the step invalid.
// PrintBase::m_state_mutex should be locked at this point, guarding access to m_state.
// In case the step has already been entered or finished, cancel the background
// processing by calling the cancel callback.
template<typename CancelationCallback>
bool invalidate(StepType step, CancelationCallback cancel) {
bool invalidated = m_state[step].state != INVALID;
if (invalidated) {
#if 0
if (mtx.state != mtx.HELD) {
printf("Not held!\n");
}
#endif
m_state[step].state = INVALID;
m_state[step].timestamp = ++ g_last_timestamp;
// Raise the mutex, so that the following cancel() callback could cancel
// the background processing.
// Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let
// the working thread to proceed.
cancel();
}
return invalidated;
}
template<typename CancelationCallback, typename StepTypeIterator>
bool invalidate_multiple(StepTypeIterator step_begin, StepTypeIterator step_end, CancelationCallback cancel) {
bool invalidated = false;
for (StepTypeIterator it = step_begin; it != step_end; ++ it) {
StateWithTimeStamp &state = m_state[*it];
if (state.state != INVALID) {
invalidated = true;
state.state = INVALID;
state.timestamp = ++ g_last_timestamp;
}
}
if (invalidated) {
#if 0
if (mtx.state != mtx.HELD) {
printf("Not held!\n");
}
#endif
// Raise the mutex, so that the following cancel() callback could cancel
// the background processing.
// Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let
// the working thread to proceed.
cancel();
}
return invalidated;
}
// Make all steps invalid.
// PrintBase::m_state_mutex should be locked at this point, guarding access to m_state.
// In case any step has already been entered or finished, cancel the background
// processing by calling the cancel callback.
template<typename CancelationCallback>
bool invalidate_all(CancelationCallback cancel) {
bool invalidated = false;
for (size_t i = 0; i < COUNT; ++ i) {
StateWithTimeStamp &state = m_state[i];
if (state.state != INVALID) {
invalidated = true;
state.state = INVALID;
state.timestamp = ++ g_last_timestamp;
}
}
if (invalidated)
cancel();
return invalidated;
}
private:
StateWithTimeStamp m_state[COUNT];
};
class PrintBase;
class PrintObjectBase
{
public:
const ModelObject* model_object() const { return m_model_object; }
ModelObject* model_object() { return m_model_object; }
protected:
PrintObjectBase(ModelObject *model_object) : m_model_object(model_object) {}
virtual ~PrintObjectBase() {}
// Declared here to allow access from PrintBase through friendship.
static tbb::mutex& state_mutex(PrintBase *print);
static std::function<void()> cancel_callback(PrintBase *print);
ModelObject *m_model_object;
};
/**
* @brief Printing involves slicing and export of device dependent instructions.
*
* Every technology has a potentially different set of requirements for
* slicing, support structures and output print instructions. The pipeline
* however remains roughly the same:
* slice -> convert to instructions -> send to printer
*
* The PrintBase class will abstract this flow for different technologies.
*
*/
class PrintBase
{
public:
PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); }
inline virtual ~PrintBase() {}
virtual PrinterTechnology technology() const noexcept = 0;
// Reset the print status including the copy of the Model / ModelObject hierarchy.
virtual void clear() = 0;
// The Print is empty either after clear() or after apply() over an empty model,
// or after apply() over a model, where no object is printable (all outside the print volume).
virtual bool empty() const = 0;
// Validate the print, return empty string if valid, return error if process() cannot (or should not) be started.
virtual std::string validate() const { return std::string(); }
enum ApplyStatus {
// No change after the Print::apply() call.
APPLY_STATUS_UNCHANGED,
// Some of the Print / PrintObject / PrintObjectInstance data was changed,
// but no result was invalidated (only data influencing not yet calculated results were changed).
APPLY_STATUS_CHANGED,
// Some data was changed, which in turn invalidated already calculated steps.
APPLY_STATUS_INVALIDATED,
};
virtual ApplyStatus apply(const Model &model, DynamicPrintConfig config) = 0;
const Model& model() const { return m_model; }
struct TaskParams {
TaskParams() : single_model_object(0), single_model_instance_only(false), to_object_step(-1), to_print_step(-1) {}
// If non-empty, limit the processing to this ModelObject.
ObjectID single_model_object;
// If set, only process single_model_object. Otherwise process everything, but single_model_object first.
bool single_model_instance_only;
// If non-negative, stop processing at the successive object step.
int to_object_step;
// If non-negative, stop processing at the successive print step.
int to_print_step;
};
// After calling the apply() function, call set_task() to limit the task to be processed by process().
virtual void set_task(const TaskParams &params) {}
// Perform the calculation. This is the only method that is to be called at a worker thread.
virtual void process() = 0;
// Clean up after process() finished, either with success, error or if canceled.
// The adjustments on the Print / PrintObject data due to set_task() are to be reverted here.
virtual void finalize() {}
struct SlicingStatus {
SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {}
int percent;
std::string text;
// Bitmap of flags.
enum FlagBits {
DEFAULT = 0,
RELOAD_SCENE = 1 << 1,
RELOAD_SLA_SUPPORT_POINTS = 1 << 2,
RELOAD_SLA_PREVIEW = 1 << 3,
};
// Bitmap of FlagBits
unsigned int flags;
};
typedef std::function<void(const SlicingStatus&)> status_callback_type;
// Default status console print out in the form of percent => message.
void set_status_default() { m_status_callback = nullptr; }
// No status output or callback whatsoever, useful mostly for automatic tests.
void set_status_silent() { m_status_callback = [](const SlicingStatus&){}; }
// Register a custom status callback.
void set_status_callback(status_callback_type cb) { m_status_callback = cb; }
// Calls a registered callback to update the status, or print out the default message.
void set_status(int percent, const std::string &message, unsigned int flags = SlicingStatus::DEFAULT) {
if (m_status_callback) m_status_callback(SlicingStatus(percent, message, flags));
else printf("%d => %s\n", percent, message.c_str());
}
typedef std::function<void()> cancel_callback_type;
// Various methods will call this callback to stop the background processing (the Print::process() call)
// in case a successive change of the Print / PrintObject / PrintRegion instances changed
// the state of the finished or running calculations.
void set_cancel_callback(cancel_callback_type cancel_callback) { m_cancel_callback = cancel_callback; }
// Has the calculation been canceled?
enum CancelStatus {
// No cancelation, background processing should run.
NOT_CANCELED = 0,
// Canceled by user from the user interface (user pressed the "Cancel" button or user closed the application).
CANCELED_BY_USER = 1,
// Canceled internally from Print::apply() through the Print/PrintObject::invalidate_step() or ::invalidate_all_steps().
CANCELED_INTERNAL = 2
};
CancelStatus cancel_status() const { return m_cancel_status; }
// Has the calculation been canceled?
bool canceled() const { return m_cancel_status != NOT_CANCELED; }
// Cancel the running computation. Stop execution of all the background threads.
void cancel() { m_cancel_status = CANCELED_BY_USER; }
void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; }
// Cancel the running computation. Stop execution of all the background threads.
void restart() { m_cancel_status = NOT_CANCELED; }
// Returns true if the last step was finished with success.
virtual bool finished() const = 0;
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; }
const DynamicPrintConfig& full_print_config() const { return m_full_print_config; }
virtual std::string output_filename(const std::string &filename_base = std::string()) const = 0;
// If the filename_base is set, it is used as the input for the template processing. In that case the path is expected to be the directory (may be empty).
// If filename_set is empty, than the path may be a file or directory. If it is a file, then the macro will not be processed.
std::string output_filepath(const std::string &path, const std::string &filename_base = std::string()) const;
protected:
friend class PrintObjectBase;
friend class BackgroundSlicingProcess;
tbb::mutex& state_mutex() const { return m_state_mutex; }
std::function<void()> cancel_callback() { return m_cancel_callback; }
void call_cancel_callback() { m_cancel_callback(); }
// If the background processing stop was requested, throw CanceledException.
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); }
// To be called by this->output_filename() with the format string pulled from the configuration layer.
std::string output_filename(const std::string &format, const std::string &default_ext, const std::string &filename_base, const DynamicConfig *config_override = nullptr) const;
// Update "scale", "input_filename", "input_filename_base" placeholders from the current printable ModelObjects.
void update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const;
Model m_model;
DynamicPrintConfig m_full_print_config;
PlaceholderParser m_placeholder_parser;
private:
tbb::atomic<CancelStatus> m_cancel_status;
// Callback to be evoked regularly to update state of the UI thread.
status_callback_type m_status_callback;
// Callback to be evoked to stop the background processing before a state is updated.
cancel_callback_type m_cancel_callback = [](){};
// Mutex used for synchronization of the worker thread with the UI thread:
// The mutex will be used to guard the worker thread against entering a stage
// while the data influencing the stage is modified.
mutable tbb::mutex m_state_mutex;
};
template<typename PrintStepEnum, const size_t COUNT>
class PrintBaseWithState : public PrintBase
{
public:
bool is_step_done(PrintStepEnum step) const { return m_state.is_done(step, this->state_mutex()); }
PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintStepEnum step) const { return m_state.state_with_timestamp(step, this->state_mutex()); }
protected:
bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); }
PrintStateBase::TimeStamp set_done(PrintStepEnum step) { return m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); }
bool invalidate_step(PrintStepEnum step)
{ return m_state.invalidate(step, this->cancel_callback()); }
template<typename StepTypeIterator>
bool invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end)
{ return m_state.invalidate_multiple(step_begin, step_end, this->cancel_callback()); }
bool invalidate_steps(std::initializer_list<PrintStepEnum> il)
{ return m_state.invalidate_multiple(il.begin(), il.end(), this->cancel_callback()); }
bool invalidate_all_steps()
{ return m_state.invalidate_all(this->cancel_callback()); }
bool is_step_started_unguarded(PrintStepEnum step) const { return m_state.is_started_unguarded(step); }
bool is_step_done_unguarded(PrintStepEnum step) const { return m_state.is_done_unguarded(step); }
private:
PrintState<PrintStepEnum, COUNT> m_state;
};
template<typename PrintType, typename PrintObjectStepEnum, const size_t COUNT>
class PrintObjectBaseWithState : public PrintObjectBase
{
public:
PrintType* print() { return m_print; }
const PrintType* print() const { return m_print; }
typedef PrintState<PrintObjectStepEnum, COUNT> PrintObjectState;
bool is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step, PrintObjectBase::state_mutex(m_print)); }
PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); }
protected:
PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {}
bool set_started(PrintObjectStepEnum step)
{ return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); }
PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step)
{ return m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); }
bool invalidate_step(PrintObjectStepEnum step)
{ return m_state.invalidate(step, PrintObjectBase::cancel_callback(m_print)); }
template<typename StepTypeIterator>
bool invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end)
{ return m_state.invalidate_multiple(step_begin, step_end, PrintObjectBase::cancel_callback(m_print)); }
bool invalidate_steps(std::initializer_list<PrintObjectStepEnum> il)
{ return m_state.invalidate_multiple(il.begin(), il.end(), PrintObjectBase::cancel_callback(m_print)); }
bool invalidate_all_steps()
{ return m_state.invalidate_all(PrintObjectBase::cancel_callback(m_print)); }
bool is_step_started_unguarded(PrintObjectStepEnum step) const { return m_state.is_started_unguarded(step); }
bool is_step_done_unguarded(PrintObjectStepEnum step) const { return m_state.is_done_unguarded(step); }
protected:
// If the background processing stop was requested, throw CanceledException.
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
void throw_if_canceled() { if (m_print->canceled()) throw CanceledException(); }
friend PrintType;
PrintType *m_print;
private:
PrintState<PrintObjectStepEnum, COUNT> m_state;
};
} // namespace Slic3r
#endif /* slic3r_PrintBase_hpp_ */