AppController integration

This commit is contained in:
tamasmeszaros 2018-06-28 18:47:18 +02:00
parent 5446b9f1e5
commit ad4d95f60c
17 changed files with 1435 additions and 12 deletions

View file

@ -0,0 +1,310 @@
#include "AppController.hpp"
#include <future>
#include <sstream>
#include <cstdarg>
#include <thread>
#include <unordered_map>
#include <slic3r/GUI/GUI.hpp>
#include <slic3r/GUI/PresetBundle.hpp>
#include <PrintConfig.hpp>
#include <Print.hpp>
#include <Model.hpp>
#include <Utils.hpp>
namespace Slic3r {
class AppControllerBoilerplate::PriMap {
public:
using M = std::unordered_map<std::thread::id, ProgresIndicatorPtr>;
std::mutex m;
M store;
std::thread::id ui_thread;
inline explicit PriMap(std::thread::id uit): ui_thread(uit) {}
};
AppControllerBoilerplate::AppControllerBoilerplate()
:progressind_(new PriMap(std::this_thread::get_id())) {}
AppControllerBoilerplate::~AppControllerBoilerplate() {
progressind_.reset();
}
bool AppControllerBoilerplate::is_main_thread() const
{
return progressind_->ui_thread == std::this_thread::get_id();
}
namespace GUI {
PresetBundle* get_preset_bundle();
}
static const PrintObjectStep STEP_SLICE = posSlice;
static const PrintObjectStep STEP_PERIMETERS = posPerimeters;
static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill;
static const PrintObjectStep STEP_INFILL = posInfill;
static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial;
static const PrintStep STEP_SKIRT = psSkirt;
static const PrintStep STEP_BRIM = psBrim;
static const PrintStep STEP_WIPE_TOWER = psWipeTower;
void AppControllerBoilerplate::progress_indicator(
AppControllerBoilerplate::ProgresIndicatorPtr progrind) {
progressind_->m.lock();
progressind_->store[std::this_thread::get_id()] = progrind;
progressind_->m.unlock();
}
void AppControllerBoilerplate::progress_indicator(unsigned statenum,
const std::string &title,
const std::string &firstmsg)
{
progressind_->m.lock();
progressind_->store[std::this_thread::get_id()] =
create_progress_indicator(statenum, title, firstmsg);;
progressind_->m.unlock();
}
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::progress_indicator() {
PriMap::M::iterator pret;
ProgresIndicatorPtr ret;
progressind_->m.lock();
if( (pret = progressind_->store.find(std::this_thread::get_id()))
== progressind_->store.end())
{
progressind_->store[std::this_thread::get_id()] = ret =
global_progressind_;
} else ret = pret->second;
progressind_->m.unlock();
return ret;
}
void PrintController::make_skirt()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects) make_perimeters(obj);
for(auto obj : print_->objects) infill(obj);
for(auto obj : print_->objects) gen_support_material(obj);
if(!print_->state.is_done(STEP_SKIRT)) {
print_->state.set_started(STEP_SKIRT);
print_->skirt.clear();
if(print_->has_skirt()) print_->_make_skirt();
print_->state.set_done(STEP_SKIRT);
}
}
void PrintController::make_brim()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects) make_perimeters(obj);
for(auto obj : print_->objects) infill(obj);
for(auto obj : print_->objects) gen_support_material(obj);
make_skirt();
if(!print_->state.is_done(STEP_BRIM)) {
print_->state.set_started(STEP_BRIM);
// since this method must be idempotent, we clear brim paths *before*
// checking whether we need to generate them
print_->brim.clear();
if(print_->config.brim_width > 0) print_->_make_brim();
print_->state.set_done(STEP_BRIM);
}
}
void PrintController::make_wipe_tower()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects) make_perimeters(obj);
for(auto obj : print_->objects) infill(obj);
for(auto obj : print_->objects) gen_support_material(obj);
make_skirt();
make_brim();
if(!print_->state.is_done(STEP_WIPE_TOWER)) {
print_->state.set_started(STEP_WIPE_TOWER);
// since this method must be idempotent, we clear brim paths *before*
// checking whether we need to generate them
print_->brim.clear();
if(print_->has_wipe_tower()) print_->_make_wipe_tower();
print_->state.set_done(STEP_WIPE_TOWER);
}
}
void PrintController::slice(PrintObject *pobj)
{
assert(pobj != nullptr && print_ != nullptr);
if(pobj->state.is_done(STEP_SLICE)) return;
pobj->state.set_started(STEP_SLICE);
pobj->_slice();
auto msg = pobj->_fix_slicing_errors();
if(!msg.empty()) report_issue(IssueType::WARN, msg);
// simplify slices if required
if (print_->config.resolution)
pobj->_simplify_slices(scale_(print_->config.resolution));
if(pobj->layers.empty())
report_issue(IssueType::ERR,
"No layers were detected. You might want to repair your "
"STL file(s) or check their size or thickness and retry"
);
pobj->state.set_done(STEP_SLICE);
}
void PrintController::make_perimeters(PrintObject *pobj)
{
assert(pobj != nullptr);
slice(pobj);
auto&& prgind = progress_indicator();
if (!pobj->state.is_done(STEP_PERIMETERS)) {
pobj->_make_perimeters();
}
}
void PrintController::infill(PrintObject *pobj)
{
assert(pobj != nullptr);
make_perimeters(pobj);
if (!pobj->state.is_done(STEP_PREPARE_INFILL)) {
pobj->state.set_started(STEP_PREPARE_INFILL);
pobj->_prepare_infill();
pobj->state.set_done(STEP_PREPARE_INFILL);
}
pobj->_infill();
}
void PrintController::gen_support_material(PrintObject *pobj)
{
assert(pobj != nullptr);
// prerequisites
slice(pobj);
if(!pobj->state.is_done(STEP_SUPPORTMATERIAL)) {
pobj->state.set_started(STEP_SUPPORTMATERIAL);
pobj->clear_support_layers();
if((pobj->config.support_material || pobj->config.raft_layers > 0)
&& pobj->layers.size() > 1) {
pobj->_generate_support_material();
}
pobj->state.set_done(STEP_SUPPORTMATERIAL);
}
}
void PrintController::slice()
{
Slic3r::trace(3, "Starting the slicing process.");
progress_indicator()->update(20u, "Generating perimeters");
for(auto obj : print_->objects) make_perimeters(obj);
progress_indicator()->update(60u, "Infilling layers");
for(auto obj : print_->objects) infill(obj);
progress_indicator()->update(70u, "Generating support material");
for(auto obj : print_->objects) gen_support_material(obj);
progress_indicator()->message_fmt("Weight: %.1fg, Cost: %.1f",
print_->total_weight,
print_->total_cost);
progress_indicator()->state(85u);
progress_indicator()->update(88u, "Generating skirt");
make_skirt();
progress_indicator()->update(90u, "Generating brim");
make_brim();
progress_indicator()->update(95u, "Generating wipe tower");
make_wipe_tower();
progress_indicator()->update(100u, "Done");
// time to make some statistics..
Slic3r::trace(3, "Slicing process finished.");
}
void IProgressIndicator::message_fmt(
const std::string &fmtstr, ...) {
std::stringstream ss;
va_list args;
va_start(args, fmtstr);
auto fmt = fmtstr.begin();
while (*fmt != '\0') {
if (*fmt == 'd') {
int i = va_arg(args, int);
ss << i << '\n';
} else if (*fmt == 'c') {
// note automatic conversion to integral type
int c = va_arg(args, int);
ss << static_cast<char>(c) << '\n';
} else if (*fmt == 'f') {
double d = va_arg(args, double);
ss << d << '\n';
}
++fmt;
}
va_end(args);
message(ss.str());
}
void AppController::arrange_model()
{
std::async(supports_asynch()? std::launch::async : std::launch::deferred,
[this](){
auto pind = progress_indicator();
// 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);
double dist = GUI::get_preset_bundle()->full_config().option("min_object_distance")->getFloat();
std::cout << dist << std::endl;
});
}
}

View file

@ -0,0 +1,267 @@
#ifndef APPCONTROLLER_HPP
#define APPCONTROLLER_HPP
#include <string>
#include <vector>
#include <memory>
#include <atomic>
#include <iostream>
#include "IProgressIndicator.hpp"
namespace Slic3r {
class Model;
class Print;
class PrintObject;
/**
* @brief A boilerplate class for creating application logic. It should provide
* features as issue reporting and progress indication, etc...
*
* The lower lever UI independent classes can be manipulated with a subclass
* of this controller class. We can also catch any exceptions that lower level
* methods could throw and display appropriate errors and warnings.
*
* Note that the outer and the inner interface of this class is free from any
* UI toolkit dependencies. We can implement it with any UI framework or make it
* a cli client.
*/
class AppControllerBoilerplate {
class PriMap; // Some structure to store progress indication data
public:
/// A Progress indicator object smart pointer
using ProgresIndicatorPtr = std::shared_ptr<IProgressIndicator>;
private:
// Pimpl data for thread safe progress indication features
std::unique_ptr<PriMap> progressind_;
public:
AppControllerBoilerplate();
~AppControllerBoilerplate();
using Path = std::string;
using PathList = std::vector<Path>;
/// Common runtime issue types
enum class IssueType {
INFO,
WARN,
WARN_Q, // Warning with a question to continue
ERR,
FATAL
};
/**
* @brief Query some paths from the user.
*
* It should display a file chooser dialog in case of a UI application.
* @param title Title of a possible query dialog.
* @param extensions Recognized file extensions.
* @return Returns a list of paths choosed by the user.
*/
PathList query_destination_paths(
const std::string& title,
const std::string& extensions) const;
/**
* @brief Same as query_destination_paths but works for directories only.
*/
PathList query_destination_dirs(
const std::string& title) const;
/**
* @brief Same as query_destination_paths but returns only one path.
*/
Path query_destination_path(
const std::string& title,
const std::string& extensions,
const std::string& hint = "") const;
/**
* @brief Report an issue to the user be it fatal or recoverable.
*
* In a UI app this should display some message dialog.
*
* @param issuetype The type of the runtime issue.
* @param description A somewhat longer description of the issue.
* @param brief A very brief description. Can be used for message dialog
* title.
*/
bool report_issue(IssueType issuetype,
const std::string& description,
const std::string& brief = "");
/**
* @brief Set up a progress indicator for the current thread.
* @param progrind An already created progress indicator object.
*/
void progress_indicator(ProgresIndicatorPtr progrind);
/**
* @brief Create and set up a new progress indicator for the current thread.
* @param statenum The number of states for the given procedure.
* @param title The title of the procedure.
* @param firstmsg The message for the first subtask to be displayed.
*/
void progress_indicator(unsigned statenum,
const std::string& title,
const std::string& firstmsg = "");
/**
* @brief Return the progress indicator set up for the current thread. This
* can be empty as well.
* @return A progress indicator object implementing IProgressIndicator. If
* a global progress indicator is available for the current implementation
* than this will be set up for the current thread and returned.
*/
ProgresIndicatorPtr progress_indicator();
/**
* @brief A predicate telling the caller whether it is the thread that
* created the AppConroller object itself. This probably means that the
* execution is in the UI thread. Otherwise it returns false meaning that
* some worker thread called this function.
* @return Return true for the same caller thread that created this
* object and false for every other.
*/
bool is_main_thread() const;
/**
* @brief The frontend supports asynch execution.
*
* A Graphic UI will support this, a CLI may not. This can be used in
* subclass methods to decide whether to start threads for block free UI.
*
* Note that even a progress indicator's update called regularly can solve
* the blocking UI problem in some cases even when an event loop is present.
* This is how wxWidgets gauge work but creating a separate thread will make
* the UI even more fluent.
*
* @return true if a job or method can be executed asynchronously, false
* otherwise.
*/
bool supports_asynch() const;
protected:
/**
* @brief Create a new progress indicator and return a smart pointer to it.
* @param statenum The number of states for the given procedure.
* @param title The title of the procedure.
* @param firstmsg The message for the first subtask to be displayed.
* @return Smart pointer to the created object.
*/
ProgresIndicatorPtr create_progress_indicator(
unsigned statenum,
const std::string& title,
const std::string& firstmsg = "") const;
// This is a global progress indicator placeholder. In the Slic3r UI it can
// contain the progress indicator on the statusbar.
ProgresIndicatorPtr global_progressind_;
};
/**
* @brief Implementation of the printing logic.
*/
class PrintController: public AppControllerBoilerplate {
Print *print_ = nullptr;
protected:
void make_skirt();
void make_brim();
void make_wipe_tower();
void make_perimeters(PrintObject *pobj);
void infill(PrintObject *pobj);
void gen_support_material(PrintObject *pobj);
public:
// Must be public for perl to use it
explicit inline PrintController(Print *print): print_(print) {}
PrintController(const PrintController&) = delete;
PrintController(PrintController&&) = delete;
using Ptr = std::unique_ptr<PrintController>;
inline static Ptr create(Print *print) {
return PrintController::Ptr( new PrintController(print) );
}
/**
* @brief Slice one pront object.
* @param pobj The print object.
*/
void slice(PrintObject *pobj);
/**
* @brief Slice the loaded print scene.
*/
void slice();
};
/**
* @brief Top level controller.
*/
class AppController: public AppControllerBoilerplate {
Model *model_ = nullptr;
PrintController::Ptr printctl;
public:
/**
* @brief Get the print controller object.
*
* @return Return a raw pointer instead of a smart one for perl to be able
* to use this function and access the print controller.
*/
PrintController * print_ctl() { return printctl.get(); }
/**
* @brief Set a model object.
*
* @param model A raw pointer to the model object. This can be used from
* perl.
*/
void set_model(Model *model) { model_ = model; }
/**
* @brief Set the print object from perl.
*
* This will create a print controller that will then be accessible from
* perl.
* @param print A print object which can be a perl-ish extension as well.
*/
void set_print(Print *print) {
printctl = PrintController::create(print);
printctl->progress_indicator(progress_indicator());
}
/**
* @brief Set up a global progress indicator.
*
* In perl we have a progress indicating status bar on the bottom of the
* window which is defined and created in perl. We can pass the ID-s of the
* gauge and the statusbar id and make a wrapper implementation of the
* IProgressIndicator interface so we can use this GUI widget from C++.
*
* This function should be called from perl.
*
* @param gauge_id The ID of the gague widget of the status bar.
* @param statusbar_id The ID of the status bar.
*/
void set_global_progress_indicator(unsigned gauge_id,
unsigned statusbar_id);
void arrange_model();
};
}
#endif // APPCONTROLLER_HPP

View file

@ -0,0 +1,295 @@
#include "AppController.hpp"
#include <thread>
#include <future>
#include <slic3r/GUI/GUI.hpp>
#include <wx/app.h>
#include <wx/filedlg.h>
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include <wx/gauge.h>
#include <wx/statusbr.h>
#include <wx/event.h>
// This source file implements the UI dependent methods of the AppControllers.
// It will be clear what is needed to be reimplemented in case of a UI framework
// change or a CLI client creation. In this particular case we use wxWidgets to
// implement everything.
namespace Slic3r {
bool AppControllerBoilerplate::supports_asynch() const
{
return true;
}
AppControllerBoilerplate::PathList
AppControllerBoilerplate::query_destination_paths(
const std::string &title,
const std::string &extensions) const
{
wxFileDialog dlg(wxTheApp->GetTopWindow(), wxString(title) );
dlg.SetWildcard(extensions);
dlg.ShowModal();
wxArrayString paths;
dlg.GetPaths(paths);
PathList ret(paths.size(), "");
for(auto& p : paths) ret.push_back(p.ToStdString());
return ret;
}
AppControllerBoilerplate::Path
AppControllerBoilerplate::query_destination_path(
const std::string &title,
const std::string &extensions,
const std::string& hint) const
{
wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
dlg.SetWildcard(extensions);
dlg.SetFilename(hint);
Path ret;
if(dlg.ShowModal() == wxID_OK) {
ret = Path(dlg.GetPath());
}
return ret;
}
bool AppControllerBoilerplate::report_issue(IssueType issuetype,
const std::string &description,
const std::string &brief)
{
auto icon = wxICON_INFORMATION;
auto style = wxOK|wxCENTRE;
switch(issuetype) {
case IssueType::INFO: break;
case IssueType::WARN: icon = wxICON_WARNING; break;
case IssueType::WARN_Q: icon = wxICON_WARNING; style |= wxCANCEL; break;
case IssueType::ERR:
case IssueType::FATAL: icon = wxICON_ERROR;
}
auto ret = wxMessageBox(description, brief, icon | style);
return ret != wxCANCEL;
}
wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
namespace {
/*
* A simple thread safe progress dialog implementation that can be used from
* the main thread as well.
*/
class GuiProgressIndicator:
public IProgressIndicator, public wxEvtHandler {
std::shared_ptr<wxProgressDialog> gauge_;
using Base = IProgressIndicator;
wxString message_;
int range_; wxString title_;
unsigned prc_ = 0;
bool is_asynch_ = false;
const int id_ = wxWindow::NewControlId();
// status update handler
void _state( wxCommandEvent& evt) {
unsigned st = evt.GetInt();
_state(st);
}
// Status update implementation
void _state( unsigned st) {
if(st < max()) {
if(!gauge_) gauge_ = std::make_shared<wxProgressDialog>(
title_, message_, range_, wxTheApp->GetTopWindow(),
wxPD_APP_MODAL | wxPD_AUTO_HIDE
);
if(!gauge_->IsShown()) gauge_->ShowModal();
Base::state(st);
gauge_->Update(static_cast<int>(st), message_);
}
if(st == max()) {
prc_++;
if(prc_ == Base::procedure_count()) {
gauge_.reset();
prc_ = 0;
}
}
}
public:
/// Setting whether it will be used from the UI thread or some worker thread
inline void asynch(bool is) { is_asynch_ = is; }
/// Get the mode of parallel operation.
inline bool asynch() const { return is_asynch_; }
inline GuiProgressIndicator(int range, const std::string& title,
const std::string& firstmsg) :
range_(range), message_(_(firstmsg)), title_(_(title))
{
Base::max(static_cast<float>(range));
Base::states(static_cast<unsigned>(range));
Bind(PROGRESS_STATUS_UPDATE_EVENT,
&GuiProgressIndicator::_state,
this, id_);
}
virtual void cancel() override {
update(max(), "Abort");
IProgressIndicator::cancel();
}
virtual void state(float val) override {
if( val >= 1.0) state(static_cast<unsigned>(val));
}
void state(unsigned st) {
// send status update event
if(is_asynch_) {
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_);
evt->SetInt(st);
wxQueueEvent(this, evt);
} else _state(st);
}
virtual void message(const std::string & msg) override {
message_ = _(msg);
}
virtual void messageFmt(const std::string& fmt, ...) {
va_list arglist;
va_start(arglist, fmt);
message_ = wxString::Format(_(fmt), arglist);
va_end(arglist);
}
virtual void title(const std::string & title) override {
title_ = _(title);
}
};
}
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::create_progress_indicator(
unsigned statenum, const std::string& title,
const std::string& firstmsg) const
{
auto pri =
std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg);
// We set up the mode of operation depending of the creator thread's
// identity
pri->asynch(!is_main_thread());
return pri;
}
namespace {
// A wrapper progress indicator class around the statusbar created in perl.
class Wrapper: public IProgressIndicator, public wxEvtHandler {
wxGauge *gauge_;
wxStatusBar *stbar_;
using Base = IProgressIndicator;
std::string message_;
AppControllerBoilerplate& ctl_;
void showProgress(bool show = true) {
gauge_->Show(show);
}
void _state(unsigned st) {
if( st <= max() ) {
Base::state(st);
if(!gauge_->IsShown()) showProgress(true);
stbar_->SetStatusText(message_);
if(st == gauge_->GetRange()) {
gauge_->SetValue(0);
showProgress(false);
} else {
gauge_->SetValue(static_cast<int>(st));
}
}
}
// status update handler
void _state( wxCommandEvent& evt) {
unsigned st = evt.GetInt(); _state(st);
}
const int id_ = wxWindow::NewControlId();
public:
inline Wrapper(wxGauge *gauge, wxStatusBar *stbar,
AppControllerBoilerplate& ctl):
gauge_(gauge), stbar_(stbar), ctl_(ctl)
{
Base::max(static_cast<float>(gauge->GetRange()));
Base::states(static_cast<unsigned>(gauge->GetRange()));
Bind(PROGRESS_STATUS_UPDATE_EVENT,
&Wrapper::_state,
this, id_);
}
virtual void state(float val) override {
if(val >= 1.0) state(unsigned(val));
}
void state(unsigned st) {
if(!ctl_.is_main_thread()) {
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_);
evt->SetInt(st);
wxQueueEvent(this, evt);
} else _state(st);
}
virtual void message(const std::string & msg) override {
message_ = msg;
}
virtual void message_fmt(const std::string& fmt, ...) override {
va_list arglist;
va_start(arglist, fmt);
message_ = wxString::Format(_(fmt), arglist);
va_end(arglist);
}
virtual void title(const std::string & /*title*/) override {}
};
}
void AppController::set_global_progress_indicator(
unsigned gid,
unsigned sid)
{
wxGauge* gauge = dynamic_cast<wxGauge*>(wxWindow::FindWindowById(gid));
wxStatusBar* sb = dynamic_cast<wxStatusBar*>(wxWindow::FindWindowById(sid));
if(gauge && sb) {
global_progressind_ = std::make_shared<Wrapper>(gauge, sb, *this);
}
}
}

View file

@ -0,0 +1,84 @@
#ifndef IPROGRESSINDICATOR_HPP
#define IPROGRESSINDICATOR_HPP
#include <string>
#include <functional>
namespace Slic3r {
/**
* @brief Generic progress indication interface.
*/
class IProgressIndicator {
public:
using CancelFn = std::function<void(void)>; // Cancel functio signature.
private:
float state_ = .0f, max_ = 1.f, step_;
CancelFn cancelfunc_ = [](){};
unsigned proc_count_ = 1;
public:
inline virtual ~IProgressIndicator() {}
/// Get the maximum of the progress range.
float max() const { return max_; }
/// Get the current progress state
float state() const { return state_; }
/// Set the maximum of hte progress range
virtual void max(float maxval) { max_ = maxval; }
/// Set the current state of the progress.
virtual void state(float val) { state_ = val; }
/**
* @brief Number of states int the progress. Can be used insted of giving a
* maximum value.
*/
virtual void states(unsigned statenum) {
step_ = max_ / statenum;
}
/// Message shown on the next status update.
virtual void message(const std::string&) = 0;
/// Title of the operaton.
virtual void title(const std::string&) = 0;
/// Formatted message for the next status update. Works just like sprinf.
virtual void message_fmt(const std::string& fmt, ...);
/// Set up a cancel callback for the operation if feasible.
inline void on_cancel(CancelFn func) { cancelfunc_ = func; }
/**
* Explicitly shut down the progress indicator and call the associated
* callback.
*/
virtual void cancel() { cancelfunc_(); }
/**
* \brief Set up how many subprocedures does the whole operation contain.
*
* This was neccesary from practical reasons. If the progress indicator is
* a dialog and we want to show the progress of a few sub operations than
* the dialog wont be closed and reopened each time a new sub operation is
* started. This is not a mandatory feature and can be ignored completely.
*/
inline void procedure_count(unsigned pc) { proc_count_ = pc; }
/// Get the current procedure count
inline unsigned procedure_count() const { return proc_count_; }
/// Convinience function to call message and status update in one function.
void update(float st, const std::string& msg) {
message(msg); state(st);
}
};
}
#endif // IPROGRESSINDICATOR_HPP