WIP: Moved sources int src/, separated most of the source code from Perl.

The XS was left only for the unit / integration tests, and it links
libslic3r only. No wxWidgets are allowed to be used from Perl starting
from now.
This commit is contained in:
bubnikv 2018-09-19 11:02:24 +02:00
parent 3ddaccb641
commit 0558b53493
1706 changed files with 7413 additions and 7638 deletions

View file

@ -0,0 +1,167 @@
#include "AppController.hpp"
#include <future>
#include <chrono>
#include <sstream>
#include <cstdarg>
#include <thread>
#include <unordered_map>
#include <slic3r/GUI/GUI.hpp>
#include <ModelArrange.hpp>
#include <slic3r/GUI/PresetBundle.hpp>
#include <Geometry.hpp>
#include <PrintConfig.hpp>
#include <Print.hpp>
#include <Model.hpp>
#include <Utils.hpp>
namespace Slic3r {
class AppControllerBoilerplate::PriData {
public:
std::mutex m;
std::thread::id ui_thread;
inline explicit PriData(std::thread::id uit): ui_thread(uit) {}
};
AppControllerBoilerplate::AppControllerBoilerplate()
:pri_data_(new PriData(std::this_thread::get_id())) {}
AppControllerBoilerplate::~AppControllerBoilerplate() {
pri_data_.reset();
}
bool AppControllerBoilerplate::is_main_thread() const
{
return pri_data_->ui_thread == std::this_thread::get_id();
}
namespace GUI {
PresetBundle* get_preset_bundle();
}
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::global_progress_indicator() {
ProgresIndicatorPtr ret;
pri_data_->m.lock();
ret = global_progressind_;
pri_data_->m.unlock();
return ret;
}
void AppControllerBoilerplate::global_progress_indicator(
AppControllerBoilerplate::ProgresIndicatorPtr gpri)
{
pri_data_->m.lock();
global_progressind_ = gpri;
pri_data_->m.unlock();
}
void ProgressIndicator::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()
{
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
if(arranging_.load()) return;
// to prevent UI reentrancies
arranging_.store(true);
unsigned count = 0;
for(auto obj : model_->objects) count += obj->instances.size();
auto pind = global_progress_indicator();
float pmax = 1.0;
if(pind) {
pmax = pind->max();
// Set the range of the progress to the object count
pind->max(count);
pind->on_cancel([this](){
arranging_.store(false);
});
}
auto dist = print_ctl()->config().min_object_distance();
// Create the arranger config
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
auto& bedpoints = print_ctl()->config().bed_shape.values;
Polyline bed; bed.points.reserve(bedpoints.size());
for(auto& v : bedpoints)
bed.append(Point::new_scale(v(0), v(1)));
if(pind) pind->update(0, L("Arranging objects..."));
try {
arr::BedShapeHint hint;
// TODO: from Sasha from GUI
hint.type = arr::BedShapeType::WHO_KNOWS;
arr::arrange(*model_,
min_obj_distance,
bed,
hint,
false, // create many piles not just one pile
[this, pind, count](unsigned rem) {
if(pind)
pind->update(count - rem, L("Arranging objects..."));
process_events();
}, [this] () { return !arranging_.load(); });
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
report_issue(IssueType::ERR,
L("Could not arrange model objects! "
"Some geometries may be invalid."),
L("Exception occurred"));
}
// Restore previous max value
if(pind) {
pind->max(pmax);
pind->update(0, arranging_.load() ? L("Arranging done.") :
L("Arranging canceled."));
pind->on_cancel(/*remove cancel function*/);
}
arranging_.store(false);
}
}

View file

@ -0,0 +1,263 @@
#ifndef APPCONTROLLER_HPP
#define APPCONTROLLER_HPP
#include <string>
#include <vector>
#include <memory>
#include <atomic>
#include <iostream>
#include "GUI/ProgressIndicator.hpp"
#include <PrintConfig.hpp>
namespace Slic3r {
class Model;
class Print;
class PrintObject;
class PrintConfig;
class ProgressStatusBar;
class DynamicPrintConfig;
/**
* @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 {
public:
/// A Progress indicator object smart pointer
using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>;
private:
class PriData; // Some structure to store progress indication data
// Pimpl data for thread safe progress indication features
std::unique_ptr<PriData> pri_data_;
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);
bool report_issue(IssueType issuetype,
const std::string& description);
/**
* @brief Return the global progress indicator for the current controller.
* Can be empty as well.
*
* Only one thread should use the global indicator at a time.
*/
ProgresIndicatorPtr global_progress_indicator();
void global_progress_indicator(ProgresIndicatorPtr gpri);
/**
* @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;
void process_events();
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;
ProgresIndicatorPtr create_progress_indicator(
unsigned statenum,
const std::string& title) const;
// This is a global progress indicator placeholder. In the Slic3r UI it can
// contain the progress indicator on the statusbar.
ProgresIndicatorPtr global_progressind_;
};
#if 0
/**
* @brief Implementation of the printing logic.
*/
class PrintController: public AppControllerBoilerplate {
Print *print_ = nullptr;
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) );
}
void slice() {}
void slice_to_png() {}
const PrintConfig& config() const;
};
#else
class PrintController: public AppControllerBoilerplate {
public:
using Ptr = std::unique_ptr<PrintController>;
explicit inline PrintController(Print *print){}
inline static Ptr create(Print *print) {
return PrintController::Ptr( new PrintController(print) );
}
void slice() {}
void slice_to_png() {}
const PrintConfig& config() const { static PrintConfig cfg; return cfg; }
};
#endif
/**
* @brief Top level controller.
*/
class AppController: public AppControllerBoilerplate {
Model *model_ = nullptr;
PrintController::Ptr printctl;
std::atomic<bool> arranging_;
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);
}
/**
* @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
* ProgressIndicator 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(ProgressStatusBar *prs);
void arrange_model();
};
}
#endif // APPCONTROLLER_HPP

View file

@ -0,0 +1,302 @@
#include "AppController.hpp"
#include <thread>
#include <future>
#include <slic3r/GUI/GUI.hpp>
#include <slic3r/GUI/ProgressStatusBar.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;
}
void AppControllerBoilerplate::process_events()
{
wxYieldIfNeeded();
}
AppControllerBoilerplate::PathList
AppControllerBoilerplate::query_destination_paths(
const std::string &title,
const std::string &extensions) const
{
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(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;
}
bool AppControllerBoilerplate::report_issue(
AppControllerBoilerplate::IssueType issuetype,
const std::string &description)
{
return report_issue(issuetype, description, std::string());
}
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 ProgressIndicator, public wxEvtHandler {
wxProgressDialog gauge_;
using Base = ProgressIndicator;
wxString message_;
int range_; wxString title_;
bool is_asynch_ = false;
const int id_ = wxWindow::NewControlId();
// status update handler
void _state( wxCommandEvent& evt) {
unsigned st = evt.GetInt();
message_ = evt.GetString();
_state(st);
}
// Status update implementation
void _state( unsigned st) {
if(!gauge_.IsShown()) gauge_.ShowModal();
Base::state(st);
if(!gauge_.Update(static_cast<int>(st), message_)) {
cancel();
}
}
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 wxString& title,
const wxString& firstmsg) :
gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(),
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT),
message_(firstmsg),
range_(range), 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 state(float val) override {
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);
evt->SetString(message_);
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;
}
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::create_progress_indicator(
unsigned statenum, const std::string &title) const
{
return create_progress_indicator(statenum, title, std::string());
}
namespace {
class Wrapper: public ProgressIndicator, public wxEvtHandler {
ProgressStatusBar *sbar_;
using Base = ProgressIndicator;
wxString message_;
AppControllerBoilerplate& ctl_;
void showProgress(bool show = true) {
sbar_->show_progress(show);
}
void _state(unsigned st) {
if( st <= ProgressIndicator::max() ) {
Base::state(st);
sbar_->set_status_text(message_);
sbar_->set_progress(st);
}
}
// status update handler
void _state( wxCommandEvent& evt) {
unsigned st = evt.GetInt(); _state(st);
}
const int id_ = wxWindow::NewControlId();
public:
inline Wrapper(ProgressStatusBar *sbar,
AppControllerBoilerplate& ctl):
sbar_(sbar), ctl_(ctl)
{
Base::max(static_cast<float>(sbar_->get_range()));
Base::states(static_cast<unsigned>(sbar_->get_range()));
Bind(PROGRESS_STATUS_UPDATE_EVENT,
&Wrapper::_state,
this, id_);
}
virtual void state(float val) override {
state(unsigned(val));
}
virtual void max(float val) override {
if(val > 1.0) {
sbar_->set_range(static_cast<int>(val));
ProgressIndicator::max(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 {}
virtual void on_cancel(CancelFn fn) override {
sbar_->set_cancel_callback(fn);
Base::on_cancel(fn);
}
};
}
void AppController::set_global_progress_indicator(ProgressStatusBar *prsb)
{
if(prsb) {
global_progress_indicator(std::make_shared<Wrapper>(prsb, *this));
}
}
}

106
src/slic3r/CMakeLists.txt Normal file
View file

@ -0,0 +1,106 @@
add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/AboutDialog.cpp
${LIBDIR}/slic3r/GUI/AboutDialog.hpp
${LIBDIR}/slic3r/GUI/AppConfig.cpp
${LIBDIR}/slic3r/GUI/AppConfig.hpp
${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.cpp
${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.hpp
${LIBDIR}/slic3r/GUI/BitmapCache.cpp
${LIBDIR}/slic3r/GUI/BitmapCache.hpp
${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.cpp
${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.hpp
${LIBDIR}/slic3r/GUI/3DScene.cpp
${LIBDIR}/slic3r/GUI/3DScene.hpp
${LIBDIR}/slic3r/GUI/GLShader.cpp
${LIBDIR}/slic3r/GUI/GLShader.hpp
${LIBDIR}/slic3r/GUI/GLCanvas3D.hpp
${LIBDIR}/slic3r/GUI/GLCanvas3D.cpp
${LIBDIR}/slic3r/GUI/GLCanvas3DManager.hpp
${LIBDIR}/slic3r/GUI/GLCanvas3DManager.cpp
${LIBDIR}/slic3r/GUI/GLGizmo.hpp
${LIBDIR}/slic3r/GUI/GLGizmo.cpp
${LIBDIR}/slic3r/GUI/GLTexture.hpp
${LIBDIR}/slic3r/GUI/GLTexture.cpp
${LIBDIR}/slic3r/GUI/GLToolbar.hpp
${LIBDIR}/slic3r/GUI/GLToolbar.cpp
${LIBDIR}/slic3r/GUI/Preferences.cpp
${LIBDIR}/slic3r/GUI/Preferences.hpp
${LIBDIR}/slic3r/GUI/Preset.cpp
${LIBDIR}/slic3r/GUI/Preset.hpp
${LIBDIR}/slic3r/GUI/PresetBundle.cpp
${LIBDIR}/slic3r/GUI/PresetBundle.hpp
${LIBDIR}/slic3r/GUI/PresetHints.cpp
${LIBDIR}/slic3r/GUI/PresetHints.hpp
${LIBDIR}/slic3r/GUI/GUI.cpp
${LIBDIR}/slic3r/GUI/GUI.hpp
${LIBDIR}/slic3r/GUI/GUI_ObjectParts.cpp
${LIBDIR}/slic3r/GUI/GUI_ObjectParts.hpp
${LIBDIR}/slic3r/GUI/LambdaObjectDialog.cpp
${LIBDIR}/slic3r/GUI/LambdaObjectDialog.hpp
${LIBDIR}/slic3r/GUI/Tab.cpp
${LIBDIR}/slic3r/GUI/Tab.hpp
${LIBDIR}/slic3r/GUI/TabIface.cpp
${LIBDIR}/slic3r/GUI/TabIface.hpp
${LIBDIR}/slic3r/GUI/Field.cpp
${LIBDIR}/slic3r/GUI/Field.hpp
${LIBDIR}/slic3r/GUI/OptionsGroup.cpp
${LIBDIR}/slic3r/GUI/OptionsGroup.hpp
${LIBDIR}/slic3r/GUI/BedShapeDialog.cpp
${LIBDIR}/slic3r/GUI/BedShapeDialog.hpp
${LIBDIR}/slic3r/GUI/2DBed.cpp
${LIBDIR}/slic3r/GUI/2DBed.hpp
${LIBDIR}/slic3r/GUI/wxExtensions.cpp
${LIBDIR}/slic3r/GUI/wxExtensions.hpp
${LIBDIR}/slic3r/GUI/WipeTowerDialog.cpp
${LIBDIR}/slic3r/GUI/WipeTowerDialog.hpp
${LIBDIR}/slic3r/GUI/RammingChart.cpp
${LIBDIR}/slic3r/GUI/RammingChart.hpp
${LIBDIR}/slic3r/GUI/BonjourDialog.cpp
${LIBDIR}/slic3r/GUI/BonjourDialog.hpp
${LIBDIR}/slic3r/GUI/ButtonsDescription.cpp
${LIBDIR}/slic3r/GUI/ButtonsDescription.hpp
${LIBDIR}/slic3r/Config/Snapshot.cpp
${LIBDIR}/slic3r/Config/Snapshot.hpp
${LIBDIR}/slic3r/Config/Version.cpp
${LIBDIR}/slic3r/Config/Version.hpp
${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp
${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp
${LIBDIR}/slic3r/Utils/Serial.cpp
${LIBDIR}/slic3r/Utils/Serial.hpp
${LIBDIR}/slic3r/GUI/ConfigWizard.cpp
${LIBDIR}/slic3r/GUI/ConfigWizard.hpp
${LIBDIR}/slic3r/GUI/MsgDialog.cpp
${LIBDIR}/slic3r/GUI/MsgDialog.hpp
${LIBDIR}/slic3r/GUI/UpdateDialogs.cpp
${LIBDIR}/slic3r/GUI/UpdateDialogs.hpp
${LIBDIR}/slic3r/GUI/FirmwareDialog.cpp
${LIBDIR}/slic3r/GUI/FirmwareDialog.hpp
${LIBDIR}/slic3r/GUI/ProgressIndicator.hpp
${LIBDIR}/slic3r/GUI/ProgressStatusBar.hpp
${LIBDIR}/slic3r/GUI/ProgressStatusBar.cpp
${LIBDIR}/slic3r/Utils/Http.cpp
${LIBDIR}/slic3r/Utils/Http.hpp
${LIBDIR}/slic3r/Utils/FixModelByWin10.cpp
${LIBDIR}/slic3r/Utils/FixModelByWin10.hpp
${LIBDIR}/slic3r/Utils/PrintHostSendDialog.cpp
${LIBDIR}/slic3r/Utils/PrintHostSendDialog.hpp
${LIBDIR}/slic3r/Utils/OctoPrint.cpp
${LIBDIR}/slic3r/Utils/OctoPrint.hpp
${LIBDIR}/slic3r/Utils/Duet.cpp
${LIBDIR}/slic3r/Utils/Duet.hpp
${LIBDIR}/slic3r/Utils/PrintHost.cpp
${LIBDIR}/slic3r/Utils/PrintHost.hpp
${LIBDIR}/slic3r/Utils/Bonjour.cpp
${LIBDIR}/slic3r/Utils/Bonjour.hpp
${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
${LIBDIR}/slic3r/Utils/PresetUpdater.hpp
${LIBDIR}/slic3r/Utils/Time.cpp
${LIBDIR}/slic3r/Utils/Time.hpp
${LIBDIR}/slic3r/Utils/HexFile.cpp
${LIBDIR}/slic3r/Utils/HexFile.hpp
${LIBDIR}/slic3r/AppController.hpp
${LIBDIR}/slic3r/AppController.cpp
${LIBDIR}/slic3r/AppControllerWx.cpp
)
target_link_libraries(libslic3r_gui libslic3r avrdude)

View file

@ -0,0 +1,532 @@
#include "Snapshot.hpp"
#include "../GUI/AppConfig.hpp"
#include "../GUI/PresetBundle.hpp"
#include "../Utils/Time.hpp"
#include <time.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Config.hpp"
#include "../../libslic3r/FileParserError.hpp"
#include "../../libslic3r/Utils.hpp"
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
namespace Slic3r {
namespace GUI {
namespace Config {
void Snapshot::clear()
{
this->id.clear();
this->time_captured = 0;
this->slic3r_version_captured = Semver::invalid();
this->comment.clear();
this->reason = SNAPSHOT_UNKNOWN;
this->print.clear();
this->filaments.clear();
this->printer.clear();
}
void Snapshot::load_ini(const std::string &path)
{
this->clear();
auto throw_on_parse_error = [&path](const std::string &msg) {
throw file_parser_error(std::string("Failed loading the snapshot file. Reason: ") + msg, path);
};
// Load the snapshot.ini file.
boost::property_tree::ptree tree;
try {
boost::nowide::ifstream ifs(path);
boost::property_tree::read_ini(ifs, tree);
} catch (const std::ifstream::failure &err) {
throw file_parser_error(std::string("The snapshot file cannot be loaded. Reason: ") + err.what(), path);
} catch (const std::runtime_error &err) {
throw_on_parse_error(err.what());
}
// Parse snapshot.ini
std::string group_name_vendor = "Vendor:";
std::string key_filament = "filament";
std::string key_prefix_model = "model_";
for (auto &section : tree) {
if (section.first == "snapshot") {
// Parse the common section.
for (auto &kvp : section.second) {
if (kvp.first == "id")
this->id = kvp.second.data();
else if (kvp.first == "time_captured") {
this->time_captured = Slic3r::Utils::parse_time_ISO8601Z(kvp.second.data());
if (this->time_captured == (time_t)-1)
throw_on_parse_error("invalid timestamp");
} else if (kvp.first == "slic3r_version_captured") {
auto semver = Semver::parse(kvp.second.data());
if (! semver)
throw_on_parse_error("invalid slic3r_version_captured semver");
this->slic3r_version_captured = *semver;
} else if (kvp.first == "comment") {
this->comment = kvp.second.data();
} else if (kvp.first == "reason") {
std::string rsn = kvp.second.data();
if (rsn == "upgrade")
this->reason = SNAPSHOT_UPGRADE;
else if (rsn == "downgrade")
this->reason = SNAPSHOT_DOWNGRADE;
else if (rsn == "before_rollback")
this->reason = SNAPSHOT_BEFORE_ROLLBACK;
else if (rsn == "user")
this->reason = SNAPSHOT_USER;
else
this->reason = SNAPSHOT_UNKNOWN;
}
}
} else if (section.first == "presets") {
// Load the names of the active presets.
for (auto &kvp : section.second) {
if (kvp.first == "print") {
this->print = kvp.second.data();
} else if (boost::starts_with(kvp.first, "filament")) {
int idx = 0;
if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) {
if (int(this->filaments.size()) <= idx)
this->filaments.resize(idx + 1, std::string());
this->filaments[idx] = kvp.second.data();
}
} else if (kvp.first == "printer") {
this->printer = kvp.second.data();
}
}
} else if (boost::starts_with(section.first, group_name_vendor) && section.first.size() > group_name_vendor.size()) {
// Vendor specific section.
VendorConfig vc;
vc.name = section.first.substr(group_name_vendor.size());
for (auto &kvp : section.second) {
if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") {
// Version of the vendor specific config bundle bundled with this snapshot.
auto semver = Semver::parse(kvp.second.data());
if (! semver)
throw_on_parse_error("invalid " + kvp.first + " format for " + section.first);
if (kvp.first == "version")
vc.version.config_version = *semver;
else if (kvp.first == "min_slic3r_version")
vc.version.min_slic3r_version = *semver;
else
vc.version.max_slic3r_version = *semver;
} else if (boost::starts_with(kvp.first, key_prefix_model) && kvp.first.size() > key_prefix_model.size()) {
// Parse the printer variants installed for the current model.
auto &set_variants = vc.models_variants_installed[kvp.first.substr(key_prefix_model.size())];
std::vector<std::string> variants;
if (unescape_strings_cstyle(kvp.second.data(), variants))
for (auto &variant : variants)
set_variants.insert(std::move(variant));
}
}
this->vendor_configs.emplace_back(std::move(vc));
}
}
// Sort the vendors lexicographically.
std::sort(this->vendor_configs.begin(), this->vendor_configs.begin(),
[](const VendorConfig &cfg1, const VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
}
static std::string reason_string(const Snapshot::Reason reason)
{
switch (reason) {
case Snapshot::SNAPSHOT_UPGRADE:
return "upgrade";
case Snapshot::SNAPSHOT_DOWNGRADE:
return "downgrade";
case Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
return "before_rollback";
case Snapshot::SNAPSHOT_USER:
return "user";
case Snapshot::SNAPSHOT_UNKNOWN:
default:
return "unknown";
}
}
void Snapshot::save_ini(const std::string &path)
{
boost::nowide::ofstream c;
c.open(path, std::ios::out | std::ios::trunc);
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
// Export the common "snapshot".
c << std::endl << "[snapshot]" << std::endl;
c << "id = " << this->id << std::endl;
c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl;
c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
c << "comment = " << this->comment << std::endl;
c << "reason = " << reason_string(this->reason) << std::endl;
// Export the active presets at the time of the snapshot.
c << std::endl << "[presets]" << std::endl;
c << "print = " << this->print << std::endl;
c << "filament = " << this->filaments.front() << std::endl;
for (size_t i = 1; i < this->filaments.size(); ++ i)
c << "filament_" << std::to_string(i) << " = " << this->filaments[i] << std::endl;
c << "printer = " << this->printer << std::endl;
// Export the vendor configs.
for (const VendorConfig &vc : this->vendor_configs) {
c << std::endl << "[Vendor:" << vc.name << "]" << std::endl;
c << "version = " << vc.version.config_version.to_string() << std::endl;
c << "min_slic3r_version = " << vc.version.min_slic3r_version.to_string() << std::endl;
c << "max_slic3r_version = " << vc.version.max_slic3r_version.to_string() << std::endl;
// Export installed printer models and their variants.
for (const auto &model : vc.models_variants_installed) {
if (model.second.size() == 0)
continue;
const std::vector<std::string> variants(model.second.begin(), model.second.end());
const auto escaped = escape_strings_cstyle(variants);
c << "model_" << model.first << " = " << escaped << std::endl;
}
}
c.close();
}
void Snapshot::export_selections(AppConfig &config) const
{
assert(filaments.size() >= 1);
config.clear_section("presets");
config.set("presets", "print", print);
config.set("presets", "filament", filaments.front());
for (int i = 1; i < filaments.size(); ++i) {
char name[64];
sprintf(name, "filament_%d", i);
config.set("presets", name, filaments[i]);
}
config.set("presets", "printer", printer);
}
void Snapshot::export_vendor_configs(AppConfig &config) const
{
std::map<std::string, std::map<std::string, std::set<std::string>>> vendors;
for (const VendorConfig &vc : vendor_configs)
vendors[vc.name] = vc.models_variants_installed;
config.set_vendors(std::move(vendors));
}
// Perform a deep compare of the active print / filament / printer / vendor directories.
// Return true if the content of the current print / filament / printer / vendor directories
// matches the state stored in this snapshot.
bool Snapshot::equal_to_active(const AppConfig &app_config) const
{
// 1) Check, whether this snapshot contains the same set of active vendors, printer models and variants
// as app_config.
{
std::set<std::string> matched;
for (const VendorConfig &vc : this->vendor_configs) {
auto it_vendor_models_variants = app_config.vendors().find(vc.name);
if (it_vendor_models_variants == app_config.vendors().end() ||
it_vendor_models_variants->second != vc.models_variants_installed)
// There are more vendors enabled in the snapshot than currently installed.
return false;
matched.insert(vc.name);
}
for (const std::pair<std::string, std::map<std::string, std::set<std::string>>> &v : app_config.vendors())
if (matched.find(v.first) == matched.end() && ! v.second.empty())
// There are more vendors currently installed than enabled in the snapshot.
return false;
}
// 2) Check, whether this snapshot references the same set of ini files as the current state.
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_dir = boost::filesystem::path(Slic3r::data_dir()) / SLIC3R_SNAPSHOTS_DIR / this->id;
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
boost::filesystem::path path1 = data_dir / subdir;
boost::filesystem::path path2 = snapshot_dir / subdir;
std::vector<std::string> files1, files2;
for (auto &dir_entry : boost::filesystem::directory_iterator(path1))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
files1.emplace_back(dir_entry.path().filename().string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path2))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
files2.emplace_back(dir_entry.path().filename().string());
std::sort(files1.begin(), files1.end());
std::sort(files2.begin(), files2.end());
if (files1 != files2)
return false;
for (const std::string &filename : files1) {
FILE *f1 = boost::nowide::fopen((path1 / filename).string().c_str(), "rb");
FILE *f2 = boost::nowide::fopen((path2 / filename).string().c_str(), "rb");
bool same = true;
if (f1 && f2) {
char buf1[4096];
char buf2[4096];
do {
size_t r1 = fread(buf1, 1, 4096, f1);
size_t r2 = fread(buf2, 1, 4096, f2);
if (r1 != r2 || memcmp(buf1, buf2, r1)) {
same = false;
break;
}
} while (! feof(f1) || ! feof(f2));
} else
same = false;
if (f1)
fclose(f1);
if (f2)
fclose(f2);
if (! same)
return false;
}
}
return true;
}
size_t SnapshotDB::load_db()
{
boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir();
m_snapshots.clear();
// Walk over the snapshot directories and load their index.
std::string errors_cummulative;
for (auto &dir_entry : boost::filesystem::directory_iterator(snapshots_dir))
if (boost::filesystem::is_directory(dir_entry.status())) {
// Try to read "snapshot.ini".
boost::filesystem::path path_ini = dir_entry.path() / SLIC3R_SNAPSHOT_FILE;
Snapshot snapshot;
try {
snapshot.load_ini(path_ini.string());
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
continue;
}
// Check that the name of the snapshot directory matches the snapshot id stored in the snapshot.ini file.
if (dir_entry.path().filename().string() != snapshot.id) {
errors_cummulative += std::string("Snapshot ID ") + snapshot.id + " does not match the snapshot directory " + dir_entry.path().filename().string() + "\n";
continue;
}
m_snapshots.emplace_back(std::move(snapshot));
}
// Sort the snapshots by their date/time.
std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; });
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
return m_snapshots.size();
}
void SnapshotDB::update_slic3r_versions(std::vector<Index> &index_db)
{
for (Snapshot &snapshot : m_snapshots) {
for (Snapshot::VendorConfig &vendor_config : snapshot.vendor_configs) {
auto it = std::find_if(index_db.begin(), index_db.end(), [&vendor_config](const Index &idx) { return idx.vendor() == vendor_config.name; });
if (it != index_db.end()) {
Index::const_iterator it_version = it->find(vendor_config.version.config_version);
if (it_version != it->end()) {
vendor_config.version.min_slic3r_version = it_version->min_slic3r_version;
vendor_config.version.max_slic3r_version = it_version->max_slic3r_version;
}
}
}
}
}
static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst)
{
if (! boost::filesystem::is_directory(path_dst) &&
! boost::filesystem::create_directory(path_dst))
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists);
}
static void delete_existing_ini_files(const boost::filesystem::path &path)
{
if (! boost::filesystem::is_directory(path))
return;
for (auto &dir_entry : boost::filesystem::directory_iterator(path))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
boost::filesystem::remove(dir_entry.path());
}
const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
{
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
// 1) Prepare the snapshot structure.
Snapshot snapshot;
// Snapshot header.
snapshot.time_captured = Slic3r::Utils::get_current_time_utc();
snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured);
snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); // XXX: have Semver Slic3r version
snapshot.comment = comment;
snapshot.reason = reason;
// Active presets at the time of the snapshot.
snapshot.print = app_config.get("presets", "print");
snapshot.filaments.emplace_back(app_config.get("presets", "filament"));
snapshot.printer = app_config.get("presets", "printer");
for (unsigned int i = 1; i < 1000; ++ i) {
char name[64];
sprintf(name, "filament_%d", i);
if (! app_config.has("presets", name))
break;
snapshot.filaments.emplace_back(app_config.get("presets", name));
}
// Vendor specific config bundles and installed printers.
for (const std::pair<std::string, std::map<std::string, std::set<std::string>>> &vendor : app_config.vendors()) {
Snapshot::VendorConfig cfg;
cfg.name = vendor.first;
cfg.models_variants_installed = vendor.second;
for (auto it = cfg.models_variants_installed.begin(); it != cfg.models_variants_installed.end();)
if (it->second.empty())
cfg.models_variants_installed.erase(it ++);
else
++ it;
// Read the active config bundle, parse the config version.
PresetBundle bundle;
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
for (const VendorProfile &vp : bundle.vendors)
if (vp.id == cfg.name)
cfg.version.config_version = vp.config_version;
// Fill-in the min/max slic3r version from the config index, if possible.
try {
// Load the config index for the vendor.
Index index;
index.load(data_dir / "vendor" / (cfg.name + ".idx"));
auto it = index.find(cfg.version.config_version);
if (it != index.end()) {
cfg.version.min_slic3r_version = it->min_slic3r_version;
cfg.version.max_slic3r_version = it->max_slic3r_version;
}
} catch (const std::runtime_error &err) {
}
snapshot.vendor_configs.emplace_back(std::move(cfg));
}
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
boost::filesystem::create_directory(snapshot_dir);
// Backup the presets.
for (const char *subdir : { "print", "filament", "printer", "vendor" })
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
m_snapshots.emplace_back(std::move(snapshot));
return m_snapshots.back();
}
const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config)
{
for (const Snapshot &snapshot : m_snapshots)
if (snapshot.id == id) {
this->restore_snapshot(snapshot, app_config);
return snapshot;
}
throw std::runtime_error(std::string("Snapshot with id " + id + " was not found."));
}
void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config)
{
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
// Remove existing ini files and restore the ini files from the snapshot.
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
delete_existing_ini_files(data_dir / subdir);
copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir);
}
// Update AppConfig with the selections of the print / filament / printer profiles
// and about the installed printer types and variants.
snapshot.export_selections(app_config);
snapshot.export_vendor_configs(app_config);
}
bool SnapshotDB::is_on_snapshot(AppConfig &app_config) const
{
// Is the "on_snapshot" configuration value set?
std::string on_snapshot = app_config.get("on_snapshot");
if (on_snapshot.empty())
// No, we are not on a snapshot.
return false;
// Is the "on_snapshot" equal to the current configuration state?
auto it_snapshot = this->snapshot(on_snapshot);
if (it_snapshot != this->end() && it_snapshot->equal_to_active(app_config))
// Yes, we are on the snapshot.
return true;
// No, we are no more on a snapshot. Reset the state.
app_config.set("on_snapshot", "");
return false;
}
SnapshotDB::const_iterator SnapshotDB::snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version)
{
auto it_found = m_snapshots.end();
Snapshot::VendorConfig key;
key.name = vendor_name;
for (auto it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) {
const Snapshot &snapshot = *it;
auto it_vendor_config = std::lower_bound(snapshot.vendor_configs.begin(), snapshot.vendor_configs.end(),
key, [](const Snapshot::VendorConfig &cfg1, const Snapshot::VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
if (it_vendor_config != snapshot.vendor_configs.end() && it_vendor_config->name == vendor_name &&
config_version == it_vendor_config->version.config_version) {
// Vendor config found with the correct version.
// Save it, but continue searching, as we want the newest snapshot.
it_found = it;
}
}
return it_found;
}
SnapshotDB::const_iterator SnapshotDB::snapshot(const std::string &id) const
{
for (const_iterator it = m_snapshots.begin(); it != m_snapshots.end(); ++ it)
if (it->id == id)
return it;
return m_snapshots.end();
}
boost::filesystem::path SnapshotDB::create_db_dir()
{
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshots_dir = data_dir / SLIC3R_SNAPSHOTS_DIR;
for (const boost::filesystem::path &path : { data_dir, snapshots_dir }) {
boost::filesystem::path subdir = path;
subdir.make_preferred();
if (! boost::filesystem::is_directory(subdir) &&
! boost::filesystem::create_directory(subdir))
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + subdir.string());
}
return snapshots_dir;
}
SnapshotDB& SnapshotDB::singleton()
{
static SnapshotDB instance;
static bool loaded = false;
if (! loaded) {
try {
loaded = true;
// Load the snapshot database.
instance.load_db();
// Load the vendor specific configuration indices.
std::vector<Index> index_db = Index::load_db();
// Update the min / max slic3r versions compatible with the configurations stored inside the snapshots
// based on the min / max slic3r versions defined by the vendor specific config indices.
instance.update_slic3r_versions(index_db);
} catch (std::exception &ex) {
}
}
return instance;
}
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,129 @@
#ifndef slic3r_GUI_Snapshot_
#define slic3r_GUI_Snapshot_
#include <map>
#include <set>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "Version.hpp"
#include "../Utils/Semver.hpp"
namespace Slic3r {
class AppConfig;
namespace GUI {
namespace Config {
class Index;
// A snapshot contains:
// Slic3r.ini
// vendor/
// print/
// filament/
// printer/
class Snapshot
{
public:
enum Reason {
SNAPSHOT_UNKNOWN,
SNAPSHOT_UPGRADE,
SNAPSHOT_DOWNGRADE,
SNAPSHOT_BEFORE_ROLLBACK,
SNAPSHOT_USER,
};
Snapshot() { clear(); }
void clear();
void load_ini(const std::string &path);
void save_ini(const std::string &path);
// Export the print / filament / printer selections to be activated into the AppConfig.
void export_selections(AppConfig &config) const;
void export_vendor_configs(AppConfig &config) const;
// Perform a deep compare of the active print / filament / printer / vendor directories.
// Return true if the content of the current print / filament / printer / vendor directories
// matches the state stored in this snapshot.
bool equal_to_active(const AppConfig &app_config) const;
// ID of a snapshot should equal to the name of the snapshot directory.
// The ID contains the date/time, reason and comment to be human readable.
std::string id;
std::time_t time_captured;
// Which Slic3r version captured this snapshot?
Semver slic3r_version_captured = Semver::invalid();
// Comment entered by the user at the start of the snapshot capture.
std::string comment;
Reason reason;
std::string format_reason() const;
// Active presets at the time of the snapshot.
std::string print;
std::vector<std::string> filaments;
std::string printer;
// Annotation of the vendor configuration stored in the snapshot.
// This information is displayed to the user and used to decide compatibility
// of the configuration stored in the snapshot with the running Slic3r version.
struct VendorConfig {
// Name of the vendor contained in this snapshot.
std::string name;
// Version of the vendor config contained in this snapshot, along with compatibility data.
Version version;
// Which printer models of this vendor were installed, and which variants of the models?
std::map<std::string, std::set<std::string>> models_variants_installed;
};
// List of vendor configs contained in this snapshot, sorted lexicographically.
std::vector<VendorConfig> vendor_configs;
};
class SnapshotDB
{
public:
// Initialize the SnapshotDB singleton instance. Load the database if it has not been loaded yet.
static SnapshotDB& singleton();
typedef std::vector<Snapshot>::const_iterator const_iterator;
// Load the snapshot database from the snapshots directory.
// If the snapshot directory or its parent does not exist yet, it will be created.
// Returns a number of snapshots loaded.
size_t load_db();
void update_slic3r_versions(std::vector<Index> &index_db);
// Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles,
// create an index.
const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment = "");
const Snapshot& restore_snapshot(const std::string &id, AppConfig &app_config);
void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config);
// Test whether the AppConfig's on_snapshot variable points to an existing snapshot, and the existing snapshot
// matches the current state. If it does not match the current state, the AppConfig's "on_snapshot" ID is reset.
bool is_on_snapshot(AppConfig &app_config) const;
// Finds the newest snapshot, which contains a config bundle for vendor_name with config_version.
const_iterator snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version);
const_iterator begin() const { return m_snapshots.begin(); }
const_iterator end() const { return m_snapshots.end(); }
const_iterator snapshot(const std::string &id) const;
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
private:
// Create the snapshots directory if it does not exist yet.
static boost::filesystem::path create_db_dir();
// Snapshots are sorted by their date/time, oldest first.
std::vector<Snapshot> m_snapshots;
};
} // namespace Config
} // namespace GUI
} // namespace Slic3r
#endif /* slic3r_GUI_Snapshot_ */

View file

@ -0,0 +1,319 @@
#include "Version.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/nowide/fstream.hpp>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Config.hpp"
#include "../../libslic3r/FileParserError.hpp"
#include "../../libslic3r/Utils.hpp"
namespace Slic3r {
namespace GUI {
namespace Config {
static const Semver s_current_slic3r_semver(SLIC3R_VERSION);
// Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix.
static int compare_prerelease(const char *p1, const char *p2)
{
for (;;) {
char c1 = *p1 ++;
char c2 = *p2 ++;
bool a1 = std::isalpha(c1) && c1 != 0;
bool a2 = std::isalpha(c2) && c2 != 0;
if (a1) {
if (a2) {
if (c1 != c2)
return (c1 < c2) ? -1 : 1;
} else
return 1;
} else {
if (a2)
return -1;
else
return 0;
}
}
// This shall never happen.
return 0;
}
bool Version::is_slic3r_supported(const Semver &slic3r_version) const
{
if (! slic3r_version.in_range(min_slic3r_version, max_slic3r_version))
return false;
// Now verify, whether the configuration pre-release status is compatible with the Slic3r's pre-release status.
// Alpha Slic3r will happily load any configuration, while beta Slic3r will ignore alpha configurations etc.
const char *prerelease_slic3r = slic3r_version.prerelease();
const char *prerelease_config = this->config_version.prerelease();
if (prerelease_config == nullptr)
// Released config is always supported.
return true;
else if (prerelease_slic3r == nullptr)
// Released slic3r only supports released configs.
return false;
// Compare the pre-release status of Slic3r against the config.
// If the prerelease status of slic3r is lexicographically lower or equal
// to the prerelease status of the config, accept it.
return compare_prerelease(prerelease_slic3r, prerelease_config) != 1;
}
bool Version::is_current_slic3r_supported() const
{
return this->is_slic3r_supported(s_current_slic3r_semver);
}
#if 0
//TODO: This test should be moved to a unit test, once we have C++ unit tests in place.
static int version_test()
{
Version v;
v.config_version = *Semver::parse("1.1.2");
v.min_slic3r_version = *Semver::parse("1.38.0");
v.max_slic3r_version = Semver::inf();
assert(v.is_slic3r_supported(*Semver::parse("1.38.0")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha")));
assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha")));
// Test the prerelease status.
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-alpha");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-alpha1");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-beta");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-rc");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-rc2");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
// Test the upper boundary.
v.config_version = *Semver::parse("1.1.2");
v.max_slic3r_version = *Semver::parse("1.39.3-beta1");
assert(v.is_slic3r_supported(*Semver::parse("1.38.0")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha")));
return 0;
}
static int version_test_run = version_test();
#endif
inline char* left_trim(char *c)
{
for (; *c == ' ' || *c == '\t'; ++ c);
return c;
}
inline char* right_trim(char *start)
{
char *end = start + strlen(start) - 1;
for (; end >= start && (*end == ' ' || *end == '\t'); -- end);
*(++ end) = 0;
return end;
}
inline std::string unquote_value(char *value, char *end, const std::string &path, int idx_line)
{
std::string svalue;
if (value == end) {
// Empty string is a valid string.
} else if (*value == '"') {
if (++ value > -- end || *end != '"')
throw file_parser_error("String not enquoted correctly", path, idx_line);
*end = 0;
if (! unescape_string_cstyle(value, svalue))
throw file_parser_error("Invalid escape sequence inside a quoted string", path, idx_line);
} else
svalue.assign(value, end);
return svalue;
}
inline std::string unquote_version_comment(char *value, char *end, const std::string &path, int idx_line)
{
std::string svalue;
if (value == end) {
// Empty string is a valid string.
} else if (*value == '"') {
if (++ value > -- end || *end != '"')
throw file_parser_error("Version comment not enquoted correctly", path, idx_line);
*end = 0;
if (! unescape_string_cstyle(value, svalue))
throw file_parser_error("Invalid escape sequence inside a quoted version comment", path, idx_line);
} else
svalue.assign(value, end);
return svalue;
}
size_t Index::load(const boost::filesystem::path &path)
{
m_configs.clear();
m_vendor = path.stem().string();
boost::nowide::ifstream ifs(path.string());
std::string line;
size_t idx_line = 0;
Version ver;
while (std::getline(ifs, line)) {
++ idx_line;
// Skip the initial white spaces.
char *key = left_trim(const_cast<char*>(line.data()));
if (*key == '#')
// Skip a comment line.
continue;
// Right trim the line.
char *end = right_trim(key);
if (key == end)
// Skip an empty line.
continue;
// Keyword may only contain alphanumeric characters. Semantic version may in addition contain "+.-".
char *key_end = key;
bool maybe_semver = true;
for (; *key_end != 0; ++ key_end) {
if (std::isalnum(*key_end) || strchr("+.-", *key_end) != nullptr) {
// It may be a semver.
} else if (*key_end == '_') {
// Cannot be a semver, but it may be a key.
maybe_semver = false;
} else
// End of semver or keyword.
break;
}
if (*key_end != 0 && *key_end != ' ' && *key_end != '\t' && *key_end != '=')
throw file_parser_error("Invalid keyword or semantic version", path, idx_line);
char *value = left_trim(key_end);
bool key_value_pair = *value == '=';
if (key_value_pair)
value = left_trim(value + 1);
*key_end = 0;
boost::optional<Semver> semver;
if (maybe_semver)
semver = Semver::parse(key);
if (key_value_pair) {
if (semver)
throw file_parser_error("Key cannot be a semantic version", path, idx_line);\
// Verify validity of the key / value pair.
std::string svalue = unquote_value(value, end, path.string(), idx_line);
if (strcmp(key, "min_slic3r_version") == 0 || strcmp(key, "max_slic3r_version") == 0) {
if (! svalue.empty())
semver = Semver::parse(svalue);
if (! semver)
throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line);
if (strcmp(key, "min_slic3r_version") == 0)
ver.min_slic3r_version = *semver;
else
ver.max_slic3r_version = *semver;
} else {
// Ignore unknown keys, as there may come new keys in the future.
}
continue;
}
if (! semver)
throw file_parser_error("Invalid semantic version", path, idx_line);
ver.config_version = *semver;
ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path.string(), idx_line);
m_configs.emplace_back(ver);
}
// Sort the configs by their version.
std::sort(m_configs.begin(), m_configs.end(), [](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; });
return m_configs.size();
}
Semver Index::version() const
{
Semver ver = Semver::zero();
for (const Version &cv : m_configs)
if (cv.config_version >= ver)
ver = cv.config_version;
return ver;
}
Index::const_iterator Index::find(const Semver &ver) const
{
Version key;
key.config_version = ver;
auto it = std::lower_bound(m_configs.begin(), m_configs.end(), key,
[](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; });
return (it == m_configs.end() || it->config_version == ver) ? it : m_configs.end();
}
Index::const_iterator Index::recommended() const
{
int idx = -1;
const_iterator highest = this->end();
for (const_iterator it = this->begin(); it != this->end(); ++ it)
if (it->is_current_slic3r_supported() &&
(highest == this->end() || highest->config_version < it->config_version))
highest = it;
return highest;
}
std::vector<Index> Index::load_db()
{
boost::filesystem::path cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "cache";
std::vector<Index> index_db;
std::string errors_cummulative;
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) {
Index idx;
try {
idx.load(dir_entry.path());
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
continue;
}
index_db.emplace_back(std::move(idx));
}
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
return index_db;
}
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,88 @@
#ifndef slic3r_GUI_ConfigIndex_
#define slic3r_GUI_ConfigIndex_
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "../../libslic3r/FileParserError.hpp"
#include "../Utils/Semver.hpp"
namespace Slic3r {
namespace GUI {
namespace Config {
// Configuration bundle version.
struct Version
{
// Version of this config.
Semver config_version = Semver::invalid();
// Minimum Slic3r version, for which this config is applicable.
Semver min_slic3r_version = Semver::zero();
// Maximum Slic3r version, for which this config is recommended.
// Slic3r should read older configuration and upgrade to a newer format,
// but likely there has been a better configuration published, using the new features.
Semver max_slic3r_version = Semver::inf();
// Single comment line.
std::string comment;
bool is_slic3r_supported(const Semver &slicer_version) const;
bool is_current_slic3r_supported() const;
};
// Index of vendor specific config bundle versions and Slic3r compatibilities.
// The index is being downloaded from the internet, also an initial version of the index
// is contained in the Slic3r installation.
//
// The index has a simple format:
//
// min_sic3r_version =
// max_slic3r_version =
// config_version "comment"
// config_version "comment"
// ...
// min_slic3r_version =
// max_slic3r_version =
// config_version comment
// config_version comment
// ...
//
// The min_slic3r_version, max_slic3r_version keys are applied to the config versions below,
// empty slic3r version means an open interval.
class Index
{
public:
typedef std::vector<Version>::const_iterator const_iterator;
// Read a config index file in the simple format described in the Index class comment.
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
size_t load(const boost::filesystem::path &path);
const std::string& vendor() const { return m_vendor; }
// Returns version of the index as the highest version of all the configs.
// If there is no config, Semver::zero() is returned.
Semver version() const;
const_iterator begin() const { return m_configs.begin(); }
const_iterator end() const { return m_configs.end(); }
const_iterator find(const Semver &ver) const;
const std::vector<Version>& configs() const { return m_configs; }
// Finds a recommended config to be installed for the current Slic3r version.
// Returns configs().end() if such version does not exist in the index. This shall never happen
// if the index is valid.
const_iterator recommended() const;
// Load all vendor specific indices.
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
static std::vector<Index> load_db();
private:
std::string m_vendor;
std::vector<Version> m_configs;
};
} // namespace Config
} // namespace GUI
} // namespace Slic3r
#endif /* slic3r_GUI_ConfigIndex_ */

183
src/slic3r/GUI/2DBed.cpp Normal file
View file

@ -0,0 +1,183 @@
#include "2DBed.hpp"
#include <wx/dcbuffer.h>
#include "BoundingBox.hpp"
#include "Geometry.hpp"
#include "ClipperUtils.hpp"
namespace Slic3r {
namespace GUI {
void Bed_2D::repaint()
{
wxAutoBufferedPaintDC dc(this);
auto cw = GetSize().GetWidth();
auto ch = GetSize().GetHeight();
// when canvas is not rendered yet, size is 0, 0
if (cw == 0) return ;
if (m_user_drawn_background) {
// On all systems the AutoBufferedPaintDC() achieves double buffering.
// On MacOS the background is erased, on Windows the background is not erased
// and on Linux / GTK the background is erased to gray color.
// Fill DC with the background on Windows & Linux / GTK.
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
dc.SetPen(*new wxPen(color, 1, wxPENSTYLE_SOLID));
dc.SetBrush(*new wxBrush(color, wxBRUSHSTYLE_SOLID));
auto rect = GetUpdateRegion().GetBox();
dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight());
}
// turn cw and ch from sizes to max coordinates
cw--;
ch--;
auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch));
// leave space for origin point
cbb.min(0) += 4;
cbb.max -= Vec2d(4., 4.);
// leave space for origin label
cbb.max(1) -= 13;
// read new size
cw = cbb.size()(0);
ch = cbb.size()(1);
auto ccenter = cbb.center();
// get bounding box of bed shape in G - code coordinates
auto bed_shape = m_bed_shape;
auto bed_polygon = Polygon::new_scale(m_bed_shape);
auto bb = BoundingBoxf(m_bed_shape);
bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area
auto bw = bb.size()(0);
auto bh = bb.size()(1);
auto bcenter = bb.center();
// calculate the scaling factor for fitting bed shape in canvas area
auto sfactor = std::min(cw/bw, ch/bh);
auto shift = Vec2d(
ccenter(0) - bcenter(0) * sfactor,
ccenter(1) - bcenter(1) * sfactor
);
m_scale_factor = sfactor;
m_shift = Vec2d(shift(0) + cbb.min(0),
shift(1) - (cbb.max(1) - GetSize().GetHeight()));
// draw bed fill
dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxSOLID));
wxPointList pt_list;
for (auto pt: m_bed_shape)
{
Point pt_pix = to_pixels(pt);
pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1)));
}
dc.DrawPolygon(&pt_list, 0, 0);
// draw grid
auto step = 10; // 1cm grid
Polylines polylines;
for (auto x = bb.min(0) - fmod(bb.min(0), step) + step; x < bb.max(0); x += step) {
polylines.push_back(Polyline::new_scale({ Vec2d(x, bb.min(1)), Vec2d(x, bb.max(1)) }));
}
for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) {
polylines.push_back(Polyline::new_scale({ Vec2d(bb.min(0), y), Vec2d(bb.max(0), y) }));
}
polylines = intersection_pl(polylines, bed_polygon);
dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxSOLID));
for (auto pl : polylines)
{
for (size_t i = 0; i < pl.points.size()-1; i++){
Point pt1 = to_pixels(unscale(pl.points[i]));
Point pt2 = to_pixels(unscale(pl.points[i+1]));
dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1));
}
}
// draw bed contour
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxTRANSPARENT));
dc.DrawPolygon(&pt_list, 0, 0);
auto origin_px = to_pixels(Vec2d(0, 0));
// draw axes
auto axes_len = 50;
auto arrow_len = 6;
auto arrow_angle = Geometry::deg2rad(45.0);
dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxSOLID)); // red
auto x_end = Vec2d(origin_px(0) + axes_len, origin_px(1));
dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(x_end(0), x_end(1)));
for (auto angle : { -arrow_angle, arrow_angle }){
auto end = Eigen::Translation2d(x_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- x_end) * Eigen::Vector2d(x_end(0) - arrow_len, x_end(1));
dc.DrawLine(wxPoint(x_end(0), x_end(1)), wxPoint(end(0), end(1)));
}
dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxSOLID)); // green
auto y_end = Vec2d(origin_px(0), origin_px(1) - axes_len);
dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(y_end(0), y_end(1)));
for (auto angle : { -arrow_angle, arrow_angle }) {
auto end = Eigen::Translation2d(y_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- y_end) * Eigen::Vector2d(y_end(0), y_end(1) + arrow_len);
dc.DrawLine(wxPoint(y_end(0), y_end(1)), wxPoint(end(0), end(1)));
}
// draw origin
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxSOLID));
dc.DrawCircle(origin_px(0), origin_px(1), 3);
static const auto origin_label = wxString("(0,0)");
dc.SetTextForeground(wxColour(0, 0, 0));
dc.SetFont(wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL));
auto extent = dc.GetTextExtent(origin_label);
const auto origin_label_x = origin_px(0) <= cw / 2 ? origin_px(0) + 1 : origin_px(0) - 1 - extent.GetWidth();
const auto origin_label_y = origin_px(1) <= ch / 2 ? origin_px(1) + 1 : origin_px(1) - 1 - extent.GetHeight();
dc.DrawText(origin_label, origin_label_x, origin_label_y);
// draw current position
if (m_pos!= Vec2d(0, 0)) {
auto pos_px = to_pixels(m_pos);
dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxSOLID));
dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxTRANSPARENT));
dc.DrawCircle(pos_px(0), pos_px(1), 5);
dc.DrawLine(pos_px(0) - 15, pos_px(1), pos_px(0) + 15, pos_px(1));
dc.DrawLine(pos_px(0), pos_px(1) - 15, pos_px(0), pos_px(1) + 15);
}
m_painted = true;
}
// convert G - code coordinates into pixels
Point Bed_2D::to_pixels(Vec2d point){
auto p = point * m_scale_factor + m_shift;
return Point(p(0), GetSize().GetHeight() - p(1));
}
void Bed_2D::mouse_event(wxMouseEvent event){
if (!m_interactive) return;
if (!m_painted) return;
auto pos = event.GetPosition();
auto point = to_units(Point(pos.x, pos.y));
if (event.LeftDown() || event.Dragging()) {
if (m_on_move)
m_on_move(point) ;
Refresh();
}
}
// convert pixels into G - code coordinates
Vec2d Bed_2D::to_units(Point point){
return (Vec2d(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor);
}
void Bed_2D::set_pos(Vec2d pos){
m_pos = pos;
Refresh();
}
} // GUI
} // Slic3r

52
src/slic3r/GUI/2DBed.hpp Normal file
View file

@ -0,0 +1,52 @@
#ifndef slic3r_2DBed_hpp_
#define slic3r_2DBed_hpp_
#include <wx/wx.h>
#include "Config.hpp"
namespace Slic3r {
namespace GUI {
class Bed_2D : public wxPanel
{
bool m_user_drawn_background = true;
bool m_painted = false;
bool m_interactive = false;
double m_scale_factor;
Vec2d m_shift = Vec2d::Zero();
Vec2d m_pos = Vec2d::Zero();
std::function<void(Vec2d)> m_on_move = nullptr;
Point to_pixels(Vec2d point);
Vec2d to_units(Point point);
void repaint();
void mouse_event(wxMouseEvent event);
void set_pos(Vec2d pos);
public:
Bed_2D(wxWindow* parent)
{
Create(parent, wxID_ANY, wxDefaultPosition, wxSize(250, -1), wxTAB_TRAVERSAL);
// m_user_drawn_background = $^O ne 'darwin';
#ifdef __APPLE__
m_user_drawn_background = false;
#endif /*__APPLE__*/
Bind(wxEVT_PAINT, ([this](wxPaintEvent e) { repaint(); }));
// EVT_ERASE_BACKGROUND($self, sub{}) if $self->{user_drawn_background};
// Bind(EVT_MOUSE_EVENTS, ([this](wxMouseEvent event){/*mouse_event()*/; }));
Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent event){ mouse_event(event); }));
Bind(wxEVT_MOTION, ([this](wxMouseEvent event){ mouse_event(event); }));
Bind(wxEVT_SIZE, ([this](wxSizeEvent e) { Refresh(); }));
}
~Bed_2D(){}
std::vector<Vec2d> m_bed_shape;
};
} // GUI
} // Slic3r
#endif /* slic3r_2DBed_hpp_ */

2205
src/slic3r/GUI/3DScene.cpp Normal file

File diff suppressed because it is too large Load diff

603
src/slic3r/GUI/3DScene.hpp Normal file
View file

@ -0,0 +1,603 @@
#ifndef slic3r_3DScene_hpp_
#define slic3r_3DScene_hpp_
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Point.hpp"
#include "../../libslic3r/Line.hpp"
#include "../../libslic3r/TriangleMesh.hpp"
#include "../../libslic3r/Utils.hpp"
#include "../../libslic3r/Model.hpp"
#include "../../slic3r/GUI/GLCanvas3DManager.hpp"
class wxBitmap;
class wxWindow;
namespace Slic3r {
class Print;
class PrintObject;
class Model;
class ModelObject;
class GCodePreviewData;
class DynamicPrintConfig;
class ExtrusionPath;
class ExtrusionMultiPath;
class ExtrusionLoop;
class ExtrusionEntity;
class ExtrusionEntityCollection;
// A container for interleaved arrays of 3D vertices and normals,
// possibly indexed by triangles and / or quads.
class GLIndexedVertexArray {
public:
GLIndexedVertexArray() :
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{ this->setup_sizes(); }
GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
triangle_indices(rhs.triangle_indices),
quad_indices(rhs.quad_indices),
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{ this->setup_sizes(); }
GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
triangle_indices(std::move(rhs.triangle_indices)),
quad_indices(std::move(rhs.quad_indices)),
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{ this->setup_sizes(); }
GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs)
{
assert(vertices_and_normals_interleaved_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved;
this->triangle_indices = rhs.triangle_indices;
this->quad_indices = rhs.quad_indices;
this->setup_sizes();
return *this;
}
GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs)
{
assert(vertices_and_normals_interleaved_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
this->triangle_indices = std::move(rhs.triangle_indices);
this->quad_indices = std::move(rhs.quad_indices);
this->setup_sizes();
return *this;
}
// Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x)
std::vector<float> vertices_and_normals_interleaved;
std::vector<int> triangle_indices;
std::vector<int> quad_indices;
// When the geometry data is loaded into the graphics card as Vertex Buffer Objects,
// the above mentioned std::vectors are cleared and the following variables keep their original length.
size_t vertices_and_normals_interleaved_size;
size_t triangle_indices_size;
size_t quad_indices_size;
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not used.
unsigned int vertices_and_normals_interleaved_VBO_id;
unsigned int triangle_indices_VBO_id;
unsigned int quad_indices_VBO_id;
void load_mesh_flat_shading(const TriangleMesh &mesh);
void load_mesh_full_shading(const TriangleMesh &mesh);
inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; }
inline void reserve(size_t sz) {
this->vertices_and_normals_interleaved.reserve(sz * 6);
this->triangle_indices.reserve(sz * 3);
this->quad_indices.reserve(sz * 4);
}
inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) {
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
this->vertices_and_normals_interleaved.push_back(nx);
this->vertices_and_normals_interleaved.push_back(ny);
this->vertices_and_normals_interleaved.push_back(nz);
this->vertices_and_normals_interleaved.push_back(x);
this->vertices_and_normals_interleaved.push_back(y);
this->vertices_and_normals_interleaved.push_back(z);
};
inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
}
inline void push_geometry(const Vec3d& p, const Vec3d& n) {
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
}
inline void push_triangle(int idx1, int idx2, int idx3) {
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
this->triangle_indices.push_back(idx1);
this->triangle_indices.push_back(idx2);
this->triangle_indices.push_back(idx3);
};
inline void push_quad(int idx1, int idx2, int idx3, int idx4) {
if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity())
this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4));
this->quad_indices.push_back(idx1);
this->quad_indices.push_back(idx2);
this->quad_indices.push_back(idx3);
this->quad_indices.push_back(idx4);
};
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
void finalize_geometry(bool use_VBOs);
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
// Render either using an immediate mode, or the VBOs.
void render() const;
void render(const std::pair<size_t, size_t> &tverts_range, const std::pair<size_t, size_t> &qverts_range) const;
// Is there any geometry data stored?
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
// Is this object indexed, or is it just a set of triangles?
bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; }
void clear() {
this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear();
this->quad_indices.clear();
this->setup_sizes();
}
// Shrink the internal storage to tighly fit the data stored.
void shrink_to_fit() {
if (! this->has_VBOs())
this->setup_sizes();
this->vertices_and_normals_interleaved.shrink_to_fit();
this->triangle_indices.shrink_to_fit();
this->quad_indices.shrink_to_fit();
}
BoundingBoxf3 bounding_box() const {
BoundingBoxf3 bbox;
if (! this->vertices_and_normals_interleaved.empty()) {
bbox.defined = true;
bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3];
bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4];
bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5];
for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) {
const float *verts = this->vertices_and_normals_interleaved.data() + i;
bbox.min(0) = std::min<coordf_t>(bbox.min(0), verts[0]);
bbox.min(1) = std::min<coordf_t>(bbox.min(1), verts[1]);
bbox.min(2) = std::min<coordf_t>(bbox.min(2), verts[2]);
bbox.max(0) = std::max<coordf_t>(bbox.max(0), verts[0]);
bbox.max(1) = std::max<coordf_t>(bbox.max(1), verts[1]);
bbox.max(2) = std::max<coordf_t>(bbox.max(2), verts[2]);
}
}
return bbox;
}
private:
inline void setup_sizes() {
vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
triangle_indices_size = this->triangle_indices.size();
quad_indices_size = this->quad_indices.size();
}
};
class LayersTexture
{
public:
LayersTexture() : width(0), height(0), levels(0), cells(0) {}
// Texture data
std::vector<char> data;
// Width of the texture, top level.
size_t width;
// Height of the texture, top level.
size_t height;
// For how many levels of detail is the data allocated?
size_t levels;
// Number of texture cells allocated for the height texture.
size_t cells;
};
class GLVolume {
struct LayerHeightTextureData
{
// ID of the layer height texture
unsigned int texture_id;
// ID of the shader used to render with the layer height texture
unsigned int shader_id;
// The print object to update when generating the layer height texture
const PrintObject* print_object;
float z_cursor_relative;
float edit_band_width;
LayerHeightTextureData() { reset(); }
void reset()
{
texture_id = 0;
shader_id = 0;
print_object = nullptr;
z_cursor_relative = 0.0f;
edit_band_width = 0.0f;
}
bool can_use() const { return (texture_id > 0) && (shader_id > 0) && (print_object != nullptr); }
};
public:
static const float SELECTED_COLOR[4];
static const float HOVER_COLOR[4];
static const float OUTSIDE_COLOR[4];
static const float SELECTED_OUTSIDE_COLOR[4];
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
private:
// Offset of the volume to be rendered.
Vec3d m_offset;
// Rotation around Z axis of the volume to be rendered.
double m_rotation;
// Scale factor of the volume to be rendered.
double m_scaling_factor;
// World matrix of the volume to be rendered.
mutable Transform3f m_world_matrix;
// Whether or not is needed to recalculate the world matrix.
mutable bool m_world_matrix_dirty;
// Bounding box of this volume, in unscaled coordinates.
mutable BoundingBoxf3 m_transformed_bounding_box;
// Whether or not is needed to recalculate the transformed bounding box.
mutable bool m_transformed_bounding_box_dirty;
// Pointer to convex hull of the original mesh, if any.
const TriangleMesh* m_convex_hull;
// Bounding box of this volume, in unscaled coordinates.
mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
// Whether or not is needed to recalculate the transformed convex hull bounding box.
mutable bool m_transformed_convex_hull_bounding_box_dirty;
public:
// Bounding box of this volume, in unscaled coordinates.
BoundingBoxf3 bounding_box;
// Color of the triangles / quads held by this volume.
float color[4];
// Color used to render this volume.
float render_color[4];
// An ID containing the object ID, volume ID and instance ID.
int composite_id;
// An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance.
int select_group_id;
// An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance.
int drag_group_id;
// An ID containing the extruder ID (used to select color).
int extruder_id;
// Is this object selected?
bool selected;
// Whether or not this volume is active for rendering
bool is_active;
// Whether or not to use this volume when applying zoom_to_volumes()
bool zoom_to_volumes;
// Wheter or not this volume is enabled for outside print volume detection in shader.
bool shader_outside_printer_detection_enabled;
// Wheter or not this volume is outside print volume.
bool is_outside;
// Boolean: Is mouse over this object?
bool hover;
// Wheter or not this volume has been generated from a modifier
bool is_modifier;
// Wheter or not this volume has been generated from the wipe tower
bool is_wipe_tower;
// Wheter or not this volume has been generated from an extrusion path
bool is_extrusion_path;
// Interleaved triangles & normals with indexed triangles & quads.
GLIndexedVertexArray indexed_vertex_array;
// Ranges of triangle and quad indices to be rendered.
std::pair<size_t, size_t> tverts_range;
std::pair<size_t, size_t> qverts_range;
// If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts
// of the extrusions per layer.
std::vector<coordf_t> print_zs;
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
std::vector<size_t> offsets;
void set_render_color(float r, float g, float b, float a);
void set_render_color(const float* rgba, unsigned int size);
// Sets render color in dependence of current state
void set_render_color();
double get_rotation();
void set_rotation(double rotation);
const Vec3d& get_offset() const;
void set_offset(const Vec3d& offset);
void set_scaling_factor(double factor);
void set_convex_hull(const TriangleMesh& convex_hull);
void set_select_group_id(const std::string& select_by);
void set_drag_group_id(const std::string& drag_by);
int object_idx() const { return this->composite_id / 1000000; }
int volume_idx() const { return (this->composite_id / 1000) % 1000; }
int instance_idx() const { return this->composite_id % 1000; }
const Transform3f& world_matrix() const;
const BoundingBoxf3& transformed_bounding_box() const;
const BoundingBoxf3& transformed_convex_hull_bounding_box() const;
bool empty() const { return this->indexed_vertex_array.empty(); }
bool indexed() const { return this->indexed_vertex_array.indexed(); }
void set_range(coordf_t low, coordf_t high);
void render() const;
void render_using_layer_height() const;
void render_VBOs(int color_id, int detection_id, int worldmatrix_id) const;
void render_legacy() const;
void finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); }
void release_geometry() { this->indexed_vertex_array.release_geometry(); }
/************************************************ Layer height texture ****************************************************/
std::shared_ptr<LayersTexture> layer_height_texture;
// Data to render this volume using the layer height texture
LayerHeightTextureData layer_height_texture_data;
bool has_layer_height_texture() const
{ return this->layer_height_texture.get() != nullptr; }
size_t layer_height_texture_width() const
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->width; }
size_t layer_height_texture_height() const
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->height; }
size_t layer_height_texture_cells() const
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->cells; }
void* layer_height_texture_data_ptr_level0() const {
return (layer_height_texture.get() == nullptr) ? 0 :
(void*)layer_height_texture->data.data();
}
void* layer_height_texture_data_ptr_level1() const {
return (layer_height_texture.get() == nullptr) ? 0 :
(void*)(layer_height_texture->data.data() + layer_height_texture->width * layer_height_texture->height * 4);
}
double layer_height_texture_z_to_row_id() const;
void generate_layer_height_texture(const PrintObject *print_object, bool force);
void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, const PrintObject* print_object, float z_cursor_relative, float edit_band_width)
{
layer_height_texture_data.texture_id = texture_id;
layer_height_texture_data.shader_id = shader_id;
layer_height_texture_data.print_object = print_object;
layer_height_texture_data.z_cursor_relative = z_cursor_relative;
layer_height_texture_data.edit_band_width = edit_band_width;
}
void reset_layer_height_texture_data() { layer_height_texture_data.reset(); }
};
class GLVolumeCollection
{
// min and max vertex of the print box volume
float print_box_min[3];
float print_box_max[3];
public:
std::vector<GLVolume*> volumes;
GLVolumeCollection() {};
~GLVolumeCollection() { clear(); };
std::vector<int> load_object(
const ModelObject *model_object,
int obj_idx,
const std::vector<int> &instance_idxs,
const std::string &color_by,
const std::string &select_by,
const std::string &drag_by,
bool use_VBOs);
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);
// Render the volumes by OpenGL.
void render_VBOs() const;
void render_legacy() const;
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); }
// Release the geometry data assigned to the volumes.
// If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
void release_geometry() { for (auto *v : volumes) v->release_geometry(); }
// Clear the geometry
void clear() { for (auto *v : volumes) delete v; volumes.clear(); }
bool empty() const { return volumes.empty(); }
void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); }
void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) {
print_box_min[0] = min_x; print_box_min[1] = min_y; print_box_min[2] = min_z;
print_box_max[0] = max_x; print_box_max[1] = max_y; print_box_max[2] = max_z;
}
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null
bool check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state);
void reset_outside_state();
void update_colors_by_extruder(const DynamicPrintConfig* config);
void set_select_by(const std::string& select_by);
void set_drag_by(const std::string& drag_by);
// Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
std::vector<double> get_current_print_zs(bool active_only) const;
private:
GLVolumeCollection(const GLVolumeCollection &other);
GLVolumeCollection& operator=(const GLVolumeCollection &);
};
class _3DScene
{
static GUI::GLCanvas3DManager s_canvas_mgr;
public:
static void init_gl();
static std::string get_gl_info(bool format_as_html, bool extensions);
static bool use_VBOs();
static bool add_canvas(wxGLCanvas* canvas);
static bool remove_canvas(wxGLCanvas* canvas);
static void remove_all_canvases();
static bool init(wxGLCanvas* canvas);
static void set_as_dirty(wxGLCanvas* canvas);
static unsigned int get_volumes_count(wxGLCanvas* canvas);
static void reset_volumes(wxGLCanvas* canvas);
static void deselect_volumes(wxGLCanvas* canvas);
static void select_volume(wxGLCanvas* canvas, unsigned int id);
static void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
static int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config);
static bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
static bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
static void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
static void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
static void set_print(wxGLCanvas* canvas, Print* print);
static void set_model(wxGLCanvas* canvas, Model* model);
static void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape);
static void set_auto_bed_shape(wxGLCanvas* canvas);
static BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas);
static void set_axes_length(wxGLCanvas* canvas, float length);
static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
static void set_color_by(wxGLCanvas* canvas, const std::string& value);
static void set_select_by(wxGLCanvas* canvas, const std::string& value);
static void set_drag_by(wxGLCanvas* canvas, const std::string& value);
static std::string get_select_by(wxGLCanvas* canvas);
static bool is_layers_editing_enabled(wxGLCanvas* canvas);
static bool is_layers_editing_allowed(wxGLCanvas* canvas);
static bool is_shader_enabled(wxGLCanvas* canvas);
static bool is_reload_delayed(wxGLCanvas* canvas);
static void enable_layers_editing(wxGLCanvas* canvas, bool enable);
static void enable_warning_texture(wxGLCanvas* canvas, bool enable);
static void enable_legend_texture(wxGLCanvas* canvas, bool enable);
static void enable_picking(wxGLCanvas* canvas, bool enable);
static void enable_moving(wxGLCanvas* canvas, bool enable);
static void enable_gizmos(wxGLCanvas* canvas, bool enable);
static void enable_toolbar(wxGLCanvas* canvas, bool enable);
static void enable_shader(wxGLCanvas* canvas, bool enable);
static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
static void enable_dynamic_background(wxGLCanvas* canvas, bool enable);
static void allow_multisample(wxGLCanvas* canvas, bool allow);
static void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable);
static bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name);
static void zoom_to_bed(wxGLCanvas* canvas);
static void zoom_to_volumes(wxGLCanvas* canvas);
static void select_view(wxGLCanvas* canvas, const std::string& direction);
static void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
static void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
static void update_gizmos_data(wxGLCanvas* canvas);
static void render(wxGLCanvas* canvas);
static std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only);
static void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
static void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback);
static void register_on_double_click_callback(wxGLCanvas* canvas, void* callback);
static void register_on_right_click_callback(wxGLCanvas* canvas, void* callback);
static void register_on_select_object_callback(wxGLCanvas* canvas, void* callback);
static void register_on_model_update_callback(wxGLCanvas* canvas, void* callback);
static void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback);
static void register_on_arrange_callback(wxGLCanvas* canvas, void* callback);
static void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback);
static void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback);
static void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback);
static void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback);
static void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback);
static void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback);
static void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback);
static void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback);
static void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback);
static void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback);
static void register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback);
static void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback);
static void register_action_add_callback(wxGLCanvas* canvas, void* callback);
static void register_action_delete_callback(wxGLCanvas* canvas, void* callback);
static void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback);
static void register_action_arrange_callback(wxGLCanvas* canvas, void* callback);
static void register_action_more_callback(wxGLCanvas* canvas, void* callback);
static void register_action_fewer_callback(wxGLCanvas* canvas, void* callback);
static void register_action_split_callback(wxGLCanvas* canvas, void* callback);
static void register_action_cut_callback(wxGLCanvas* canvas, void* callback);
static void register_action_settings_callback(wxGLCanvas* canvas, void* callback);
static void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback);
static void register_action_selectbyparts_callback(wxGLCanvas* canvas, void* callback);
static std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
static std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
static int get_first_volume_id(wxGLCanvas* canvas, int obj_idx);
static int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx);
static void reload_scene(wxGLCanvas* canvas, bool force);
static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
static void load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
static void reset_legend_texture();
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
};
}
#endif

View file

@ -0,0 +1,136 @@
#include "AboutDialog.hpp"
#include "../../libslic3r/Utils.hpp"
namespace Slic3r {
namespace GUI {
AboutDialogLogo::AboutDialogLogo(wxWindow* parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
this->SetBackgroundColour(*wxWHITE);
this->logo = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG);
this->SetMinSize(this->logo.GetSize());
this->Bind(wxEVT_PAINT, &AboutDialogLogo::onRepaint, this);
}
void AboutDialogLogo::onRepaint(wxEvent &event)
{
wxPaintDC dc(this);
dc.SetBackgroundMode(wxTRANSPARENT);
wxSize size = this->GetSize();
int logo_w = this->logo.GetWidth();
int logo_h = this->logo.GetHeight();
dc.DrawBitmap(this->logo, (size.GetWidth() - logo_w)/2, (size.GetHeight() - logo_h)/2, true);
event.Skip();
}
AboutDialog::AboutDialog()
: wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxDefaultSize, wxCAPTION)
{
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
SetBackgroundColour(bgr_clr);
wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20);
// logo
wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG);
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp));
hsizer->Add(logo, 1, wxALIGN_CENTRE_VERTICAL | wxEXPAND | wxTOP | wxBOTTOM, 35);
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
#ifdef __WXMSW__
int proportion = 2;
#else
int proportion = 3;
#endif
hsizer->Add(vsizer, proportion, wxEXPAND|wxLEFT, 20);
// title
{
wxStaticText* title = new wxStaticText(this, wxID_ANY, "Slic3r Prusa Edition", wxDefaultPosition, wxDefaultSize);
wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
title_font.SetWeight(wxFONTWEIGHT_BOLD);
title_font.SetFamily(wxFONTFAMILY_ROMAN);
title_font.SetPointSize(24);
title->SetFont(title_font);
vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 10);
}
// version
{
auto version_string = _(L("Version"))+ " " + std::string(SLIC3R_VERSION);
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMSW__
version_font.SetPointSize(9);
#else
version_font.SetPointSize(11);
#endif
version->SetFont(version_font);
vsizer->Add(version, 0, wxALIGN_LEFT | wxBOTTOM, 10);
}
// text
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/);
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const int fs = font.GetPointSize()-1;
int size[] = {fs,fs,fs,fs,fs,fs,fs};
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
html->SetBorders(2);
const auto text = wxString::Format(
"<html>"
"<body bgcolor= %s link= %s>"
"<font color=%s>"
"Copyright &copy; 2016-2018 Prusa Research. <br />"
"Copyright &copy; 2011-2017 Alessandro Ranellucci. <br />"
"<a href=\"http://slic3r.org/\">Slic3r</a> is licensed under the "
"<a href=\"http://www.gnu.org/licenses/agpl-3.0.html\">GNU Affero General Public License, version 3</a>."
"<br /><br />"
"Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. "
"Manual by Gary Hodgson. Inspired by the RepRap community. <br />"
"Slic3r logo designed by Corey Daniels, <a href=\"http://www.famfamfam.com/lab/icons/silk/\">Silk Icon Set</a> designed by Mark James. "
"</font>"
"</body>"
"</html>", bgr_clr_str, text_clr_str, text_clr_str);
html->SetPage(text);
vsizer->Add(html, 1, wxEXPAND | wxBOTTOM, 10);
html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this);
}
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE);
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
this->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
logo->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
}
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
event.Skip(false);
}
void AboutDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
this->Close();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,36 @@
#ifndef slic3r_GUI_AboutDialog_hpp_
#define slic3r_GUI_AboutDialog_hpp_
#include "GUI.hpp"
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/html/htmlwin.h>
namespace Slic3r {
namespace GUI {
class AboutDialogLogo : public wxPanel
{
public:
AboutDialogLogo(wxWindow* parent);
private:
wxBitmap logo;
void onRepaint(wxEvent &event);
};
class AboutDialog : public wxDialog
{
public:
AboutDialog();
private:
void onLinkClicked(wxHtmlLinkEvent &event);
void onCloseDialog(wxEvent &);
};
} // namespace GUI
} // namespace Slic3r
#endif

View file

@ -0,0 +1,266 @@
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Utils.hpp"
#include "AppConfig.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utility>
#include <assert.h>
#include <vector>
#include <stdexcept>
#include <boost/filesystem.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/algorithm/string/predicate.hpp>
namespace Slic3r {
static const std::string VENDOR_PREFIX = "vendor:";
static const std::string MODEL_PREFIX = "model:";
static const std::string VERSION_CHECK_URL = "https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/Slic3rPE.version";
void AppConfig::reset()
{
m_storage.clear();
set_defaults();
};
// Override missing or keys with their defaults.
void AppConfig::set_defaults()
{
// Reset the empty fields to defaults.
if (get("autocenter").empty())
set("autocenter", "0");
// Disable background processing by default as it is not stable.
if (get("background_processing").empty())
set("background_processing", "0");
// If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
// By default, Prusa has the controller hidden.
if (get("no_controller").empty())
set("no_controller", "1");
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
if (get("no_defaults").empty())
set("no_defaults", "1");
if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0");
if (get("version_check").empty())
set("version_check", "1");
if (get("preset_update").empty())
set("preset_update", "1");
// Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers.
// https://github.com/prusa3d/Slic3r/issues/233
if (get("use_legacy_opengl").empty())
set("use_legacy_opengl", "0");
if (get("remember_output_path").empty())
set("remember_output_path", "1");
// Remove legacy window positions/sizes
erase("", "main_frame_maximized");
erase("", "main_frame_pos");
erase("", "main_frame_size");
erase("", "object_settings_maximized");
erase("", "object_settings_pos");
erase("", "object_settings_size");
}
void AppConfig::load()
{
// 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree;
pt::ptree tree;
boost::nowide::ifstream ifs(AppConfig::config_path());
pt::read_ini(ifs, tree);
// 2) Parse the property_tree, extract the sections and key / value pairs.
for (const auto &section : tree) {
if (section.second.empty()) {
// This may be a top level (no section) entry, or an empty section.
std::string data = section.second.data();
if (! data.empty())
// If there is a non-empty data, then it must be a top-level (without a section) config entry.
m_storage[""][section.first] = data;
} else if (boost::starts_with(section.first, VENDOR_PREFIX)) {
// This is a vendor section listing enabled model / variants
const auto vendor_name = section.first.substr(VENDOR_PREFIX.size());
auto &vendor = m_vendors[vendor_name];
for (const auto &kvp : section.second) {
if (! boost::starts_with(kvp.first, MODEL_PREFIX)) { continue; }
const auto model_name = kvp.first.substr(MODEL_PREFIX.size());
std::vector<std::string> variants;
if (! unescape_strings_cstyle(kvp.second.data(), variants)) { continue; }
for (const auto &variant : variants) {
vendor[model_name].insert(variant);
}
}
} else {
// This must be a section name. Read the entries of a section.
std::map<std::string, std::string> &storage = m_storage[section.first];
for (auto &kvp : section.second)
storage[kvp.first] = kvp.second.data();
}
}
// Figure out if datadir has legacy presets
auto ini_ver = Semver::parse(get("version"));
m_legacy_datadir = false;
if (ini_ver) {
m_orig_version = *ini_ver;
// Make 1.40.0 alphas compare well
ini_ver->set_metadata(boost::none);
ini_ver->set_prerelease(boost::none);
m_legacy_datadir = ini_ver < Semver(1, 40, 0);
}
// Override missing or keys with their defaults.
this->set_defaults();
m_dirty = false;
}
void AppConfig::save()
{
boost::nowide::ofstream c;
c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc);
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
// Make sure the "no" category is written first.
for (const std::pair<std::string, std::string> &kvp : m_storage[""])
c << kvp.first << " = " << kvp.second << std::endl;
// Write the other categories.
for (const auto category : m_storage) {
if (category.first.empty())
continue;
c << std::endl << "[" << category.first << "]" << std::endl;
for (const std::pair<std::string, std::string> &kvp : category.second)
c << kvp.first << " = " << kvp.second << std::endl;
}
// Write vendor sections
for (const auto &vendor : m_vendors) {
size_t size_sum = 0;
for (const auto &model : vendor.second) { size_sum += model.second.size(); }
if (size_sum == 0) { continue; }
c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
for (const auto &model : vendor.second) {
if (model.second.size() == 0) { continue; }
const std::vector<std::string> variants(model.second.begin(), model.second.end());
const auto escaped = escape_strings_cstyle(variants);
c << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
}
}
c.close();
m_dirty = false;
}
bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const
{
const auto it_v = m_vendors.find(vendor);
if (it_v == m_vendors.end()) { return false; }
const auto it_m = it_v->second.find(model);
return it_m == it_v->second.end() ? false : it_m->second.find(variant) != it_m->second.end();
}
void AppConfig::set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable)
{
if (enable) {
if (get_variant(vendor, model, variant)) { return; }
m_vendors[vendor][model].insert(variant);
} else {
auto it_v = m_vendors.find(vendor);
if (it_v == m_vendors.end()) { return; }
auto it_m = it_v->second.find(model);
if (it_m == it_v->second.end()) { return; }
auto it_var = it_m->second.find(variant);
if (it_var == it_m->second.end()) { return; }
it_m->second.erase(it_var);
}
// If we got here, there was an update
m_dirty = true;
}
void AppConfig::set_vendors(const AppConfig &from)
{
m_vendors = from.m_vendors;
m_dirty = true;
}
std::string AppConfig::get_last_dir() const
{
const auto it = m_storage.find("recent");
if (it != m_storage.end()) {
{
const auto it2 = it->second.find("skein_directory");
if (it2 != it->second.end() && ! it2->second.empty())
return it2->second;
}
{
const auto it2 = it->second.find("config_directory");
if (it2 != it->second.end() && ! it2->second.empty())
return it2->second;
}
}
return std::string();
}
void AppConfig::update_config_dir(const std::string &dir)
{
this->set("recent", "config_directory", dir);
}
void AppConfig::update_skein_dir(const std::string &dir)
{
this->set("recent", "skein_directory", dir);
}
std::string AppConfig::get_last_output_dir(const std::string &alt) const
{
const auto it = m_storage.find("");
if (it != m_storage.end()) {
const auto it2 = it->second.find("last_output_path");
const auto it3 = it->second.find("remember_output_path");
if (it2 != it->second.end() && it3 != it->second.end() && ! it2->second.empty() && it3->second == "1")
return it2->second;
}
return alt;
}
void AppConfig::update_last_output_dir(const std::string &dir)
{
this->set("", "last_output_path", dir);
}
void AppConfig::reset_selections()
{
auto it = m_storage.find("presets");
if (it != m_storage.end()) {
it->second.erase("print");
it->second.erase("filament");
it->second.erase("sla_material");
it->second.erase("printer");
m_dirty = true;
}
}
std::string AppConfig::config_path()
{
return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string();
}
std::string AppConfig::version_check_url() const
{
auto from_settings = get("version_check_url");
return from_settings.empty() ? VERSION_CHECK_URL : from_settings;
}
bool AppConfig::exists()
{
return boost::filesystem::exists(AppConfig::config_path());
}
}; // namespace Slic3r

View file

@ -0,0 +1,140 @@
#ifndef slic3r_AppConfig_hpp_
#define slic3r_AppConfig_hpp_
#include <set>
#include <map>
#include <string>
#include "libslic3r/Config.hpp"
#include "slic3r/Utils/Semver.hpp"
namespace Slic3r {
class AppConfig
{
public:
AppConfig() :
m_dirty(false),
m_orig_version(Semver::invalid()),
m_legacy_datadir(false)
{
this->reset();
}
// Clear and reset to defaults.
void reset();
// Override missing or keys with their defaults.
void set_defaults();
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
void load();
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
void save();
// Does this config need to be saved?
bool dirty() const { return m_dirty; }
// Const accessor, it will return false if a section or a key does not exist.
bool get(const std::string &section, const std::string &key, std::string &value) const
{
value.clear();
auto it = m_storage.find(section);
if (it == m_storage.end())
return false;
auto it2 = it->second.find(key);
if (it2 == it->second.end())
return false;
value = it2->second;
return true;
}
std::string get(const std::string &section, const std::string &key) const
{ std::string value; this->get(section, key, value); return value; }
std::string get(const std::string &key) const
{ std::string value; this->get("", key, value); return value; }
void set(const std::string &section, const std::string &key, const std::string &value)
{
std::string &old = m_storage[section][key];
if (old != value) {
old = value;
m_dirty = true;
}
}
void set(const std::string &key, const std::string &value)
{ this->set("", key, value); }
bool has(const std::string &section, const std::string &key) const
{
auto it = m_storage.find(section);
if (it == m_storage.end())
return false;
auto it2 = it->second.find(key);
return it2 != it->second.end() && ! it2->second.empty();
}
bool has(const std::string &key) const
{ return this->has("", key); }
void erase(const std::string &section, const std::string &key)
{
auto it = m_storage.find(section);
if (it != m_storage.end()) {
it->second.erase(key);
}
}
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
typedef std::map<std::string, std::map<std::string, std::set<std::string>>> VendorMap;
bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const;
void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable);
void set_vendors(const AppConfig &from);
void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; }
void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
const VendorMap& vendors() const { return m_vendors; }
// return recent/skein_directory or recent/config_directory or empty string.
std::string get_last_dir() const;
void update_config_dir(const std::string &dir);
void update_skein_dir(const std::string &dir);
std::string get_last_output_dir(const std::string &alt) const;
void update_last_output_dir(const std::string &dir);
// reset the current print / filament / printer selections, so that
// the PresetBundle::load_selections(const AppConfig &config) call will select
// the first non-default preset when called.
void reset_selections();
// Get the default config path from Slic3r::data_dir().
static std::string config_path();
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
bool legacy_datadir() const { return m_legacy_datadir; }
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
// Get the Slic3r version check url.
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
std::string version_check_url() const;
// Returns the original Slic3r version found in the ini file before it was overwritten
// by the current version
Semver orig_version() const { return m_orig_version; }
// Does the config file exist?
static bool exists();
private:
// Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage;
// Map of enabled vendors / models / variants
VendorMap m_vendors;
// Has any value been modified since the config.ini has been last saved or loaded?
bool m_dirty;
// Original version found in the ini file before it was overwritten
Semver m_orig_version;
// Whether the existing version is before system profiles & configuration updating
bool m_legacy_datadir;
};
}; // namespace Slic3r
#endif /* slic3r_AppConfig_hpp_ */

View file

@ -0,0 +1,167 @@
#include "BackgroundSlicingProcess.hpp"
#include "GUI.hpp"
#include <wx/event.h>
#include <wx/panel.h>
#include <wx/stdpaths.h>
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
#include "../../libslic3r/Print.hpp"
#include "../../libslic3r/Utils.hpp"
#include "../../libslic3r/GCode/PostProcessor.hpp"
//#undef NDEBUG
#include <cassert>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/nowide/cstdio.hpp>
namespace Slic3r {
namespace GUI {
extern wxPanel *g_wxPlater;
};
BackgroundSlicingProcess::BackgroundSlicingProcess()
{
m_temp_output_path = wxStandardPaths::Get().GetTempDir().utf8_str().data();
m_temp_output_path += (boost::format(".%1%.gcode") % get_current_pid()).str();
}
BackgroundSlicingProcess::~BackgroundSlicingProcess()
{
this->stop();
this->join_background_thread();
boost::nowide::remove(m_temp_output_path.c_str());
}
void BackgroundSlicingProcess::thread_proc()
{
std::unique_lock<std::mutex> lck(m_mutex);
// Let the caller know we are ready to run the background processing task.
m_state = STATE_IDLE;
lck.unlock();
m_condition.notify_one();
for (;;) {
assert(m_state == STATE_IDLE || m_state == STATE_CANCELED || m_state == STATE_FINISHED);
// 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()) {
wxQueueEvent(GUI::g_wxPlater, new wxCommandEvent(m_event_sliced_id));
m_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
if (! m_print->canceled() && ! m_output_path.empty()) {
if (copy_file(m_temp_output_path, m_output_path) != 0)
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
m_print->set_status(95, "Running post-processing scripts");
run_post_process_scripts(m_output_path, m_print->config());
}
}
} catch (CanceledException &ex) {
// Canceled, this is all right.
assert(m_print->canceled());
} 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());
m_print->restart();
lck.unlock();
// Let the UI thread wake up if it is waiting for the background task to finish.
m_condition.notify_one();
// Let the UI thread see the result.
}
m_state = STATE_EXITED;
lck.unlock();
// End of the background processing thread. The UI thread should join m_thread now.
}
void BackgroundSlicingProcess::join_background_thread()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// Worker thread has not been started yet.
assert(! m_thread.joinable());
} else {
assert(m_state == STATE_IDLE);
assert(m_thread.joinable());
// Notify the worker thread to exit.
m_state = STATE_EXIT;
lck.unlock();
m_condition.notify_one();
// Wait until the worker thread exits.
m_thread.join();
}
}
bool BackgroundSlicingProcess::start()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// The worker thread is not running yet. Start it.
assert(! m_thread.joinable());
m_thread = std::thread([this]{this->thread_proc();});
// Wait until the worker thread is ready to execute the background processing task.
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
}
assert(m_state == STATE_IDLE || this->running());
if (this->running())
// The background processing thread is already running.
return false;
if (! this->idle())
throw std::runtime_error("Cannot start a background task, the worker thread is not idle.");
m_state = STATE_STARTED;
lck.unlock();
m_condition.notify_one();
return true;
}
bool BackgroundSlicingProcess::stop()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
this->m_output_path.clear();
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.
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
// In the "Canceled" state. Reset the state to "Idle".
m_state = STATE_IDLE;
} else if (m_state == STATE_FINISHED || m_state == STATE_CANCELED) {
// In the "Finished" or "Canceled" state. Reset the state to "Idle".
m_state = STATE_IDLE;
}
this->m_output_path.clear();
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(const DynamicPrintConfig &config)
{
this->stop();
bool invalidated = m_print->apply_config(config);
return invalidated;
}
}; // namespace Slic3r

View file

@ -0,0 +1,91 @@
#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_
#define slic3r_GUI_BackgroundSlicingProcess_hpp_
#include <string>
#include <condition_variable>
#include <mutex>
#include <thread>
namespace Slic3r {
class DynamicPrintConfig;
class GCodePreviewData;
class Print;
// Support for the GUI background processing (Slicing and G-code generation).
// As of now this class is not declared in Slic3r::GUI due to the Perl bindings limits.
class BackgroundSlicingProcess
{
public:
BackgroundSlicingProcess();
// Stop the background processing and finalize the bacgkround processing thread, remove temp files.
~BackgroundSlicingProcess();
void set_print(Print *print) { m_print = print; }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
// and the background processing will transition into G-code export.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_sliced_event(int event_id) { m_event_sliced_id = event_id; }
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the G-code export is finished.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
// Set the output path of the G-code.
void set_output_path(const std::string &path) { m_output_path = path; }
// 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(const 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 join_background_thread();
Print *m_print = nullptr;
// Data structure, to which the G-code export writes its annotations.
GCodePreviewData *m_gcode_preview_data = nullptr;
std::string m_temp_output_path;
std::string m_output_path;
// Thread, on which the background processing is executed. The thread will always be present
// and ready to execute the slicing process.
std::thread m_thread;
// Mutex and condition variable to synchronize m_thread with the UI thread.
std::mutex m_mutex;
std::condition_variable m_condition;
State m_state = STATE_INITIAL;
// wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue.
int m_event_sliced_id = 0;
// wxWidgets command ID to be sent to the platter to inform that the task finished.
int m_event_finished_id = 0;
};
}; // namespace Slic3r
#endif /* slic3r_GUI_BackgroundSlicingProcess_hpp_ */

View file

@ -0,0 +1,343 @@
#include "BedShapeDialog.hpp"
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/wx.h>
#include "Polygon.hpp"
#include "BoundingBox.hpp"
#include <wx/numformatter.h>
#include "Model.hpp"
#include "boost/nowide/iostream.hpp"
#include <algorithm>
namespace Slic3r {
namespace GUI {
void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt)
{
m_panel = new BedShapePanel(this);
m_panel->build_panel(default_pt);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_panel, 1, wxEXPAND);
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(main_sizer);
SetMinSize(GetSize());
main_sizer->SetSizeHints(this);
// needed to actually free memory
this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent e){
EndModal(wxID_OK);
Destroy();
}));
}
void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
{
// on_change(nullptr);
auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape")));
auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL);
// shape options
m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(300, -1), wxCHB_TOP);
sbsizer->Add(m_shape_options_book);
auto optgroup = init_shape_options_page(_(L("Rectangular")));
ConfigOptionDef def;
def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Vec2d(200, 200) };
def.label = L("Size");
def.tooltip = L("Size in X and Y of the rectangular plate.");
Option option(def, "rect_size");
optgroup->append_single_option_line(option);
def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Vec2d(0, 0) };
def.label = L("Origin");
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
option = Option(def, "rect_origin");
optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_(L("Circular")));
def.type = coFloat;
def.default_value = new ConfigOptionFloat(200);
def.sidetext = L("mm");
def.label = L("Diameter");
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
option = Option(def, "diameter");
optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_(L("Custom")));
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e)
{
load_stl();
}));
return sizer;
};
optgroup->append_line(line);
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e)
{
update_shape();
}));
// right pane with preview canvas
m_canvas = new Bed_2D(this);
m_canvas->m_bed_shape = default_pt->values;
// main sizer
auto top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10);
if (m_canvas)
top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ;
SetSizerAndFit(top_sizer);
set_shape(default_pt);
update_preview();
}
#define SHAPE_RECTANGULAR 0
#define SHAPE_CIRCULAR 1
#define SHAPE_CUSTOM 2
// Called from the constructor.
// Create a panel for a rectangular / circular / custom bed shape.
ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title){
auto panel = new wxPanel(m_shape_options_book);
ConfigOptionsGroupShp optgroup;
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
optgroup->label_width = 100;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
update_shape();
};
m_optgroups.push_back(optgroup);
panel->SetSizerAndFit(optgroup->sizer);
m_shape_options_book->AddPage(panel, title);
return optgroup;
}
// Called from the constructor.
// Set the initial bed shape from a list of points.
// Deduce the bed shape type(rect, circle, custom)
// This routine shall be smart enough if the user messes up
// with the list of points in the ini file directly.
void BedShapePanel::set_shape(ConfigOptionPoints* points)
{
auto polygon = Polygon::new_scale(points->values);
// is this a rectangle ?
if (points->size() == 4) {
auto lines = polygon.lines();
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
// okay, it's a rectangle
// find origin
coordf_t x_min, x_max, y_min, y_max;
x_max = x_min = points->values[0](0);
y_max = y_min = points->values[0](1);
for (auto pt : points->values)
{
x_min = std::min(x_min, pt(0));
x_max = std::max(x_max, pt(0));
y_min = std::min(y_min, pt(1));
y_max = std::max(y_max, pt(1));
}
auto origin = new ConfigOptionPoints{ Vec2d(-x_min, -y_min) };
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
optgroup->set_value("rect_origin", origin);
update_shape();
return;
}
}
// is this a circle ?
{
// Analyze the array of points.Do they reside on a circle ?
auto center = polygon.bounding_box().center();
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt: polygon.points)
{
double distance = (pt - center).cast<double>().norm();
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
bool defined_value = true;
for (auto el: vertex_distances)
{
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
defined_value = false;
break;
}
if (defined_value) {
// all vertices are equidistant to center
m_shape_options_book->SetSelection(SHAPE_CIRCULAR);
auto optgroup = m_optgroups[SHAPE_CIRCULAR];
boost::any ret = wxNumberFormatter::ToString(unscale<double>(avg_dist * 2), 0);
optgroup->set_value("diameter", ret);
update_shape();
return;
}
}
if (points->size() < 3) {
// Invalid polygon.Revert to default bed dimensions.
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) });
optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) });
update_shape();
return;
}
// This is a custom bed shape, use the polygon provided.
m_shape_options_book->SetSelection(SHAPE_CUSTOM);
// Copy the polygon to the canvas, make a copy of the array.
m_canvas->m_bed_shape = points->values;
update_shape();
}
void BedShapePanel::update_preview()
{
if (m_canvas) m_canvas->Refresh();
Refresh();
}
// Update the bed shape from the dialog fields.
void BedShapePanel::update_shape()
{
auto page_idx = m_shape_options_book->GetSelection();
if (page_idx == SHAPE_RECTANGULAR) {
Vec2d rect_size(Vec2d::Zero());
Vec2d rect_origin(Vec2d::Zero());
try{
rect_size = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
catch (const std::exception &e){
return;
}
try{
rect_origin = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
}
catch (const std::exception &e){
return;}
auto x = rect_size(0);
auto y = rect_size(1);
// empty strings or '-' or other things
if (x == 0 || y == 0) return;
double x0 = 0.0;
double y0 = 0.0;
double x1 = x;
double y1 = y;
auto dx = rect_origin(0);
auto dy = rect_origin(1);
x0 -= dx;
x1 -= dx;
y0 -= dy;
y1 -= dy;
m_canvas->m_bed_shape = { Vec2d(x0, y0),
Vec2d(x1, y0),
Vec2d(x1, y1),
Vec2d(x0, y1)};
}
else if(page_idx == SHAPE_CIRCULAR) {
double diameter;
try{
diameter = boost::any_cast<double>(m_optgroups[SHAPE_CIRCULAR]->get_value("diameter"));
}
catch (const std::exception &e){
return;
}
if (diameter == 0.0) return ;
auto r = diameter / 2;
auto twopi = 2 * PI;
auto edges = 60;
std::vector<Vec2d> points;
for (size_t i = 1; i <= 60; ++i){
auto angle = i * twopi / edges;
points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
}
m_canvas->m_bed_shape = points;
}
// $self->{on_change}->();
update_preview();
}
// Loads an stl file, projects it to the XY plane and calculates a polygon.
void BedShapePanel::load_stl()
{
t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card();
std::vector<std::string> file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" };
wxString MODEL_WILDCARD;
for (auto file_type: file_types)
MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|";
auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "",
MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog->ShowModal() != wxID_OK) {
dialog->Destroy();
return;
}
wxArrayString input_file;
dialog->GetPaths(input_file);
dialog->Destroy();
std::string file_name = input_file[0].ToStdString();
Model model;
try {
model = Model::read_from_file(file_name);
}
catch (std::exception &e) {
auto msg = _(L("Error! ")) + file_name + " : " + e.what() + ".";
show_error(this, msg);
exit(1);
}
auto mesh = model.mesh();
auto expolygons = mesh.horizontal_projection();
if (expolygons.size() == 0) {
show_error(this, _(L("The selected file contains no geometry.")));
return;
}
if (expolygons.size() > 1) {
show_error(this, _(L("The selected file contains several disjoint areas. This is not supported.")));
return;
}
auto polygon = expolygons[0].contour;
std::vector<Vec2d> points;
for (auto pt : polygon.points)
points.push_back(unscale(pt));
m_canvas->m_bed_shape = points;
update_preview();
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,56 @@
#ifndef slic3r_BedShapeDialog_hpp_
#define slic3r_BedShapeDialog_hpp_
// The bed shape dialog.
// The dialog opens from Print Settins tab->Bed Shape : Set...
#include "OptionsGroup.hpp"
#include "2DBed.hpp"
#include <wx/dialog.h>
#include <wx/choicebk.h>
namespace Slic3r {
namespace GUI {
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
class BedShapePanel : public wxPanel
{
wxChoicebook* m_shape_options_book;
Bed_2D* m_canvas;
std::vector <ConfigOptionsGroupShp> m_optgroups;
public:
BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY){}
~BedShapePanel(){}
void build_panel(ConfigOptionPoints* default_pt);
ConfigOptionsGroupShp init_shape_options_page(wxString title);
void set_shape(ConfigOptionPoints* points);
void update_preview();
void update_shape();
void load_stl();
// Returns the resulting bed shape polygon. This value will be stored to the ini file.
std::vector<Vec2d> GetValue() { return m_canvas->m_bed_shape; }
};
class BedShapeDialog : public wxDialog
{
BedShapePanel* m_panel;
public:
BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _(L("Bed Shape")),
wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER){}
~BedShapeDialog(){ }
void build_dialog(ConfigOptionPoints* default_pt);
std::vector<Vec2d> GetValue() { return m_panel->GetValue(); }
};
} // GUI
} // Slic3r
#endif /* slic3r_BedShapeDialog_hpp_ */

View file

@ -0,0 +1,172 @@
#include "BitmapCache.hpp"
#if ! defined(WIN32) && ! defined(__APPLE__)
#define BROKEN_ALPHA
#endif
#ifdef BROKEN_ALPHA
#include <wx/mstream.h>
#include <wx/rawbmp.h>
#endif /* BROKEN_ALPHA */
namespace Slic3r { namespace GUI {
void BitmapCache::clear()
{
for (std::pair<const std::string, wxBitmap*> &bitmap : m_map)
delete bitmap.second;
}
static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image)
{
#ifdef BROKEN_ALPHA
wxMemoryOutputStream stream;
image.SaveFile(stream, wxBITMAP_TYPE_PNG);
wxStreamBuffer *buf = stream.GetOutputStreamBuffer();
return wxBitmap::NewFromPNGData(buf->GetBufferStart(), buf->GetBufferSize());
#else
return wxBitmap(std::move(image));
#endif
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height)
{
wxBitmap *bitmap = nullptr;
auto it = m_map.find(bitmap_key);
if (it == m_map.end()) {
bitmap = new wxBitmap(width, height);
m_map[bitmap_key] = bitmap;
} else {
bitmap = it->second;
if (bitmap->GetWidth() != width || bitmap->GetHeight() != height)
bitmap->Create(width, height);
}
#ifndef BROKEN_ALPHA
bitmap->UseAlpha();
#endif
return bitmap;
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp)
{
wxBitmap *bitmap = nullptr;
auto it = m_map.find(bitmap_key);
if (it == m_map.end()) {
bitmap = new wxBitmap(bmp);
m_map[bitmap_key] = bitmap;
} else {
bitmap = it->second;
*bitmap = bmp;
}
return bitmap;
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2)
{
// Copying the wxBitmaps is cheap as the bitmap's content is reference counted.
const wxBitmap bmps[2] = { bmp, bmp2 };
return this->insert(bitmap_key, bmps, bmps + 2);
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3)
{
// Copying the wxBitmaps is cheap as the bitmap's content is reference counted.
const wxBitmap bmps[3] = { bmp, bmp2, bmp3 };
return this->insert(bitmap_key, bmps, bmps + 3);
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end)
{
size_t width = 0;
size_t height = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
width += bmp->GetWidth();
height = std::max<size_t>(height, bmp->GetHeight());
}
#ifdef BROKEN_ALPHA
wxImage image(width, height);
image.InitAlpha();
// Fill in with a white color.
memset(image.GetData(), 0x0ff, width * height * 3);
// Fill in with full transparency.
memset(image.GetAlpha(), 0, width * height);
size_t x = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
if (bmp->GetWidth() > 0) {
if (bmp->GetDepth() == 32) {
wxAlphaPixelData data(*const_cast<wxBitmap*>(bmp));
data.UseAlpha();
if (data) {
for (int r = 0; r < bmp->GetHeight(); ++ r) {
wxAlphaPixelData::Iterator src(data);
src.Offset(data, 0, r);
unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3;
unsigned char *dst_alpha = image.GetAlpha() + x + r * width;
for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) {
*dst_pixels ++ = src.Red();
*dst_pixels ++ = src.Green();
*dst_pixels ++ = src.Blue();
*dst_alpha ++ = src.Alpha();
}
}
}
} else if (bmp->GetDepth() == 24) {
wxNativePixelData data(*const_cast<wxBitmap*>(bmp));
if (data) {
for (int r = 0; r < bmp->GetHeight(); ++ r) {
wxNativePixelData::Iterator src(data);
src.Offset(data, 0, r);
unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3;
unsigned char *dst_alpha = image.GetAlpha() + x + r * width;
for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) {
*dst_pixels ++ = src.Red();
*dst_pixels ++ = src.Green();
*dst_pixels ++ = src.Blue();
*dst_alpha ++ = wxALPHA_OPAQUE;
}
}
}
}
}
x += bmp->GetWidth();
}
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
#else
wxBitmap *bitmap = this->insert(bitmap_key, width, height);
wxMemoryDC memDC;
memDC.SelectObject(*bitmap);
memDC.SetBackground(*wxTRANSPARENT_BRUSH);
memDC.Clear();
size_t x = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
if (bmp->GetWidth() > 0)
memDC.DrawBitmap(*bmp, x, 0, true);
x += bmp->GetWidth();
}
memDC.SelectObject(wxNullBitmap);
return bitmap;
#endif
}
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency)
{
wxImage image(width, height);
image.InitAlpha();
unsigned char* imgdata = image.GetData();
unsigned char* imgalpha = image.GetAlpha();
for (size_t i = 0; i < width * height; ++ i) {
*imgdata ++ = r;
*imgdata ++ = g;
*imgdata ++ = b;
*imgalpha ++ = transparency;
}
return wxImage_to_wxBitmap_with_alpha(std::move(image));
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,44 @@
#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP
#define SLIC3R_GUI_BITMAP_CACHE_HPP
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Config.hpp"
#include "GUI.hpp"
namespace Slic3r { namespace GUI {
class BitmapCache
{
public:
BitmapCache() {}
~BitmapCache() { clear(); }
void clear();
wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; }
const wxBitmap* find(const std::string &name) const { return const_cast<BitmapCache*>(this)->find(name); }
wxBitmap* insert(const std::string &name, size_t width, size_t height);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3);
wxBitmap* insert(const std::string &name, const std::vector<wxBitmap> &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); }
wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end);
static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency);
static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }
static wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); }
private:
std::map<std::string, wxBitmap*> m_map;
};
} // GUI
} // Slic3r
#endif /* SLIC3R_GUI_BITMAP_CACHE_HPP */

View file

@ -0,0 +1,200 @@
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
#include "BonjourDialog.hpp"
#include <set>
#include <mutex>
#include <wx/sizer.h>
#include <wx/button.h>
#include <wx/listctrl.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/Utils/Bonjour.hpp"
namespace Slic3r {
struct BonjourReplyEvent : public wxEvent
{
BonjourReply reply;
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
wxEvent(winid, eventType),
reply(std::move(reply))
{}
virtual wxEvent *Clone() const
{
return new BonjourReplyEvent(*this);
}
};
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
class ReplySet: public std::set<BonjourReply> {};
struct LifetimeGuard
{
std::mutex mutex;
BonjourDialog *dialog;
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
};
BonjourDialog::BonjourDialog(wxWindow *parent) :
wxDialog(parent, wxID_ANY, _(L("Network lookup"))),
list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxSize(800, 300))),
replies(new ReplySet),
label(new wxStaticText(this, wxID_ANY, "")),
timer(new wxTimer()),
timer_state(0)
{
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
list->SetSingleStyle(wxLC_SINGLE_SEL);
list->SetSingleStyle(wxLC_SORT_DESCENDING);
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 50);
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 100);
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 200);
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 50);
vsizer->Add(list, 1, wxEXPAND | wxALL, 10);
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, 10);
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10);
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
SetSizerAndFit(vsizer);
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
this->timer_state = 0;
});
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
}
BonjourDialog::~BonjourDialog()
{
// Needed bacuse of forward defs
}
bool BonjourDialog::show_and_lookup()
{
Show(); // Because we need GetId() to work before ShowModal()
timer->Stop();
timer->SetOwner(this);
timer_state = 1;
timer->Start(1000);
wxTimerEvent evt_dummy;
on_timer(evt_dummy);
// The background thread needs to queue messages for this dialog
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
// Here we put the pointer under a shared_ptr and protect it by a mutex,
// so that both threads can access it safely.
auto dguard = std::make_shared<LifetimeGuard>(this);
bonjour = std::move(Bonjour("octoprint")
.set_retries(3)
.set_timeout(4)
.on_reply([dguard](BonjourReply &&reply) {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
wxQueueEvent(dialog, evt);
}
})
.on_complete([dguard]() {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
wxQueueEvent(dialog, evt);
}
})
.lookup()
);
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
{
// Tell the background thread the dialog is going away...
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
dguard->dialog = nullptr;
}
return res;
}
wxString BonjourDialog::get_selected() const
{
auto sel = list->GetFirstSelected();
return sel >= 0 ? list->GetItemText(sel) : wxString();
}
// Private
void BonjourDialog::on_reply(BonjourReplyEvent &e)
{
if (replies->find(e.reply) != replies->end()) {
// We already have this reply
return;
}
replies->insert(std::move(e.reply));
auto selected = get_selected();
list->DeleteAllItems();
// The whole list is recreated so that we benefit from it already being sorted in the set.
// (And also because wxListView's sorting API is bananas.)
for (const auto &reply : *replies) {
auto item = list->InsertItem(0, reply.full_address);
list->SetItem(item, 1, reply.hostname);
list->SetItem(item, 2, reply.service_name);
list->SetItem(item, 3, reply.version);
}
for (int i = 0; i < 4; i++) {
this->list->SetColumnWidth(i, wxLIST_AUTOSIZE);
if (this->list->GetColumnWidth(i) < 100) { this->list->SetColumnWidth(i, 100); }
}
if (!selected.IsEmpty()) {
// Attempt to preserve selection
auto hit = list->FindItem(-1, selected);
if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); }
}
}
void BonjourDialog::on_timer(wxTimerEvent &)
{
const auto search_str = _(L("Searching for devices"));
if (timer_state > 0) {
const std::string dots(timer_state, '.');
label->SetLabel(wxString::Format("%s %s", search_str, dots));
timer_state = (timer_state) % 3 + 1;
} else {
label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished"))+"."));
timer->Stop();
}
}
}

View file

@ -0,0 +1,49 @@
#ifndef slic3r_BonjourDialog_hpp_
#define slic3r_BonjourDialog_hpp_
#include <memory>
#include <wx/dialog.h>
class wxListView;
class wxStaticText;
class wxTimer;
class wxTimerEvent;
namespace Slic3r {
class Bonjour;
class BonjourReplyEvent;
class ReplySet;
class BonjourDialog: public wxDialog
{
public:
BonjourDialog(wxWindow *parent);
BonjourDialog(BonjourDialog &&) = delete;
BonjourDialog(const BonjourDialog &) = delete;
BonjourDialog &operator=(BonjourDialog &&) = delete;
BonjourDialog &operator=(const BonjourDialog &) = delete;
~BonjourDialog();
bool show_and_lookup();
wxString get_selected() const;
private:
wxListView *list;
std::unique_ptr<ReplySet> replies;
wxStaticText *label;
std::shared_ptr<Bonjour> bonjour;
std::unique_ptr<wxTimer> timer;
unsigned timer_state;
void on_reply(BonjourReplyEvent &);
void on_timer(wxTimerEvent &);
};
}
#endif

View file

@ -0,0 +1,84 @@
#include "ButtonsDescription.hpp"
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/statbmp.h>
#include <wx/clrpicker.h>
#include "GUI.hpp"
namespace Slic3r {
namespace GUI {
ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions) :
wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize),
m_icon_descriptions(icon_descriptions)
{
auto grid_sizer = new wxFlexGridSizer(3, 20, 20);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(grid_sizer, 0, wxEXPAND | wxALL, 20);
// Icon description
for (auto pair : *m_icon_descriptions)
{
auto icon = new wxStaticBitmap(this, wxID_ANY, *pair.first);
grid_sizer->Add(icon, -1, wxALIGN_CENTRE_VERTICAL);
std::istringstream f(pair.second);
std::string s;
getline(f, s, ';');
auto description = new wxStaticText(this, wxID_ANY, _(s));
grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL);
getline(f, s, ';');
description = new wxStaticText(this, wxID_ANY, _(s));
grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
}
// Text color description
auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value")));
sys_label->SetForegroundColour(get_label_clr_sys());
auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_sys());
sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([sys_colour, sys_label](wxCommandEvent e)
{
sys_label->SetForegroundColour(sys_colour->GetColour());
sys_label->Refresh();
}));
size_t t= 0;
while (t < 3){
grid_sizer->Add(new wxStaticText(this, wxID_ANY, ""), -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
++t;
}
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_colour, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
auto mod_label = new wxStaticText(this, wxID_ANY, _(L("Value was changed and is not equal to the system value or the last saved preset")));
mod_label->SetForegroundColour(get_label_clr_modified());
auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_modified());
mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([mod_colour, mod_label](wxCommandEvent e)
{
mod_label->SetForegroundColour(mod_colour->GetColour());
mod_label->Refresh();
}));
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_colour, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) {
set_label_clr_sys(sys_colour->GetColour());
set_label_clr_modified(mod_colour->GetColour());
EndModal(wxID_OK);
});
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,27 @@
#ifndef slic3r_ButtonsDescription_hpp
#define slic3r_ButtonsDescription_hpp
#include <wx/dialog.h>
#include <vector>
namespace Slic3r {
namespace GUI {
using t_icon_descriptions = std::vector<std::pair<wxBitmap*, std::string>>;
class ButtonsDescription : public wxDialog
{
t_icon_descriptions* m_icon_descriptions;
public:
ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions);
~ButtonsDescription(){}
};
} // GUI
} // Slic3r
#endif

View file

@ -0,0 +1,15 @@
#include <exception>
namespace Slic3r {
class ConfigError : public std::runtime_error {
using std::runtime_error::runtime_error;
};
namespace GUI {
class ConfigGUITypeError : public ConfigError {
using ConfigError::ConfigError;
};
}
}

View file

@ -0,0 +1,140 @@
#include "ConfigSnapshotDialog.hpp"
#include "../Config/Snapshot.hpp"
#include "../Utils/Time.hpp"
#include "../../libslic3r/Utils.hpp"
namespace Slic3r {
namespace GUI {
static wxString format_reason(const Config::Snapshot::Reason reason)
{
switch (reason) {
case Config::Snapshot::SNAPSHOT_UPGRADE:
return wxString(_(L("Upgrade")));
case Config::Snapshot::SNAPSHOT_DOWNGRADE:
return wxString(_(L("Downgrade")));
case Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
return wxString(_(L("Before roll back")));
case Config::Snapshot::SNAPSHOT_USER:
return wxString(_(L("User")));
case Config::Snapshot::SNAPSHOT_UNKNOWN:
default:
return wxString(_(L("Unknown")));
}
}
static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active)
{
// Start by declaring a row with an alternating background color.
wxString text = "<tr bgcolor=\"";
text += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5");
text += "\">";
text += "<td>";
// Format the row header.
text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active: ")) : "") +
Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
if (! snapshot.comment.empty())
text += " (" + snapshot.comment + ")";
text += "</b></font><br>";
// End of row header.
text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
text += _(L("print")) + ": " + snapshot.print + "<br>";
text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
text += _(L("printer")) + ": " + snapshot.printer + "<br>";
bool compatible = true;
for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) {
text += _(L("vendor")) + ": " + vc.name +", " + _(L("version")) + ": " + vc.version.config_version.to_string() +
", " + _(L("min slic3r version")) + ": " + vc.version.min_slic3r_version.to_string();
if (vc.version.max_slic3r_version != Semver::inf())
text += ", " + _(L("max slic3r version")) + ": " + vc.version.max_slic3r_version.to_string();
text += "<br>";
for (const std::pair<std::string, std::set<std::string>> &model : vc.models_variants_installed) {
text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": ";
for (const std::string &variant : model.second) {
if (&variant != &*model.second.begin())
text += ", ";
text += variant;
}
text += "<br>";
}
if (! vc.version.is_current_slic3r_supported()) { compatible = false; }
}
if (! compatible) {
text += "<p align=\"right\">" + _(L("Incompatible with this Slic3r")) + "</p>";
}
else if (! snapshot_active)
text += "<p align=\"right\"><a href=\"" + snapshot.id + "\">" + _(L("Activate")) + "</a></p>";
text += "</td>";
text += "</tr>";
return text;
}
static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot)
{
wxString text =
"<html>"
"<body bgcolor=\"#ffffff\" cellspacing=\"2\" cellpadding=\"0\" border=\"0\" link=\"#800000\">"
"<font color=\"#000000\">";
text += "<table style=\"width:100%\">";
for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) {
const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1];
text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot);
}
text +=
"</table>"
"</font>"
"</body>"
"</html>";
return text;
}
ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot)
: wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
{
this->SetBackgroundColour(*wxWHITE);
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
this->SetSizer(vsizer);
// text
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMSW__
int size[] = {8,8,8,8,11,11,11};
#else
int size[] = {11,11,11,11,14,14,14};
#endif
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
html->SetBorders(2);
wxString text = generate_html_page(snapshot_db, on_snapshot);
html->SetPage(text);
vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0);
html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this);
}
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &ConfigSnapshotDialog::onCloseDialog, this, wxID_CLOSE);
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
}
void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
m_snapshot_to_activate = event.GetLinkInfo().GetHref();
this->EndModal(wxID_CLOSE);
this->Close();
}
void ConfigSnapshotDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
this->Close();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,34 @@
#ifndef slic3r_GUI_ConfigSnapshotDialog_hpp_
#define slic3r_GUI_ConfigSnapshotDialog_hpp_
#include "GUI.hpp"
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/html/htmlwin.h>
namespace Slic3r {
namespace GUI {
namespace Config {
class SnapshotDB;
}
class ConfigSnapshotDialog : public wxDialog
{
public:
ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &id);
const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; }
private:
void onLinkClicked(wxHtmlLinkEvent &event);
void onCloseDialog(wxEvent &);
// If set, it contains a snapshot ID to be restored after the dialog closes.
std::string m_snapshot_to_activate;
};
} // namespace GUI
} // namespace Slic3r
#endif /* slic3r_GUI_ConfigSnapshotDialog_hpp_ */

View file

@ -0,0 +1,915 @@
#include "ConfigWizard_private.hpp"
#include <algorithm>
#include <utility>
#include <unordered_map>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <wx/settings.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/dcclient.h>
#include <wx/statbmp.h>
#include <wx/checkbox.h>
#include <wx/statline.h>
#include "libslic3r/Utils.hpp"
#include "PresetBundle.hpp"
#include "GUI.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
namespace Slic3r {
namespace GUI {
// Printer model picker GUI control
struct PrinterPickerEvent : public wxEvent
{
std::string vendor_id;
std::string model_id;
std::string variant_name;
bool enable;
PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) :
wxEvent(winid, eventType),
vendor_id(std::move(vendor_id)),
model_id(std::move(model_id)),
variant_name(std::move(variant_name)),
enable(enable)
{}
virtual wxEvent *Clone() const
{
return new PrinterPickerEvent(*this);
}
};
wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors) :
wxPanel(parent),
vendor_id(vendor.id),
variants_checked(0)
{
const auto &models = vendor.models;
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20);
printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
sizer->Add(printer_grid);
auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
namefont.SetWeight(wxFONTWEIGHT_BOLD);
// wxGrid appends widgets by rows, but we need to construct them in columns.
// These vectors are used to hold the elements so that they can be appended in the right order.
std::vector<wxStaticText*> titles;
std::vector<wxStaticBitmap*> bitmaps;
std::vector<wxPanel*> variants_panels;
for (const auto &model : models) {
auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model.id);
wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG);
auto *title = new wxStaticText(this, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
title->SetFont(namefont);
title->Wrap(std::max((int)MODEL_MIN_WRAP, bitmap.GetWidth()));
titles.push_back(title);
auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
bitmaps.push_back(bitmap_widget);
auto *variants_panel = new wxPanel(this);
auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
variants_panel->SetSizer(variants_sizer);
const auto model_id = model.id;
bool default_variant = true; // Mark the first variant as default in the GUI
for (const auto &variant : model.variants) {
const auto label = wxString::Format("%s %s %s %s", variant.name, _(L("mm")), _(L("nozzle")),
(default_variant ? _(L("(default)")) : wxString()));
default_variant = false;
auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
const size_t idx = cboxes.size();
cboxes.push_back(cbox);
bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant.name);
variants_checked += enabled;
cbox->SetValue(enabled);
variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
cbox->Bind(wxEVT_CHECKBOX, [this, idx](wxCommandEvent &event) {
if (idx >= this->cboxes.size()) { return; }
this->on_checkbox(this->cboxes[idx], event.IsChecked());
});
}
variants_panels.push_back(variants_panel);
}
for (auto title : titles) { printer_grid->Add(title, 0, wxBOTTOM, 3); }
for (auto bitmap : bitmaps) { printer_grid->Add(bitmap, 0, wxBOTTOM, 20); }
for (auto vp : variants_panels) { printer_grid->Add(vp); }
auto *all_none_sizer = new wxBoxSizer(wxHORIZONTAL);
auto *sel_all = new wxButton(this, wxID_ANY, _(L("Select all")));
auto *sel_none = new wxButton(this, wxID_ANY, _(L("Select none")));
sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true); });
sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
all_none_sizer->AddStretchSpacer();
all_none_sizer->Add(sel_all);
all_none_sizer->Add(sel_none);
sizer->AddStretchSpacer();
sizer->Add(all_none_sizer, 0, wxEXPAND);
SetSizer(sizer);
}
void PrinterPicker::select_all(bool select)
{
for (const auto &cb : cboxes) {
if (cb->GetValue() != select) {
cb->SetValue(select);
on_checkbox(cb, select);
}
}
}
void PrinterPicker::select_one(size_t i, bool select)
{
if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
cboxes[i]->SetValue(select);
on_checkbox(cboxes[i], select);
}
}
void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
{
variants_checked += checked ? 1 : -1;
PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
AddPendingEvent(evt);
}
// Wizard page base
ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname) :
wxPanel(parent->p->hscroll),
parent(parent),
shortname(std::move(shortname)),
p_prev(nullptr),
p_next(nullptr)
{
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
auto font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
font.SetWeight(wxFONTWEIGHT_BOLD);
font.SetPointSize(14);
text->SetFont(font);
sizer->Add(text, 0, wxALIGN_LEFT, 0);
sizer->AddSpacer(10);
content = new wxBoxSizer(wxVERTICAL);
sizer->Add(content, 1);
SetSizer(sizer);
this->Hide();
Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
this->Layout();
event.Skip();
});
}
ConfigWizardPage::~ConfigWizardPage() {}
ConfigWizardPage* ConfigWizardPage::chain(ConfigWizardPage *page)
{
if (p_next != nullptr) { p_next->p_prev = nullptr; }
p_next = page;
if (page != nullptr) {
if (page->p_prev != nullptr) { page->p_prev->p_next = nullptr; }
page->p_prev = this;
}
return page;
}
void ConfigWizardPage::append_text(wxString text)
{
auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
widget->Wrap(WRAP_WIDTH);
widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
append(widget);
}
void ConfigWizardPage::append_spacer(int space)
{
content->AddSpacer(space);
}
bool ConfigWizardPage::Show(bool show)
{
if (extra_buttons() != nullptr) { extra_buttons()->Show(show); }
return wxPanel::Show(show);
}
void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable); }
// Wizard pages
PageWelcome::PageWelcome(ConfigWizard *parent, bool check_first_variant) :
ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))),
printer_picker(nullptr),
others_buttons(new wxPanel(parent)),
cbox_reset(nullptr)
{
if (wizard_p()->run_reason == ConfigWizard::RR_DATA_EMPTY) {
wxString::Format(_(L("Run %s")), ConfigWizard::name());
append_text(wxString::Format(
_(L("Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")),
ConfigWizard::name())
);
} else {
cbox_reset = new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")));
append(cbox_reset);
}
const auto &vendors = wizard_p()->vendors;
const auto vendor_prusa = vendors.find("PrusaResearch");
if (vendor_prusa != vendors.cend()) {
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors);
if (check_first_variant) {
// Select the default (first) model/variant on the Prusa vendor
printer_picker->select_one(0, true);
}
printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
this->on_variant_checked();
});
append(printer_picker);
}
const size_t num_other_vendors = vendors.size() - (vendor_prusa != vendors.cend());
auto *sizer = new wxBoxSizer(wxHORIZONTAL);
auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors")));
other_vendors->Enable(num_other_vendors > 0);
auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup")));
sizer->Add(other_vendors);
sizer->AddSpacer(BTN_SPACING);
sizer->Add(custom_setup);
other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); });
custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); });
others_buttons->SetSizer(sizer);
}
void PageWelcome::on_page_set()
{
chain(wizard_p()->page_update);
on_variant_checked();
}
void PageWelcome::on_variant_checked()
{
enable_next(printer_picker != nullptr ? printer_picker->variants_checked > 0 : false);
}
PageUpdate::PageUpdate(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))),
version_check(true),
preset_update(true)
{
const AppConfig *app_config = GUI::get_app_config();
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for application updates")));
box_slic3r->SetValue(app_config->get("version_check") == "1");
append(box_slic3r);
append_text(_(L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.")));
append_spacer(VERTICAL_SPACING);
auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically")));
box_presets->SetValue(app_config->get("preset_update") == "1");
append(box_presets);
append_text(_(L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.")));
const auto text_bold = _(L("Updates are never applied without user's consent and never overwrite user's customized settings."));
auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
label_bold->SetFont(boldfont);
label_bold->Wrap(WRAP_WIDTH);
append(label_bold);
append_text(_(L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")));
box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
}
PageVendors::PageVendors(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors")))
{
append_text(_(L("Pick another vendor supported by Slic3r PE:")));
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
wxArrayString choices_vendors;
for (const auto vendor_pair : wizard_p()->vendors) {
const auto &vendor = vendor_pair.second;
if (vendor.id == "PrusaResearch") { continue; }
auto *picker = new PrinterPicker(this, vendor, appconfig_vendors);
picker->Hide();
pickers.push_back(picker);
choices_vendors.Add(vendor.name);
picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
this->on_variant_checked();
});
}
auto *vendor_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices_vendors);
if (choices_vendors.GetCount() > 0) {
vendor_picker->SetSelection(0);
on_vendor_pick(0);
}
vendor_picker->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) {
this->on_vendor_pick(evt.GetInt());
});
append(vendor_picker);
for (PrinterPicker *picker : pickers) { this->append(picker); }
}
void PageVendors::on_page_set()
{
on_variant_checked();
}
void PageVendors::on_vendor_pick(size_t i)
{
for (PrinterPicker *picker : pickers) { picker->Hide(); }
if (i < pickers.size()) {
pickers[i]->Show();
wizard_p()->layout_fit();
}
}
void PageVendors::on_variant_checked()
{
size_t variants_checked = 0;
for (const PrinterPicker *picker : pickers) { variants_checked += picker->variants_checked; }
enable_next(variants_checked > 0);
}
PageFirmware::PageFirmware(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))),
gcode_opt(print_config_def.options["gcode_flavor"]),
gcode_picker(nullptr)
{
append_text(_(L("Choose the type of firmware used by your printer.")));
append_text(gcode_opt.tooltip);
wxArrayString choices;
choices.Alloc(gcode_opt.enum_labels.size());
for (const auto &label : gcode_opt.enum_labels) {
choices.Add(label);
}
gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
const auto &enum_values = gcode_opt.enum_values;
auto needle = enum_values.cend();
if (gcode_opt.default_value != nullptr) {
needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
}
if (needle != enum_values.cend()) {
gcode_picker->SetSelection(needle - enum_values.cbegin());
} else {
gcode_picker->SetSelection(0);
}
append(gcode_picker);
}
void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
{
auto sel = gcode_picker->GetSelection();
if (sel >= 0 && sel < gcode_opt.enum_labels.size()) {
auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
config.set_key_value("gcode_flavor", opt);
}
}
PageBedShape::PageBedShape(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Bed Shape and Size")), _(L("Bed Shape"))),
shape_panel(new BedShapePanel(this))
{
append_text(_(L("Set the shape of your printer's bed.")));
shape_panel->build_panel(wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape"));
append(shape_panel);
}
void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
{
const auto points(shape_panel->GetValue());
auto *opt = new ConfigOptionPoints(points);
config.set_key_value("bed_shape", opt);
}
PageDiameters::PageDiameters(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Filament and Nozzle Diameters")), _(L("Print Diameters"))),
spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)),
spin_filam(new wxSpinCtrlDouble(this, wxID_ANY))
{
spin_nozzle->SetDigits(2);
spin_nozzle->SetIncrement(0.1);
const auto &def_nozzle = print_config_def.options["nozzle_diameter"];
auto *default_nozzle = dynamic_cast<const ConfigOptionFloats*>(def_nozzle.default_value);
spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
spin_filam->SetDigits(2);
spin_filam->SetIncrement(0.25);
const auto &def_filam = print_config_def.options["filament_diameter"];
auto *default_filam = dynamic_cast<const ConfigOptionFloats*>(def_filam.default_value);
spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
append_text(_(L("Enter the diameter of your printer's hot end nozzle.")));
auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5);
auto *text_nozzle = new wxStaticText(this, wxID_ANY, _(L("Nozzle Diameter:")));
auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _(L("mm")));
sizer_nozzle->AddGrowableCol(0, 1);
sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
sizer_nozzle->Add(spin_nozzle);
sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_nozzle);
append_spacer(VERTICAL_SPACING);
append_text(_(L("Enter the diameter of your filament.")));
append_text(_(L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")));
auto *sizer_filam = new wxFlexGridSizer(3, 5, 5);
auto *text_filam = new wxStaticText(this, wxID_ANY, _(L("Filament Diameter:")));
auto *unit_filam = new wxStaticText(this, wxID_ANY, _(L("mm")));
sizer_filam->AddGrowableCol(0, 1);
sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
sizer_filam->Add(spin_filam);
sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_filam);
}
void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
{
auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue());
config.set_key_value("nozzle_diameter", opt_nozzle);
auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue());
config.set_key_value("filament_diameter", opt_filam);
}
PageTemperatures::PageTemperatures(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Extruder and Bed Temperatures")), _(L("Temperatures"))),
spin_extr(new wxSpinCtrlDouble(this, wxID_ANY)),
spin_bed(new wxSpinCtrlDouble(this, wxID_ANY))
{
spin_extr->SetIncrement(5.0);
const auto &def_extr = print_config_def.options["temperature"];
spin_extr->SetRange(def_extr.min, def_extr.max);
auto *default_extr = dynamic_cast<const ConfigOptionInts*>(def_extr.default_value);
spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200);
spin_bed->SetIncrement(5.0);
const auto &def_bed = print_config_def.options["bed_temperature"];
spin_bed->SetRange(def_bed.min, def_bed.max);
auto *default_bed = dynamic_cast<const ConfigOptionInts*>(def_bed.default_value);
spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
append_text(_(L("Enter the temperature needed for extruding your filament.")));
append_text(_(L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")));
auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
auto *text_extr = new wxStaticText(this, wxID_ANY, _(L("Extrusion Temperature:")));
auto *unit_extr = new wxStaticText(this, wxID_ANY, _(L("°C")));
sizer_extr->AddGrowableCol(0, 1);
sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
sizer_extr->Add(spin_extr);
sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_extr);
append_spacer(VERTICAL_SPACING);
append_text(_(L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")));
append_text(_(L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")));
auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
auto *text_bed = new wxStaticText(this, wxID_ANY, _(L("Bed Temperature:")));
auto *unit_bed = new wxStaticText(this, wxID_ANY, _(L("°C")));
sizer_bed->AddGrowableCol(0, 1);
sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
sizer_bed->Add(spin_bed);
sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_bed);
}
void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
{
auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
config.set_key_value("temperature", opt_extr);
auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
config.set_key_value("first_layer_temperature", opt_extr1st);
auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
config.set_key_value("bed_temperature", opt_bed);
auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
config.set_key_value("first_layer_bed_temperature", opt_bed1st);
}
// Index
ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) :
wxPanel(parent),
bg(GUI::from_u8(Slic3r::var("Slic3r_192px_transparent.png")), wxBITMAP_TYPE_PNG),
bullet_black(GUI::from_u8(Slic3r::var("bullet_black.png")), wxBITMAP_TYPE_PNG),
bullet_blue(GUI::from_u8(Slic3r::var("bullet_blue.png")), wxBITMAP_TYPE_PNG),
bullet_white(GUI::from_u8(Slic3r::var("bullet_white.png")), wxBITMAP_TYPE_PNG)
{
SetMinSize(bg.GetSize());
wxClientDC dc(this);
text_height = dc.GetCharHeight();
// Add logo bitmap.
// This could be done in on_paint() along with the index labels, but I've found it tricky
// to get the bitmap rendered well on all platforms with transparent background.
// In some cases it didn't work at all. And so wxStaticBitmap is used here instead,
// because it has all the platform quirks figured out.
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *logo = new wxStaticBitmap(this, wxID_ANY, bg);
sizer->AddStretchSpacer();
sizer->Add(logo);
SetSizer(sizer);
Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
}
void ConfigWizardIndex::load_items(ConfigWizardPage *firstpage)
{
items.clear();
item_active = items.cend();
for (auto *page = firstpage; page != nullptr; page = page->page_next()) {
items.emplace_back(page->shortname);
}
Refresh();
}
void ConfigWizardIndex::set_active(ConfigWizardPage *page)
{
item_active = std::find(items.cbegin(), items.cend(), page->shortname);
Refresh();
}
void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
{
enum {
MARGIN = 10,
SPACING = 5,
};
const auto size = GetClientSize();
if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
wxPaintDC dc(this);
const auto bullet_w = bullet_black.GetSize().GetWidth();
const auto bullet_h = bullet_black.GetSize().GetHeight();
const int yoff_icon = bullet_h < text_height ? (text_height - bullet_h) / 2 : 0;
const int yoff_text = bullet_h > text_height ? (bullet_h - text_height) / 2 : 0;
const int yinc = std::max(bullet_h, text_height) + SPACING;
unsigned y = 0;
for (auto it = items.cbegin(); it != items.cend(); ++it) {
if (it < item_active) { dc.DrawBitmap(bullet_black, MARGIN, y + yoff_icon, false); }
if (it == item_active) { dc.DrawBitmap(bullet_blue, MARGIN, y + yoff_icon, false); }
if (it > item_active) { dc.DrawBitmap(bullet_white, MARGIN, y + yoff_icon, false); }
dc.DrawText(*it, MARGIN + bullet_w + SPACING, y + yoff_text);
y += yinc;
}
}
// priv
static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
{ "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") },
{ "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") },
{ "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
{ "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") },
{ "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
{ "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") },
{ "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
{ "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") },
}};
void ConfigWizard::priv::load_vendors()
{
const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor";
const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles";
// Load vendors from the "vendors" directory in datadir
for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == ".ini") {
try {
auto vp = VendorProfile::from_ini(it->path());
vendors[vp.id] = std::move(vp);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what();
}
}
}
// Additionally load up vendors from the application resources directory, but only those not seen in the datadir
for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == ".ini") {
const auto id = it->path().stem().string();
if (vendors.find(id) == vendors.end()) {
try {
auto vp = VendorProfile::from_ini(it->path());
vendors_rsrc[vp.id] = it->path().filename().string();
vendors[vp.id] = std::move(vp);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what();
}
}
}
}
// Load up the set of vendors / models / variants the user has had enabled up till now
const AppConfig *app_config = GUI::get_app_config();
if (! app_config->legacy_datadir()) {
appconfig_vendors.set_vendors(*app_config);
} else {
// In case of legacy datadir, try to guess the preference based on the printer preset files that are present
const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
for (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) {
auto needle = legacy_preset_map.find(it->path().filename().string());
if (needle == legacy_preset_map.end()) { continue; }
const auto &model = needle->second.first;
const auto &variant = needle->second.second;
appconfig_vendors.set_variant("PrusaResearch", model, variant, true);
}
}
}
void ConfigWizard::priv::index_refresh()
{
index->load_items(page_welcome);
}
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
{
hscroll_sizer->Add(page, 0, wxEXPAND);
auto *extra_buttons = page->extra_buttons();
if (extra_buttons != nullptr) {
btnsizer->Prepend(extra_buttons, 0);
}
}
void ConfigWizard::priv::set_page(ConfigWizardPage *page)
{
if (page == nullptr) { return; }
if (page_current != nullptr) { page_current->Hide(); }
page_current = page;
enable_next(true);
page->on_page_set();
index->load_items(page_welcome);
index->set_active(page);
page->Show();
btn_prev->Enable(page->page_prev() != nullptr);
btn_next->Show(page->page_next() != nullptr);
btn_finish->Show(page->page_next() == nullptr);
layout_fit();
}
void ConfigWizard::priv::layout_fit()
{
q->Layout();
q->Fit();
}
void ConfigWizard::priv::enable_next(bool enable)
{
btn_next->Enable(enable);
btn_finish->Enable(enable);
}
void ConfigWizard::priv::on_other_vendors()
{
page_welcome
->chain(page_vendors)
->chain(page_update);
set_page(page_vendors);
}
void ConfigWizard::priv::on_custom_setup()
{
page_welcome->chain(page_firmware);
page_temps->chain(page_update);
set_page(page_firmware);
}
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
{
const bool is_custom_setup = page_welcome->page_next() == page_firmware;
if (! is_custom_setup) {
const auto enabled_vendors = appconfig_vendors.vendors();
// Install bundles from resources if needed:
std::vector<std::string> install_bundles;
for (const auto &vendor_rsrc : vendors_rsrc) {
const auto vendor = enabled_vendors.find(vendor_rsrc.first);
if (vendor == enabled_vendors.end()) { continue; }
size_t size_sum = 0;
for (const auto &model : vendor->second) { size_sum += model.second.size(); }
if (size_sum == 0) { continue; }
// This vendor needs to be installed
install_bundles.emplace_back(vendor_rsrc.second);
}
// Decide whether to create snapshot based on run_reason and the reset profile checkbox
bool snapshot = true;
switch (run_reason) {
case ConfigWizard::RR_DATA_EMPTY: snapshot = false; break;
case ConfigWizard::RR_DATA_LEGACY: snapshot = true; break;
case ConfigWizard::RR_DATA_INCOMPAT: snapshot = false; break; // In this case snapshot is done by PresetUpdater with the appropriate reason
case ConfigWizard::RR_USER: snapshot = page_welcome->reset_user_profile(); break;
}
if (install_bundles.size() > 0) {
// Install bundles from resources.
updater->install_bundles_rsrc(std::move(install_bundles), snapshot);
} else {
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
}
if (page_welcome->reset_user_profile()) {
BOOST_LOG_TRIVIAL(info) << "Resetting user profiles...";
preset_bundle->reset(true);
}
app_config->set_vendors(appconfig_vendors);
app_config->set("version_check", page_update->version_check ? "1" : "0");
app_config->set("preset_update", page_update->preset_update ? "1" : "0");
app_config->reset_selections();
preset_bundle->load_presets(*app_config);
} else {
for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) {
page->apply_custom_config(*custom_config);
}
preset_bundle->load_config("My Settings", *custom_config);
}
// Update the selections from the compatibilty.
preset_bundle->export_selections(*app_config);
}
// Public
ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) :
wxDialog(parent, wxID_ANY, name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
p(new priv(this))
{
p->run_reason = reason;
p->load_vendors();
p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
"gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
}));
p->index = new ConfigWizardIndex(this);
auto *vsizer = new wxBoxSizer(wxVERTICAL);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *hline = new wxStaticLine(this);
p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
// Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling.
// Later, we compare that to the size of the current screen and set minimum width based on that (see below).
p->hscroll = new wxScrolledWindow(this);
p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL);
p->hscroll->SetSizer(p->hscroll_sizer);
topsizer->Add(p->index, 0, wxEXPAND);
topsizer->AddSpacer(INDEX_MARGIN);
topsizer->Add(p->hscroll, 1, wxEXPAND);
p->btn_prev = new wxButton(this, wxID_NONE, _(L("< &Back")));
p->btn_next = new wxButton(this, wxID_NONE, _(L("&Next >")));
p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish")));
p->btn_cancel = new wxButton(this, wxID_CANCEL);
p->btnsizer->AddStretchSpacer();
p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
p->add_page(p->page_welcome = new PageWelcome(this, reason == RR_DATA_EMPTY || reason == RR_DATA_LEGACY));
p->add_page(p->page_update = new PageUpdate(this));
p->add_page(p->page_vendors = new PageVendors(this));
p->add_page(p->page_firmware = new PageFirmware(this));
p->add_page(p->page_bed = new PageBedShape(this));
p->add_page(p->page_diams = new PageDiameters(this));
p->add_page(p->page_temps = new PageTemperatures(this));
p->index_refresh();
p->page_welcome->chain(p->page_update);
p->page_firmware
->chain(p->page_bed)
->chain(p->page_diams)
->chain(p->page_temps);
vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
vsizer->Add(hline, 0, wxEXPAND);
vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
p->set_page(p->page_welcome);
SetSizer(vsizer);
SetSizerAndFit(vsizer);
// We can now enable scrolling on hscroll
p->hscroll->SetScrollRate(30, 30);
// Compare current ("ideal") wizard size with the size of the current screen.
// If the screen is smaller, resize wizrad to match, which will enable scrollbars.
auto wizard_size = GetSize();
unsigned width, height;
if (GUI::get_current_screen_size(this, width, height)) {
wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN)));
wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN)));
SetMinSize(wizard_size);
}
Fit();
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });
p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); });
p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->EndModal(wxID_OK); });
}
ConfigWizard::~ConfigWizard() {}
bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater)
{
BOOST_LOG_TRIVIAL(info) << "Running ConfigWizard, reason: " << p->run_reason;
if (ShowModal() == wxID_OK) {
auto *app_config = GUI::get_app_config();
p->apply_config(app_config, preset_bundle, updater);
app_config->set_legacy_datadir(false);
BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
return true;
} else {
BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
return false;
}
}
const wxString& ConfigWizard::name()
{
// A different naming convention is used for the Wizard on Windows vs. OSX & GTK.
#if WIN32
static const wxString config_wizard_name = L("Configuration Wizard");
#else
static const wxString config_wizard_name = L("Configuration Assistant");
#endif
return config_wizard_name;
}
}
}

View file

@ -0,0 +1,50 @@
#ifndef slic3r_ConfigWizard_hpp_
#define slic3r_ConfigWizard_hpp_
#include <memory>
#include <wx/dialog.h>
namespace Slic3r {
class PresetBundle;
class PresetUpdater;
namespace GUI {
class ConfigWizard: public wxDialog
{
public:
// Why is the Wizard run
enum RunReason {
RR_DATA_EMPTY, // No or empty datadir
RR_DATA_LEGACY, // Pre-updating datadir
RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation
RR_USER, // User requested the Wizard from the menus
};
ConfigWizard(wxWindow *parent, RunReason run_reason);
ConfigWizard(ConfigWizard &&) = delete;
ConfigWizard(const ConfigWizard &) = delete;
ConfigWizard &operator=(ConfigWizard &&) = delete;
ConfigWizard &operator=(const ConfigWizard &) = delete;
~ConfigWizard();
// Run the Wizard. Return whether it was completed.
bool run(PresetBundle *preset_bundle, const PresetUpdater *updater);
static const wxString& name();
private:
struct priv;
std::unique_ptr<priv> p;
friend class ConfigWizardPage;
};
}
}
#endif

View file

@ -0,0 +1,241 @@
#ifndef slic3r_ConfigWizard_private_hpp_
#define slic3r_ConfigWizard_private_hpp_
#include "ConfigWizard.hpp"
#include <vector>
#include <set>
#include <unordered_map>
#include <boost/filesystem.hpp>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/spinctrl.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "AppConfig.hpp"
#include "Preset.hpp"
#include "BedShapeDialog.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
enum {
WRAP_WIDTH = 500,
MODEL_MIN_WRAP = 150,
DIALOG_MARGIN = 15,
INDEX_MARGIN = 40,
BTN_SPACING = 10,
INDENT_SPACING = 30,
VERTICAL_SPACING = 10,
};
struct PrinterPicker: wxPanel
{
struct Checkbox : wxCheckBox
{
Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
wxCheckBox(parent, wxID_ANY, label),
model(model),
variant(variant)
{}
std::string model;
std::string variant;
};
const std::string vendor_id;
std::vector<Checkbox*> cboxes;
unsigned variants_checked;
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors);
void select_all(bool select);
void select_one(size_t i, bool select);
void on_checkbox(const Checkbox *cbox, bool checked);
};
struct ConfigWizardPage: wxPanel
{
ConfigWizard *parent;
const wxString shortname;
wxBoxSizer *content;
ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname);
virtual ~ConfigWizardPage();
ConfigWizardPage* page_prev() const { return p_prev; }
ConfigWizardPage* page_next() const { return p_next; }
ConfigWizardPage* chain(ConfigWizardPage *page);
template<class T>
void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
{
content->Add(thing, proportion, flag, border);
}
void append_text(wxString text);
void append_spacer(int space);
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
virtual bool Show(bool show = true);
virtual bool Hide() { return Show(false); }
virtual wxPanel* extra_buttons() { return nullptr; }
virtual void on_page_set() {}
virtual void apply_custom_config(DynamicPrintConfig &config) {}
void enable_next(bool enable);
private:
ConfigWizardPage *p_prev;
ConfigWizardPage *p_next;
};
struct PageWelcome: ConfigWizardPage
{
PrinterPicker *printer_picker;
wxPanel *others_buttons;
wxCheckBox *cbox_reset;
PageWelcome(ConfigWizard *parent, bool check_first_variant);
virtual wxPanel* extra_buttons() { return others_buttons; }
virtual void on_page_set();
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
void on_variant_checked();
};
struct PageUpdate: ConfigWizardPage
{
bool version_check;
bool preset_update;
PageUpdate(ConfigWizard *parent);
};
struct PageVendors: ConfigWizardPage
{
std::vector<PrinterPicker*> pickers;
PageVendors(ConfigWizard *parent);
virtual void on_page_set();
void on_vendor_pick(size_t i);
void on_variant_checked();
};
struct PageFirmware: ConfigWizardPage
{
const ConfigOptionDef &gcode_opt;
wxChoice *gcode_picker;
PageFirmware(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
struct PageBedShape: ConfigWizardPage
{
BedShapePanel *shape_panel;
PageBedShape(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
struct PageDiameters: ConfigWizardPage
{
wxSpinCtrlDouble *spin_nozzle;
wxSpinCtrlDouble *spin_filam;
PageDiameters(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
struct PageTemperatures: ConfigWizardPage
{
wxSpinCtrlDouble *spin_extr;
wxSpinCtrlDouble *spin_bed;
PageTemperatures(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
class ConfigWizardIndex: public wxPanel
{
public:
ConfigWizardIndex(wxWindow *parent);
void load_items(ConfigWizardPage *firstpage);
void set_active(ConfigWizardPage *page);
private:
const wxBitmap bg;
const wxBitmap bullet_black;
const wxBitmap bullet_blue;
const wxBitmap bullet_white;
int text_height;
std::vector<wxString> items;
std::vector<wxString>::const_iterator item_active;
void on_paint(wxPaintEvent &evt);
};
struct ConfigWizard::priv
{
ConfigWizard *q;
ConfigWizard::RunReason run_reason;
AppConfig appconfig_vendors;
std::unordered_map<std::string, VendorProfile> vendors;
std::unordered_map<std::string, std::string> vendors_rsrc;
std::unique_ptr<DynamicPrintConfig> custom_config;
wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr;
wxBoxSizer *btnsizer = nullptr;
ConfigWizardPage *page_current = nullptr;
ConfigWizardIndex *index = nullptr;
wxButton *btn_prev = nullptr;
wxButton *btn_next = nullptr;
wxButton *btn_finish = nullptr;
wxButton *btn_cancel = nullptr;
PageWelcome *page_welcome = nullptr;
PageUpdate *page_update = nullptr;
PageVendors *page_vendors = nullptr;
PageFirmware *page_firmware = nullptr;
PageBedShape *page_bed = nullptr;
PageDiameters *page_diams = nullptr;
PageTemperatures *page_temps = nullptr;
priv(ConfigWizard *q) : q(q) {}
void load_vendors();
void add_page(ConfigWizardPage *page);
void index_refresh();
void set_page(ConfigWizardPage *page);
void layout_fit();
void go_prev() { if (page_current != nullptr) { set_page(page_current->page_prev()); } }
void go_next() { if (page_current != nullptr) { set_page(page_current->page_next()); } }
void enable_next(bool enable);
void on_other_vendors();
void on_custom_setup();
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
};
}
}
#endif

784
src/slic3r/GUI/Field.cpp Normal file
View file

@ -0,0 +1,784 @@
#include "GUI.hpp"//"slic3r_gui.hpp"
#include "Field.hpp"
//#include <wx/event.h>
#include <regex>
#include <wx/numformatter.h>
#include <wx/tooltip.h>
#include "PrintConfig.hpp"
#include <boost/algorithm/string/predicate.hpp>
namespace Slic3r { namespace GUI {
wxString double_to_string(double const value)
{
if (value - int(value) == 0)
return wxString::Format(_T("%i"), int(value));
else {
int precision = 4;
for (size_t p = 1; p < 4; p++)
{
double cur_val = pow(10, p)*value;
if (cur_val - int(cur_val) == 0) {
precision = p;
break;
}
}
return wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
}
}
void Field::PostInitialize(){
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
if (wxMSW) {
m_Undo_btn->SetBackgroundColour(color);
m_Undo_to_sys_btn->SetBackgroundColour(color);
}
m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); }));
m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); }));
//set default bitmap
wxBitmap bmp;
bmp.LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG);
set_undo_bitmap(&bmp);
set_undo_to_sys_bitmap(&bmp);
switch (m_opt.type)
{
case coPercents:
case coFloats:
case coStrings:
case coBools:
case coInts: {
auto tag_pos = m_opt_id.find("#");
if (tag_pos != std::string::npos)
m_opt_idx = stoi(m_opt_id.substr(tag_pos + 1, m_opt_id.size()));
break;
}
default:
break;
}
BUILD();
}
void Field::on_kill_focus(wxEvent& event) {
// Without this, there will be nasty focus bugs on Windows.
// Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
// non-command events to allow the default handling to take place."
event.Skip();
// call the registered function if it is available
if (m_on_kill_focus!=nullptr)
m_on_kill_focus();
}
void Field::on_change_field()
{
// std::cerr << "calling Field::_on_change \n";
if (m_on_change != nullptr && !m_disable_change_event)
m_on_change(m_opt_id, get_value());
}
void Field::on_back_to_initial_value(){
if (m_back_to_initial_value != nullptr && m_is_modified_value)
m_back_to_initial_value(m_opt_id);
}
void Field::on_back_to_sys_value(){
if (m_back_to_sys_value != nullptr && m_is_nonsys_value)
m_back_to_sys_value(m_opt_id);
}
wxString Field::get_tooltip_text(const wxString& default_string)
{
wxString tooltip_text("");
wxString tooltip = _(m_opt.tooltip);
if (tooltip.length() > 0)
tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " +
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string +
(boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") +
_(L("parameter name")) + "\t: " + m_opt_id;
return tooltip_text;
}
bool Field::is_matched(const std::string& string, const std::string& pattern)
{
std::regex regex_pattern(pattern, std::regex_constants::icase); // use ::icase to make the matching case insensitive like /i in perl
return std::regex_match(string, regex_pattern);
}
void Field::get_value_by_opt_type(wxString& str)
{
switch (m_opt.type){
case coInt:
m_value = wxAtoi(str);
break;
case coPercent:
case coPercents:
case coFloats:
case coFloat:{
if (m_opt.type == coPercent && str.Last() == '%')
str.RemoveLast();
else if (str.Last() == '%') {
wxString label = m_Label->GetLabel();
if (label.Last() == '\n') label.RemoveLast();
while (label.Last() == ' ') label.RemoveLast();
if (label.Last() == ':') label.RemoveLast();
show_error(m_parent, wxString::Format(_(L("%s doesn't support percentage")), label));
set_value(double_to_string(m_opt.min), true);
m_value = double(m_opt.min);
break;
}
double val;
if(!str.ToCDouble(&val))
{
show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits")));
set_value(double_to_string(val), true);
}
if (m_opt.min > val || val > m_opt.max)
{
show_error(m_parent, _(L("Input value is out of range")));
if (m_opt.min > val) val = m_opt.min;
if (val > m_opt.max) val = m_opt.max;
set_value(double_to_string(val), true);
}
m_value = val;
break; }
case coString:
case coStrings:
case coFloatOrPercent:
m_value = str.ToStdString();
break;
default:
break;
}
}
void TextCtrl::BUILD() {
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxString text_value = wxString("");
switch (m_opt.type) {
case coFloatOrPercent:
{
text_value = double_to_string(m_opt.default_value->getFloat());
if (static_cast<const ConfigOptionFloatOrPercent*>(m_opt.default_value)->percent)
text_value += "%";
break;
}
case coPercent:
{
text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat()));
text_value += "%";
break;
}
case coPercents:
case coFloats:
case coFloat:
{
double val = m_opt.type == coFloats ?
static_cast<const ConfigOptionFloats*>(m_opt.default_value)->get_at(m_opt_idx) :
m_opt.type == coFloat ?
m_opt.default_value->getFloat() :
static_cast<const ConfigOptionPercents*>(m_opt.default_value)->get_at(m_opt_idx);
text_value = double_to_string(val);
break;
}
case coString:
text_value = static_cast<const ConfigOptionString*>(m_opt.default_value)->value;
break;
case coStrings:
{
const ConfigOptionStrings *vec = static_cast<const ConfigOptionStrings*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break; //for the case of empty default value
text_value = vec->get_at(m_opt_idx);
break;
}
default:
break;
}
auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, (m_opt.multiline ? wxTE_MULTILINE : 0));
temp->SetToolTip(get_tooltip_text(text_value));
temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event)
{
//! to allow the default handling
event.Skip();
//! eliminating the g-code pop up text description
bool flag = false;
#ifdef __WXGTK__
// I have no idea why, but on GTK flag works in other way
flag = true;
#endif // __WXGTK__
temp->GetToolTip()->Enable(flag);
}), temp->GetId());
#if !defined(__WXGTK__)
temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e)
{
e.Skip();// on_kill_focus(e);
temp->GetToolTip()->Enable(true);
}), temp->GetId());
#endif // __WXGTK__
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt)
{
#ifdef __WXGTK__
if (bChangedValueEvent)
#endif //__WXGTK__
on_change_field();
}), temp->GetId());
#ifdef __WXGTK__
// to correct value updating on GTK we should:
// call on_change_field() on wxEVT_KEY_UP instead of wxEVT_TEXT
// and prevent value updating on wxEVT_KEY_DOWN
temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this);
temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this);
#endif //__WXGTK__
// select all text using Ctrl+A
temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event)
{
if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
temp->SetSelection(-1, -1); //select all
event.Skip();
}));
// recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
}
boost::any& TextCtrl::get_value()
{
wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
get_value_by_opt_type(ret_str);
return m_value;
}
void TextCtrl::enable() { dynamic_cast<wxTextCtrl*>(window)->Enable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(true); }
void TextCtrl::disable() { dynamic_cast<wxTextCtrl*>(window)->Disable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(false); }
#ifdef __WXGTK__
void TextCtrl::change_field_value(wxEvent& event)
{
if (bChangedValueEvent = event.GetEventType()==wxEVT_KEY_UP)
on_change_field();
event.Skip();
};
#endif //__WXGTK__
void CheckBox::BUILD() {
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
bool check_value = m_opt.type == coBool ?
m_opt.default_value->getBool() : m_opt.type == coBools ?
static_cast<const ConfigOptionBools*>(m_opt.default_value)->get_at(m_opt_idx) :
false;
auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
temp->SetValue(check_value);
if (m_opt.readonly) temp->Disable();
temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
temp->SetToolTip(get_tooltip_text(check_value ? "true" : "false"));
// recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
}
boost::any& CheckBox::get_value()
{
// boost::any m_value;
bool value = dynamic_cast<wxCheckBox*>(window)->GetValue();
if (m_opt.type == coBool)
m_value = static_cast<bool>(value);
else
m_value = static_cast<unsigned char>(value);
return m_value;
}
int undef_spin_val = -9999; //! Probably, It's not necessary
void SpinCtrl::BUILD() {
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxString text_value = wxString("");
int default_value = 0;
switch (m_opt.type) {
case coInt:
default_value = m_opt.default_value->getInt();
text_value = wxString::Format(_T("%i"), default_value);
break;
case coInts:
{
const ConfigOptionInts *vec = static_cast<const ConfigOptionInts*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break;
for (size_t id = 0; id < vec->size(); ++id)
{
default_value = vec->get_at(id);
text_value += wxString::Format(_T("%i"), default_value);
}
break;
}
default:
break;
}
const int min_val = m_opt.min == INT_MIN ? 0: m_opt.min;
const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647;
auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size,
0, min_val, max_val, default_value);
// temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId());
// temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId());
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e)
{
// # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
// # when it was changed from the text control, so the on_change callback
// # gets the old one, and on_kill_focus resets the control to the old value.
// # As a workaround, we get the new value from $event->GetString and store
// # here temporarily so that we can return it from $self->get_value
std::string value = e.GetString().utf8_str().data();
if (is_matched(value, "^\\d+$"))
tmp_value = std::stoi(value);
on_change_field();
// # We don't reset tmp_value here because _on_change might put callbacks
// # in the CallAfter queue, and we want the tmp value to be available from
// # them as well.
}), temp->GetId());
temp->SetToolTip(get_tooltip_text(text_value));
// recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
}
void Choice::BUILD() {
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxComboBox* temp;
if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0)
temp = new wxComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
else
temp = new wxComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, NULL, wxCB_READONLY);
// recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
if (m_opt.enum_labels.empty() && m_opt.enum_values.empty()){
}
else{
for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels){
const wxString& str = _(el);//m_opt_id == "support" ? _(el) : el;
temp->Append(str);
}
set_selection();
}
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
temp->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
}
void Choice::set_selection()
{
wxString text_value = wxString("");
switch (m_opt.type){
case coFloat:
case coPercent: {
double val = m_opt.default_value->getFloat();
text_value = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 1);
size_t idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(text_value) == 0)
break;
++idx;
}
// if (m_opt.type == coPercent) text_value += "%";
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
break;
}
case coEnum:{
int id_value = static_cast<const ConfigOptionEnum<SeamPosition>*>(m_opt.default_value)->value; //!!
dynamic_cast<wxComboBox*>(window)->SetSelection(id_value);
break;
}
case coInt:{
int val = m_opt.default_value->getInt(); //!!
text_value = wxString::Format(_T("%i"), int(val));
size_t idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(text_value) == 0)
break;
++idx;
}
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
break;
}
case coStrings:{
text_value = static_cast<const ConfigOptionStrings*>(m_opt.default_value)->get_at(m_opt_idx);
size_t idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(text_value) == 0)
break;
++idx;
}
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
break;
}
}
}
void Choice::set_value(const std::string& value, bool change_event) //! Redundant?
{
m_disable_change_event = !change_event;
size_t idx=0;
for (auto el : m_opt.enum_values)
{
if (el.compare(value) == 0)
break;
++idx;
}
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
m_disable_change_event = false;
}
void Choice::set_value(const boost::any& value, bool change_event)
{
m_disable_change_event = !change_event;
switch (m_opt.type){
case coInt:
case coFloat:
case coPercent:
case coString:
case coStrings:{
wxString text_value;
if (m_opt.type == coInt)
text_value = wxString::Format(_T("%i"), int(boost::any_cast<int>(value)));
else
text_value = boost::any_cast<wxString>(value);
auto idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(text_value) == 0)
break;
++idx;
}
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
break;
}
case coEnum:{
int val = boost::any_cast<int>(value);
if (m_opt_id.compare("external_fill_pattern") == 0)
{
if (!m_opt.enum_values.empty()){
std::string key;
t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
for (auto it : map_names) {
if (val == it.second) {
key = it.first;
break;
}
}
size_t idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(key) == 0)
break;
++idx;
}
val = idx == m_opt.enum_values.size() ? 0 : idx;
}
else
val = 0;
}
dynamic_cast<wxComboBox*>(window)->SetSelection(val);
break;
}
default:
break;
}
m_disable_change_event = false;
}
//! it's needed for _update_serial_ports()
void Choice::set_values(const std::vector<std::string>& values)
{
if (values.empty())
return;
m_disable_change_event = true;
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
// # but we want to preserve it
auto ww = dynamic_cast<wxComboBox*>(window);
auto value = ww->GetValue();
ww->Clear();
ww->Append("");
for (auto el : values)
ww->Append(wxString(el));
ww->SetValue(value);
m_disable_change_event = false;
}
boost::any& Choice::get_value()
{
// boost::any m_value;
wxString ret_str = static_cast<wxComboBox*>(window)->GetValue();
// options from right panel
std::vector <std::string> right_panel_options{ "support", "scale_unit" };
for (auto rp_option: right_panel_options)
if (m_opt_id == rp_option)
return m_value = boost::any(ret_str);
if (m_opt.type != coEnum)
/*m_value = */get_value_by_opt_type(ret_str);
else
{
int ret_enum = static_cast<wxComboBox*>(window)->GetSelection();
if (m_opt_id.compare("external_fill_pattern") == 0)
{
if (!m_opt.enum_values.empty()){
std::string key = m_opt.enum_values[ret_enum];
t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
int value = map_names.at(key);
m_value = static_cast<InfillPattern>(value);
}
else
m_value = static_cast<InfillPattern>(0);
}
if (m_opt_id.compare("fill_pattern") == 0)
m_value = static_cast<InfillPattern>(ret_enum);
else if (m_opt_id.compare("gcode_flavor") == 0)
m_value = static_cast<GCodeFlavor>(ret_enum);
else if (m_opt_id.compare("support_material_pattern") == 0)
m_value = static_cast<SupportMaterialPattern>(ret_enum);
else if (m_opt_id.compare("seam_position") == 0)
m_value = static_cast<SeamPosition>(ret_enum);
else if (m_opt_id.compare("host_type") == 0)
m_value = static_cast<PrintHostType>(ret_enum);
}
return m_value;
}
void ColourPicker::BUILD()
{
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxString clr(static_cast<const ConfigOptionStrings*>(m_opt.default_value)->get_at(m_opt_idx));
auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
// // recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
temp->Bind(wxEVT_COLOURPICKER_CHANGED, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
temp->SetToolTip(get_tooltip_text(clr));
}
boost::any& ColourPicker::get_value(){
// boost::any m_value;
auto colour = static_cast<wxColourPickerCtrl*>(window)->GetColour();
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue());
m_value = clr_str.ToStdString();
return m_value;
}
void PointCtrl::BUILD()
{
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
auto temp = new wxBoxSizer(wxHORIZONTAL);
// $self->wxSizer($sizer);
//
wxSize field_size(40, -1);
auto default_pt = static_cast<const ConfigOptionPoints*>(m_opt.default_value)->values.at(0);
double val = default_pt(0);
wxString X = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
val = default_pt(1);
wxString Y = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
x_textctrl = new wxTextCtrl(m_parent, wxID_ANY, X, wxDefaultPosition, field_size);
y_textctrl = new wxTextCtrl(m_parent, wxID_ANY, Y, wxDefaultPosition, field_size);
temp->Add(new wxStaticText(m_parent, wxID_ANY, "x : "), 0, wxALIGN_CENTER_VERTICAL, 0);
temp->Add(x_textctrl);
temp->Add(new wxStaticText(m_parent, wxID_ANY, " y : "), 0, wxALIGN_CENTER_VERTICAL, 0);
temp->Add(y_textctrl);
x_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), x_textctrl->GetId());
y_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), y_textctrl->GetId());
// // recast as a wxWindow to fit the calling convention
sizer = dynamic_cast<wxSizer*>(temp);
x_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
}
void PointCtrl::set_value(const Vec2d& value, bool change_event)
{
m_disable_change_event = !change_event;
double val = value(0);
x_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
val = value(1);
y_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
m_disable_change_event = false;
}
void PointCtrl::set_value(const boost::any& value, bool change_event)
{
Vec2d pt(Vec2d::Zero());
const Vec2d *ptf = boost::any_cast<Vec2d>(&value);
if (!ptf)
{
ConfigOptionPoints* pts = boost::any_cast<ConfigOptionPoints*>(value);
pt = pts->values.at(0);
}
else
pt = *ptf;
set_value(pt, change_event);
}
boost::any& PointCtrl::get_value()
{
double x, y;
x_textctrl->GetValue().ToDouble(&x);
y_textctrl->GetValue().ToDouble(&y);
return m_value = Vec2d(x, y);
}
void StaticText::BUILD()
{
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxString legend(static_cast<const ConfigOptionString*>(m_opt.default_value)->value);
auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size);
temp->SetFont(bold_font());
// // recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
temp->SetToolTip(get_tooltip_text(legend));
}
void SliderCtrl::BUILD()
{
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
auto temp = new wxBoxSizer(wxHORIZONTAL);
auto def_val = static_cast<const ConfigOptionInt*>(m_opt.default_value)->value;
auto min = m_opt.min == INT_MIN ? 0 : m_opt.min;
auto max = m_opt.max == INT_MAX ? 100 : m_opt.max;
m_slider = new wxSlider(m_parent, wxID_ANY, def_val * m_scale,
min * m_scale, max * m_scale,
wxDefaultPosition, size);
wxSize field_size(40, -1);
m_textctrl = new wxTextCtrl(m_parent, wxID_ANY, wxString::Format("%d", m_slider->GetValue()/m_scale),
wxDefaultPosition, field_size);
temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0);
temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
m_slider->Bind(wxEVT_SLIDER, ([this](wxCommandEvent e) {
if (!m_disable_change_event){
int val = boost::any_cast<int>(get_value());
m_textctrl->SetLabel(wxString::Format("%d", val));
on_change_field();
}
}), m_slider->GetId());
m_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) {
std::string value = e.GetString().utf8_str().data();
if (is_matched(value, "^-?\\d+(\\.\\d*)?$")){
m_disable_change_event = true;
m_slider->SetValue(stoi(value)*m_scale);
m_disable_change_event = false;
on_change_field();
}
}), m_textctrl->GetId());
m_sizer = dynamic_cast<wxSizer*>(temp);
}
void SliderCtrl::set_value(const boost::any& value, bool change_event)
{
m_disable_change_event = !change_event;
m_slider->SetValue(boost::any_cast<int>(value)*m_scale);
int val = boost::any_cast<int>(get_value());
m_textctrl->SetLabel(wxString::Format("%d", val));
m_disable_change_event = false;
}
boost::any& SliderCtrl::get_value()
{
// int ret_val;
// x_textctrl->GetValue().ToDouble(&val);
return m_value = int(m_slider->GetValue()/m_scale);
}
} // GUI
} // Slic3r

466
src/slic3r/GUI/Field.hpp Normal file
View file

@ -0,0 +1,466 @@
#ifndef SLIC3R_GUI_FIELD_HPP
#define SLIC3R_GUI_FIELD_HPP
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <memory>
#include <functional>
#include <boost/any.hpp>
#include <wx/spinctrl.h>
#include <wx/clrpicker.h>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Config.hpp"
//#include "slic3r_gui.hpp"
#include "GUI.hpp"
#include "Utils.hpp"
#ifdef __WXMSW__
#define wxMSW true
#else
#define wxMSW false
#endif
namespace Slic3r { namespace GUI {
class Field;
using t_field = std::unique_ptr<Field>;
using t_kill_focus = std::function<void()>;
using t_change = std::function<void(t_config_option_key, const boost::any&)>;
using t_back_to_init = std::function<void(const std::string&)>;
wxString double_to_string(double const value);
class MyButton : public wxButton
{
bool hidden = false; // never show button if it's hidden ones
public:
MyButton() {}
MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize, long style = 0,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxTextCtrlNameStr)
{
this->Create(parent, id, label, pos, size, style, validator, name);
}
// overridden from wxWindow base class
virtual bool
AcceptsFocusFromKeyboard() const { return false; }
virtual bool Show(bool show = true) override {
if (!show)
hidden = true;
return wxButton::Show(!hidden);
}
};
class Field {
protected:
// factory function to defer and enforce creation of derived type.
virtual void PostInitialize();
/// Finish constructing the Field's wxWidget-related properties, including setting its own sizer, etc.
virtual void BUILD() = 0;
/// Call the attached on_kill_focus method.
//! It's important to use wxEvent instead of wxFocusEvent,
//! in another case we can't unfocused control at all
void on_kill_focus(wxEvent& event);
/// Call the attached on_change method.
void on_change_field();
/// Call the attached m_back_to_initial_value method.
void on_back_to_initial_value();
/// Call the attached m_back_to_sys_value method.
void on_back_to_sys_value();
public:
/// parent wx item, opportunity to refactor (probably not necessary - data duplication)
wxWindow* m_parent {nullptr};
/// Function object to store callback passed in from owning object.
t_kill_focus m_on_kill_focus {nullptr};
/// Function object to store callback passed in from owning object.
t_change m_on_change {nullptr};
/// Function object to store callback passed in from owning object.
t_back_to_init m_back_to_initial_value{ nullptr };
t_back_to_init m_back_to_sys_value{ nullptr };
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
bool m_disable_change_event {false};
bool m_is_modified_value {false};
bool m_is_nonsys_value {true};
/// Copy of ConfigOption for deduction purposes
const ConfigOptionDef m_opt {ConfigOptionDef()};
const t_config_option_key m_opt_id;//! {""};
int m_opt_idx = 0;
/// Sets a value for this control.
/// subclasses should overload with a specific version
/// Postcondition: Method does not fire the on_change event.
virtual void set_value(const boost::any& value, bool change_event) = 0;
/// Gets a boost::any representing this control.
/// subclasses should overload with a specific version
virtual boost::any& get_value() = 0;
virtual void enable() = 0;
virtual void disable() = 0;
/// Fires the enable or disable function, based on the input.
inline void toggle(bool en) { en ? enable() : disable(); }
virtual wxString get_tooltip_text(const wxString& default_string);
// set icon to "UndoToSystemValue" button according to an inheritance of preset
// void set_nonsys_btn_icon(const wxBitmap& icon);
Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {};
Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {};
/// If you don't know what you are getting back, check both methods for nullptr.
virtual wxSizer* getSizer() { return nullptr; }
virtual wxWindow* getWindow() { return nullptr; }
bool is_matched(const std::string& string, const std::string& pattern);
void get_value_by_opt_type(wxString& str);
/// Factory method for generating new derived classes.
template<class T>
static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) // interface for creating shared objects
{
auto p = Slic3r::make_unique<T>(parent, opt, id);
p->PostInitialize();
return std::move(p); //!p;
}
bool set_undo_bitmap(const wxBitmap *bmp) {
if (m_undo_bitmap != bmp) {
m_undo_bitmap = bmp;
m_Undo_btn->SetBitmap(*bmp);
return true;
}
return false;
}
bool set_undo_to_sys_bitmap(const wxBitmap *bmp) {
if (m_undo_to_sys_bitmap != bmp) {
m_undo_to_sys_bitmap = bmp;
m_Undo_to_sys_btn->SetBitmap(*bmp);
return true;
}
return false;
}
bool set_label_colour(const wxColour *clr) {
if (m_Label == nullptr) return false;
if (m_label_color != clr) {
m_label_color = clr;
m_Label->SetForegroundColour(*clr);
m_Label->Refresh(true);
}
return false;
}
bool set_label_colour_force(const wxColour *clr) {
if (m_Label == nullptr) return false;
m_Label->SetForegroundColour(*clr);
m_Label->Refresh(true);
return false;
}
bool set_undo_tooltip(const wxString *tip) {
if (m_undo_tooltip != tip) {
m_undo_tooltip = tip;
m_Undo_btn->SetToolTip(*tip);
return true;
}
return false;
}
bool set_undo_to_sys_tooltip(const wxString *tip) {
if (m_undo_to_sys_tooltip != tip) {
m_undo_to_sys_tooltip = tip;
m_Undo_to_sys_btn->SetToolTip(*tip);
return true;
}
return false;
}
void set_side_text_ptr(wxStaticText* side_text) {
m_side_text = side_text;
}
protected:
MyButton* m_Undo_btn = nullptr;
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const wxBitmap* m_undo_bitmap = nullptr;
const wxString* m_undo_tooltip = nullptr;
MyButton* m_Undo_to_sys_btn = nullptr;
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const wxBitmap* m_undo_to_sys_bitmap = nullptr;
const wxString* m_undo_to_sys_tooltip = nullptr;
wxStaticText* m_Label = nullptr;
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_label_color = nullptr;
wxStaticText* m_side_text = nullptr;
// current value
boost::any m_value;
friend class OptionsGroup;
};
/// Convenience function, accepts a const reference to t_field and checks to see whether
/// or not both wx pointers are null.
inline bool is_bad_field(const t_field& obj) { return obj->getSizer() == nullptr && obj->getWindow() == nullptr; }
/// Covenience function to determine whether this field is a valid window field.
inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr && obj->getSizer() == nullptr; }
/// Covenience function to determine whether this field is a valid sizer field.
inline bool is_sizer_field(const t_field& obj) { return !is_bad_field(obj) && obj->getSizer() != nullptr; }
class TextCtrl : public Field {
using Field::Field;
#ifdef __WXGTK__
bool bChangedValueEvent = true;
void change_field_value(wxEvent& event);
#endif //__WXGTK__
public:
TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~TextCtrl() {}
void BUILD();
wxWindow* window {nullptr};
virtual void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxTextCtrl*>(window)->SetValue(wxString(value));
m_disable_change_event = false;
}
virtual void set_value(const boost::any& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value));
m_disable_change_event = false;
}
boost::any& get_value() override;
virtual void enable();
virtual void disable();
virtual wxWindow* getWindow() { return window; }
};
class CheckBox : public Field {
using Field::Field;
public:
CheckBox(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
CheckBox(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~CheckBox() {}
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const bool value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxCheckBox*>(window)->SetValue(value);
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value));
m_disable_change_event = false;
}
boost::any& get_value() override;
void enable() override { dynamic_cast<wxCheckBox*>(window)->Enable(); }
void disable() override { dynamic_cast<wxCheckBox*>(window)->Disable(); }
wxWindow* getWindow() override { return window; }
};
class SpinCtrl : public Field {
using Field::Field;
public:
SpinCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id), tmp_value(-9999) {}
SpinCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id), tmp_value(-9999) {}
~SpinCtrl() {}
int tmp_value;
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) {
m_disable_change_event = !change_event;
tmp_value = boost::any_cast<int>(value);
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
m_disable_change_event = false;
}
boost::any& get_value() override {
// return boost::any(tmp_value);
return m_value = tmp_value;
}
void enable() override { dynamic_cast<wxSpinCtrl*>(window)->Enable(); }
void disable() override { dynamic_cast<wxSpinCtrl*>(window)->Disable(); }
wxWindow* getWindow() override { return window; }
};
class Choice : public Field {
using Field::Field;
public:
Choice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
Choice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~Choice() {}
wxWindow* window{ nullptr };
void BUILD() override;
void set_selection();
void set_value(const std::string& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false);
void set_values(const std::vector<std::string> &values);
boost::any& get_value() override;
void enable() override { dynamic_cast<wxComboBox*>(window)->Enable(); };
void disable() override{ dynamic_cast<wxComboBox*>(window)->Disable(); };
wxWindow* getWindow() override { return window; }
};
class ColourPicker : public Field {
using Field::Field;
public:
ColourPicker(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
ColourPicker(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~ColourPicker() {}
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(value);
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(boost::any_cast<wxString>(value));
m_disable_change_event = false;
}
boost::any& get_value() override;
void enable() override { dynamic_cast<wxColourPickerCtrl*>(window)->Enable(); };
void disable() override{ dynamic_cast<wxColourPickerCtrl*>(window)->Disable(); };
wxWindow* getWindow() override { return window; }
};
class PointCtrl : public Field {
using Field::Field;
public:
PointCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
PointCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~PointCtrl() {}
wxSizer* sizer{ nullptr };
wxTextCtrl* x_textctrl{ nullptr };
wxTextCtrl* y_textctrl{ nullptr };
void BUILD() override;
void set_value(const Vec2d& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false);
boost::any& get_value() override;
void enable() override {
x_textctrl->Enable();
y_textctrl->Enable(); }
void disable() override{
x_textctrl->Disable();
y_textctrl->Disable(); }
wxSizer* getSizer() override { return sizer; }
};
class StaticText : public Field {
using Field::Field;
public:
StaticText(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
StaticText(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~StaticText() {}
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxStaticText*>(window)->SetLabel(value);
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxStaticText*>(window)->SetLabel(boost::any_cast<wxString>(value));
m_disable_change_event = false;
}
boost::any& get_value()override { return m_value; }
void enable() override { dynamic_cast<wxStaticText*>(window)->Enable(); };
void disable() override{ dynamic_cast<wxStaticText*>(window)->Disable(); };
wxWindow* getWindow() override { return window; }
};
class SliderCtrl : public Field {
using Field::Field;
public:
SliderCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
SliderCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~SliderCtrl() {}
wxSizer* m_sizer{ nullptr };
wxTextCtrl* m_textctrl{ nullptr };
wxSlider* m_slider{ nullptr };
int m_scale = 10;
void BUILD() override;
void set_value(const int value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false);
boost::any& get_value() override;
void enable() override {
m_slider->Enable();
m_textctrl->Enable();
m_textctrl->SetEditable(true);
}
void disable() override{
m_slider->Disable();
m_textctrl->Disable();
m_textctrl->SetEditable(false);
}
wxSizer* getSizer() override { return m_sizer; }
wxWindow* getWindow() override { return dynamic_cast<wxWindow*>(m_slider); }
};
} // GUI
} // Slic3r
#endif /* SLIC3R_GUI_FIELD_HPP */

View file

@ -0,0 +1,846 @@
#include <numeric>
#include <algorithm>
#include <thread>
#include <condition_variable>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <boost/optional.hpp>
#include "libslic3r/Utils.hpp"
#include "avrdude/avrdude-slic3r.hpp"
#include "GUI.hpp"
#include "MsgDialog.hpp"
#include "../Utils/HexFile.hpp"
#include "../Utils/Serial.hpp"
// wx includes need to come after asio because of the WinSock.h problem
#include "FirmwareDialog.hpp"
#include <wx/app.h>
#include <wx/event.h>
#include <wx/sizer.h>
#include <wx/settings.h>
#include <wx/timer.h>
#include <wx/panel.h>
#include <wx/button.h>
#include <wx/filepicker.h>
#include <wx/textctrl.h>
#include <wx/stattext.h>
#include <wx/combobox.h>
#include <wx/gauge.h>
#include <wx/collpane.h>
#include <wx/msgdlg.h>
#include <wx/filefn.h>
namespace fs = boost::filesystem;
namespace asio = boost::asio;
using boost::system::error_code;
using boost::optional;
namespace Slic3r {
using Utils::HexFile;
using Utils::SerialPortInfo;
using Utils::Serial;
// USB IDs used to perform device lookup
enum {
USB_VID_PRUSA = 0x2c99,
USB_PID_MK2 = 1,
USB_PID_MK3 = 2,
USB_PID_MMU_BOOT = 3,
USB_PID_MMU_APP = 4,
};
// This enum discriminates the kind of information in EVT_AVRDUDE,
// it's stored in the ExtraLong field of wxCommandEvent.
enum AvrdudeEvent
{
AE_MESSAGE,
AE_PROGRESS,
AE_STATUS,
AE_EXIT,
};
wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent);
wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent);
wxDECLARE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
wxDEFINE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
// Private
struct FirmwareDialog::priv
{
enum AvrDudeComplete
{
AC_NONE,
AC_SUCCESS,
AC_FAILURE,
AC_USER_CANCELLED,
};
FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer")
// GUI elements
wxComboBox *port_picker;
wxStaticText *port_autodetect;
wxFilePickerCtrl *hex_picker;
wxStaticText *txt_status;
wxGauge *progressbar;
wxCollapsiblePane *spoiler;
wxTextCtrl *txt_stdout;
wxButton *btn_rescan;
wxButton *btn_close;
wxButton *btn_flash;
wxString btn_flash_label_ready;
wxString btn_flash_label_flashing;
wxString label_status_flashing;
wxTimer timer_pulse;
// Async modal dialog during flashing
std::mutex mutex;
int modal_response;
std::condition_variable response_cv;
// Data
std::vector<SerialPortInfo> ports;
optional<SerialPortInfo> port;
HexFile hex_file;
// This is a shared pointer holding the background AvrDude task
// also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset).
AvrDude::Ptr avrdude;
std::string avrdude_config;
unsigned progress_tasks_done;
unsigned progress_tasks_bar;
bool user_cancelled;
const bool extra_verbose; // For debugging
priv(FirmwareDialog *q) :
q(q),
btn_flash_label_ready(_(L("Flash!"))),
btn_flash_label_flashing(_(L("Cancel"))),
label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))),
timer_pulse(q),
avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()),
progress_tasks_done(0),
progress_tasks_bar(0),
user_cancelled(false),
extra_verbose(false)
{}
void find_serial_ports();
void fit_no_shrink();
void set_txt_status(const wxString &label);
void flashing_start(unsigned tasks);
void flashing_done(AvrDudeComplete complete);
void enable_port_picker(bool enable);
void load_hex_file(const wxString &path);
void queue_status(wxString message);
void queue_error(const wxString &message);
bool ask_model_id_mismatch(const std::string &printer_model);
bool check_model_id();
void wait_for_mmu_bootloader(unsigned retries);
void mmu_reboot(const SerialPortInfo &port);
void lookup_port_mmu();
void prepare_common();
void prepare_mk2();
void prepare_mk3();
void prepare_mm_control();
void perform_upload();
void user_cancel();
void on_avrdude(const wxCommandEvent &evt);
void on_async_dialog(const wxCommandEvent &evt);
void ensure_joined();
};
void FirmwareDialog::priv::find_serial_ports()
{
auto new_ports = Utils::scan_serial_ports_extended();
if (new_ports != this->ports) {
this->ports = new_ports;
port_picker->Clear();
for (const auto &port : this->ports)
port_picker->Append(wxString::FromUTF8(port.friendly_name.data()));
if (ports.size() > 0) {
int idx = port_picker->GetValue().IsEmpty() ? 0 : -1;
for (int i = 0; i < (int)this->ports.size(); ++ i)
if (this->ports[i].is_printer) {
idx = i;
break;
}
if (idx != -1)
port_picker->SetSelection(idx);
}
}
}
void FirmwareDialog::priv::fit_no_shrink()
{
// Ensure content fits into window and window is not shrinked
const auto old_size = q->GetSize();
q->Layout();
q->Fit();
const auto new_size = q->GetSize();
const auto new_width = std::max(old_size.GetWidth(), new_size.GetWidth());
const auto new_height = std::max(old_size.GetHeight(), new_size.GetHeight());
q->SetSize(new_width, new_height);
}
void FirmwareDialog::priv::set_txt_status(const wxString &label)
{
const auto width = txt_status->GetSize().GetWidth();
txt_status->SetLabel(label);
txt_status->Wrap(width);
fit_no_shrink();
}
void FirmwareDialog::priv::flashing_start(unsigned tasks)
{
modal_response = wxID_NONE;
txt_stdout->Clear();
set_txt_status(label_status_flashing);
txt_status->SetForegroundColour(GUI::get_label_clr_modified());
port_picker->Disable();
btn_rescan->Disable();
hex_picker->Disable();
btn_close->Disable();
btn_flash->SetLabel(btn_flash_label_flashing);
progressbar->SetRange(200 * tasks); // See progress callback below
progressbar->SetValue(0);
progress_tasks_done = 0;
progress_tasks_bar = 0;
user_cancelled = false;
timer_pulse.Start(50);
}
void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
{
auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
port_picker->Enable();
btn_rescan->Enable();
hex_picker->Enable();
btn_close->Enable();
btn_flash->SetLabel(btn_flash_label_ready);
txt_status->SetForegroundColour(text_color);
timer_pulse.Stop();
progressbar->SetValue(progressbar->GetRange());
switch (complete) {
case AC_SUCCESS: set_txt_status(_(L("Flashing succeeded!"))); break;
case AC_FAILURE: set_txt_status(_(L("Flashing failed. Please see the avrdude log below."))); break;
case AC_USER_CANCELLED: set_txt_status(_(L("Flashing cancelled."))); break;
default: break;
}
}
void FirmwareDialog::priv::enable_port_picker(bool enable)
{
port_picker->Show(enable);
btn_rescan->Show(enable);
port_autodetect->Show(! enable);
q->Layout();
fit_no_shrink();
}
void FirmwareDialog::priv::load_hex_file(const wxString &path)
{
hex_file = HexFile(path.wx_str());
enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL);
}
void FirmwareDialog::priv::queue_status(wxString message)
{
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_STATUS);
evt->SetString(std::move(message));
wxQueueEvent(this->q, evt);
}
void FirmwareDialog::priv::queue_error(const wxString &message)
{
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_STATUS);
evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message));
wxQueueEvent(this->q, evt); avrdude->cancel();
}
bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model)
{
// model_id in the hex file doesn't match what the printer repoted.
// Ask the user if it should be flashed anyway.
std::unique_lock<std::mutex> lock(mutex);
auto evt = new wxCommandEvent(EVT_ASYNC_DIALOG, this->q->GetId());
evt->SetString(wxString::Format(_(L(
"This firmware hex file does not match the printer model.\n"
"The hex file is intended for: %s\n"
"Printer reported: %s\n\n"
"Do you want to continue and flash this hex file anyway?\n"
"Please only continue if you are sure this is the right thing to do.")),
hex_file.model_id, printer_model
));
wxQueueEvent(this->q, evt);
response_cv.wait(lock, [this]() { return this->modal_response != wxID_NONE; });
if (modal_response == wxID_YES) {
return true;
} else {
user_cancel();
return false;
}
}
bool FirmwareDialog::priv::check_model_id()
{
// XXX: The implementation in Serial doesn't currently work reliably enough to be used.
// Therefore, regretably, so far the check cannot be used and we just return true here.
// TODO: Rewrite Serial using more platform-native code.
return true;
// if (hex_file.model_id.empty()) {
// // No data to check against, assume it's ok
// return true;
// }
// asio::io_service io;
// Serial serial(io, port->port, 115200);
// serial.printer_setup();
// enum {
// TIMEOUT = 2000,
// RETREIES = 5,
// };
// if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) {
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
// return false;
// }
// std::string line;
// error_code ec;
// serial.printer_write_line("PRUSA Rev");
// while (serial.read_line(TIMEOUT, line, ec)) {
// if (ec) {
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
// return false;
// }
// if (line == "ok") { continue; }
// if (line == hex_file.model_id) {
// return true;
// } else {
// return ask_model_id_mismatch(line);
// }
// line.clear();
// }
// return false;
}
void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
{
enum {
SLEEP_MS = 500,
};
for (unsigned i = 0; i < retries && !user_cancelled; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_MS));
auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT;
}), ports.end());
if (ports.size() == 1) {
port = ports[0];
return;
} else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
return;
}
}
}
void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
{
asio::io_service io;
Serial serial(io, port.port, 1200);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
void FirmwareDialog::priv::lookup_port_mmu()
{
static const auto msg_not_found =
"The Multi Material Control device was not found.\n"
"If the device is connected, please press the Reset button next to the USB connector ...";
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
return port.id_vendor != USB_VID_PRUSA ||
port.id_product != USB_PID_MMU_BOOT &&
port.id_product != USB_PID_MMU_APP;
}), ports.end());
if (ports.size() == 0) {
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(msg_not_found)));
wait_for_mmu_bootloader(30);
} else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
} else {
if (ports[0].id_product == USB_PID_MMU_APP) {
// The device needs to be rebooted into the bootloader mode
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
mmu_reboot(ports[0]);
wait_for_mmu_bootloader(10);
if (! port) {
// The device in bootloader mode was not found, inform the user and wait some more...
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(msg_not_found)));
wait_for_mmu_bootloader(30);
}
} else {
port = ports[0];
}
}
}
void FirmwareDialog::priv::prepare_common()
{
std::vector<std::string> args {{
extra_verbose ? "-vvvvv" : "-v",
"-p", "atmega2560",
// Using the "Wiring" mode to program Rambo or Einsy, using the STK500v2 protocol (not the STK500).
// The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip
// is flashed with a buggy firmware.
"-c", "wiring",
"-P", port->port,
"-b", "115200", // TODO: Allow other rates? Ditto elsewhere.
"-D",
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
}};
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
return a + ' ' + b;
});
avrdude->push_args(std::move(args));
}
void FirmwareDialog::priv::prepare_mk2()
{
if (! port) { return; }
if (! check_model_id()) {
avrdude->cancel();
return;
}
prepare_common();
}
void FirmwareDialog::priv::prepare_mk3()
{
if (! port) { return; }
if (! check_model_id()) {
avrdude->cancel();
return;
}
prepare_common();
// The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy)
// This is done via another avrdude invocation, here we build arg list for that:
std::vector<std::string> args {{
extra_verbose ? "-vvvvv" : "-v",
"-p", "atmega2560",
// Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2).
// The Prusa's avrdude is patched again to never send semicolons inside the data packets.
"-c", "arduino",
"-P", port->port,
"-b", "115200",
"-D",
"-u", // disable safe mode
"-U", (boost::format("flash:w:1:%1%:i") % hex_file.path.string()).str(),
}};
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: "
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
return a + ' ' + b;
});
avrdude->push_args(std::move(args));
}
void FirmwareDialog::priv::prepare_mm_control()
{
port = boost::none;
lookup_port_mmu();
if (! port) {
queue_error(_(L("The device could not have been found")));
return;
}
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port->port;
queue_status(label_status_flashing);
std::vector<std::string> args {{
extra_verbose ? "-vvvvv" : "-v",
"-p", "atmega32u4",
"-c", "avr109",
"-P", port->port,
"-b", "57600",
"-D",
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
}};
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
return a + ' ' + b;
});
avrdude->push_args(std::move(args));
}
void FirmwareDialog::priv::perform_upload()
{
auto filename = hex_picker->GetPath();
if (filename.IsEmpty()) { return; }
load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh
int selection = port_picker->GetSelection();
if (selection != wxNOT_FOUND) {
port = this->ports[selection];
// Verify whether the combo box list selection equals to the combo box edit value.
if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) {
return;
}
}
const bool extra_verbose = false; // For debugging
flashing_start(hex_file.device == HexFile::DEV_MK3 ? 2 : 1);
// Init the avrdude object
AvrDude avrdude(avrdude_config);
// It is ok here to use the q-pointer to the FirmwareDialog
// because the dialog ensures it doesn't exit before the background thread is done.
auto q = this->q;
avrdude
.on_run([this](AvrDude::Ptr avrdude) {
this->avrdude = std::move(avrdude);
try {
switch (this->hex_file.device) {
case HexFile::DEV_MK3:
this->prepare_mk3();
break;
case HexFile::DEV_MM_CONTROL:
this->prepare_mm_control();
break;
default:
this->prepare_mk2();
break;
}
} catch (const std::exception &ex) {
queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what()));
}
})
.on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) {
if (extra_verbose) {
BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg;
}
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
auto wxmsg = wxString::FromUTF8(msg);
evt->SetExtraLong(AE_MESSAGE);
evt->SetString(std::move(wxmsg));
wxQueueEvent(q, evt);
}))
.on_progress(std::move([q](const char * /* task */, unsigned progress) {
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
evt->SetExtraLong(AE_PROGRESS);
evt->SetInt(progress);
wxQueueEvent(q, evt);
}))
.on_complete(std::move([this]() {
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_EXIT);
evt->SetInt(this->avrdude->exit_code());
wxQueueEvent(this->q, evt);
}))
.run();
}
void FirmwareDialog::priv::user_cancel()
{
if (avrdude) {
user_cancelled = true;
avrdude->cancel();
}
}
void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
{
AvrDudeComplete complete_kind;
switch (evt.GetExtraLong()) {
case AE_MESSAGE:
txt_stdout->AppendText(evt.GetString());
break;
case AE_PROGRESS:
// We try to track overall progress here.
// Avrdude performs 3 tasks per one memory operation ("-U" arg),
// first of which is reading of status data (very short).
// We use the timer_pulse during the very first task to indicate intialization
// and then display overall progress during the latter tasks.
if (progress_tasks_done > 0) {
progressbar->SetValue(progress_tasks_bar + evt.GetInt());
}
if (evt.GetInt() == 100) {
timer_pulse.Stop();
if (progress_tasks_done % 3 != 0) {
progress_tasks_bar += 100;
}
progress_tasks_done++;
}
break;
case AE_EXIT:
BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt();
// Figure out the exit state
if (user_cancelled) { complete_kind = AC_USER_CANCELLED; }
else if (avrdude->cancelled()) { complete_kind = AC_NONE; } // Ie. cancelled programatically
else { complete_kind = evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE; }
flashing_done(complete_kind);
ensure_joined();
break;
case AE_STATUS:
set_txt_status(evt.GetString());
break;
default:
break;
}
}
void FirmwareDialog::priv::on_async_dialog(const wxCommandEvent &evt)
{
wxMessageDialog dlg(this->q, evt.GetString(), wxMessageBoxCaptionStr, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
{
std::lock_guard<std::mutex> lock(mutex);
modal_response = dlg.ShowModal();
}
response_cv.notify_all();
}
void FirmwareDialog::priv::ensure_joined()
{
// Make sure the background thread is collected and the AvrDude object reset
if (avrdude) { avrdude->join(); }
avrdude.reset();
}
// Public
FirmwareDialog::FirmwareDialog(wxWindow *parent) :
wxDialog(parent, wxID_ANY, _(L("Firmware flasher")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
p(new priv(this))
{
enum {
DIALOG_MARGIN = 15,
SPACING = 10,
MIN_WIDTH = 600,
MIN_HEIGHT = 200,
MIN_HEIGHT_EXPANDED = 500,
};
wxFont status_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
status_font.MakeBold();
wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
mono_font.MakeSmaller();
// Create GUI components and layout
auto *panel = new wxPanel(this);
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(vsizer);
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr,
"Hex files (*.hex)|*.hex|All files|*.*");
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY);
p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
port_sizer->Add(p->btn_rescan, 0);
port_sizer->Add(p->port_autodetect, 1, wxEXPAND);
p->enable_port_picker(true);
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:")));
p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready")));
p->txt_status->SetFont(status_font);
auto *grid = new wxFlexGridSizer(2, SPACING, SPACING);
grid->AddGrowableCol(1);
grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->hex_picker, 0, wxEXPAND);
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(port_sizer, 0, wxEXPAND);
grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->txt_status, 0, wxEXPAND);
vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING);
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE);
auto *spoiler_pane = p->spoiler->GetPane();
auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL);
p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
p->txt_stdout->SetFont(mono_font);
spoiler_sizer->Add(p->txt_stdout, 1, wxEXPAND);
spoiler_pane->SetSizer(spoiler_sizer);
// The doc says proportion need to be 0 for wxCollapsiblePane.
// Experience says it needs to be 1, otherwise things won't get sized properly.
vsizer->Add(p->spoiler, 1, wxEXPAND | wxBOTTOM, SPACING);
p->btn_close = new wxButton(panel, wxID_CLOSE);
p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready);
p->btn_flash->Disable();
auto *bsizer = new wxBoxSizer(wxHORIZONTAL);
bsizer->Add(p->btn_close);
bsizer->AddStretchSpacer();
bsizer->Add(p->btn_flash);
vsizer->Add(bsizer, 0, wxEXPAND);
auto *topsizer = new wxBoxSizer(wxVERTICAL);
topsizer->Add(panel, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
SetSizerAndFit(topsizer);
const auto size = GetSize();
SetSize(std::max(size.GetWidth(), static_cast<int>(MIN_WIDTH)), std::max(size.GetHeight(), static_cast<int>(MIN_HEIGHT)));
Layout();
// Bind events
p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
if (wxFileExists(evt.GetPath())) {
this->p->load_hex_file(evt.GetPath());
this->p->btn_flash->Enable();
}
});
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) {
if (evt.GetCollapsed()) {
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight();
this->SetSize(this->GetSize().GetWidth(), new_height);
} else {
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED));
}
this->Layout();
this->p->fit_no_shrink();
});
p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); });
p->btn_rescan->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->p->find_serial_ports(); });
p->btn_flash->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
if (this->p->avrdude) {
// Flashing is in progress, ask the user if they're really sure about canceling it
wxMessageDialog dlg(this,
_(L("Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!")),
_(L("Confirmation")),
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
if (dlg.ShowModal() == wxID_YES) {
this->p->set_txt_status(_(L("Cancelling...")));
this->p->user_cancel();
}
} else {
// Start a flashing task
this->p->perform_upload();
}
});
Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); });
Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); });
Bind(EVT_ASYNC_DIALOG, [this](wxCommandEvent &evt) { this->p->on_async_dialog(evt); });
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
if (this->p->avrdude) {
evt.Veto();
} else {
evt.Skip();
}
});
p->find_serial_ports();
}
FirmwareDialog::~FirmwareDialog()
{
// Needed bacuse of forward defs
}
void FirmwareDialog::run(wxWindow *parent)
{
FirmwareDialog dialog(parent);
dialog.ShowModal();
}
}

View file

@ -0,0 +1,31 @@
#ifndef slic3r_FirmwareDialog_hpp_
#define slic3r_FirmwareDialog_hpp_
#include <memory>
#include <wx/dialog.h>
namespace Slic3r {
class FirmwareDialog: public wxDialog
{
public:
FirmwareDialog(wxWindow *parent);
FirmwareDialog(FirmwareDialog &&) = delete;
FirmwareDialog(const FirmwareDialog &) = delete;
FirmwareDialog &operator=(FirmwareDialog &&) = delete;
FirmwareDialog &operator=(const FirmwareDialog &) = delete;
~FirmwareDialog();
static void run(wxWindow *parent);
private:
struct priv;
std::unique_ptr<priv> p;
};
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,765 @@
#ifndef slic3r_GLCanvas3D_hpp_
#define slic3r_GLCanvas3D_hpp_
#include "../../slic3r/GUI/3DScene.hpp"
#include "../../slic3r/GUI/GLToolbar.hpp"
class wxTimer;
class wxSizeEvent;
class wxIdleEvent;
class wxKeyEvent;
class wxMouseEvent;
class wxTimerEvent;
class wxPaintEvent;
namespace Slic3r {
class GLShader;
class ExPolygon;
namespace GUI {
class GLGizmoBase;
class GeometryBuffer
{
std::vector<float> m_vertices;
std::vector<float> m_tex_coords;
public:
bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords);
bool set_from_lines(const Lines& lines, float z);
const float* get_vertices() const;
const float* get_tex_coords() const;
unsigned int get_vertices_count() const;
};
class Size
{
int m_width;
int m_height;
public:
Size();
Size(int width, int height);
int get_width() const;
void set_width(int width);
int get_height() const;
void set_height(int height);
};
class Rect
{
float m_left;
float m_top;
float m_right;
float m_bottom;
public:
Rect();
Rect(float left, float top, float right, float bottom);
float get_left() const;
void set_left(float left);
float get_top() const;
void set_top(float top);
float get_right() const;
void set_right(float right);
float get_bottom() const;
void set_bottom(float bottom);
};
class GLCanvas3D
{
struct GCodePreviewVolumeIndex
{
enum EType
{
Extrusion,
Travel,
Retraction,
Unretraction,
Shell,
Num_Geometry_Types
};
struct FirstVolume
{
EType type;
unsigned int flag;
// Index of the first volume in a GLVolumeCollection.
unsigned int id;
FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {}
};
std::vector<FirstVolume> first_volumes;
void reset() { first_volumes.clear(); }
};
struct Camera
{
enum EType : unsigned char
{
Unknown,
// Perspective,
Ortho,
Num_types
};
EType type;
float zoom;
float phi;
// float distance;
Vec3d target;
private:
float m_theta;
public:
Camera();
std::string get_type_as_string() const;
float get_theta() const;
void set_theta(float theta);
};
class Bed
{
public:
enum EType : unsigned char
{
MK2,
MK3,
Custom,
Num_Types
};
private:
EType m_type;
Pointfs m_shape;
BoundingBoxf3 m_bounding_box;
Polygon m_polygon;
GeometryBuffer m_triangles;
GeometryBuffer m_gridlines;
mutable GLTexture m_top_texture;
mutable GLTexture m_bottom_texture;
public:
Bed();
bool is_prusa() const;
bool is_custom() const;
const Pointfs& get_shape() const;
// Return true if the bed shape changed, so the calee will update the UI.
bool set_shape(const Pointfs& shape);
const BoundingBoxf3& get_bounding_box() const;
bool contains(const Point& point) const;
Point point_projection(const Point& point) const;
void render(float theta) const;
private:
void _calc_bounding_box();
void _calc_triangles(const ExPolygon& poly);
void _calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
EType _detect_type() const;
void _render_mk2(float theta) const;
void _render_mk3(float theta) const;
void _render_prusa(float theta) const;
void _render_custom() const;
static bool _are_equal(const Pointfs& bed_1, const Pointfs& bed_2);
};
struct Axes
{
Vec3d origin;
float length;
Axes();
void render(bool depth_test) const;
};
class CuttingPlane
{
float m_z;
GeometryBuffer m_lines;
public:
CuttingPlane();
bool set(float z, const ExPolygons& polygons);
void render(const BoundingBoxf3& bb) const;
private:
void _render_plane(const BoundingBoxf3& bb) const;
void _render_contour() const;
};
class Shader
{
GLShader* m_shader;
public:
Shader();
~Shader();
bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
bool is_initialized() const;
bool start_using() const;
void stop_using() const;
void set_uniform(const std::string& name, float value) const;
void set_uniform(const std::string& name, const float* matrix) const;
const GLShader* get_shader() const;
private:
void _reset();
};
class LayersEditing
{
public:
enum EState : unsigned char
{
Unknown,
Editing,
Completed,
Num_States
};
private:
bool m_use_legacy_opengl;
bool m_enabled;
Shader m_shader;
unsigned int m_z_texture_id;
mutable GLTexture m_tooltip_texture;
mutable GLTexture m_reset_texture;
public:
EState state;
float band_width;
float strength;
int last_object_id;
float last_z;
unsigned int last_action;
LayersEditing();
~LayersEditing();
bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
bool is_allowed() const;
void set_use_legacy_opengl(bool use_legacy_opengl);
bool is_enabled() const;
void set_enabled(bool enabled);
unsigned int get_z_texture_id() const;
void render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const;
int get_shader_program_id() const;
static float get_cursor_z_relative(const GLCanvas3D& canvas);
static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y);
static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y);
static Rect get_bar_rect_screen(const GLCanvas3D& canvas);
static Rect get_reset_rect_screen(const GLCanvas3D& canvas);
static Rect get_bar_rect_viewport(const GLCanvas3D& canvas);
static Rect get_reset_rect_viewport(const GLCanvas3D& canvas);
private:
bool _is_initialized() const;
void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const;
void _render_reset_texture(const Rect& reset_rect) const;
void _render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const;
void _render_profile(const PrintObject& print_object, const Rect& bar_rect) const;
};
struct Mouse
{
struct Drag
{
static const Point Invalid_2D_Point;
static const Vec3d Invalid_3D_Point;
Point start_position_2D;
Vec3d start_position_3D;
Vec3d volume_center_offset;
bool move_with_shift;
int move_volume_idx;
int gizmo_volume_idx;
public:
Drag();
};
bool dragging;
Vec2d position;
Drag drag;
Mouse();
void set_start_position_2D_as_invalid();
void set_start_position_3D_as_invalid();
bool is_start_position_2D_defined() const;
bool is_start_position_3D_defined() const;
};
class Gizmos
{
static const float OverlayTexturesScale;
static const float OverlayOffsetX;
static const float OverlayGapY;
public:
enum EType : unsigned char
{
Undefined,
Move,
Scale,
Rotate,
Flatten,
Num_Types
};
private:
bool m_enabled;
typedef std::map<EType, GLGizmoBase*> GizmosMap;
GizmosMap m_gizmos;
EType m_current;
public:
Gizmos();
~Gizmos();
bool init(GLCanvas3D& parent);
bool is_enabled() const;
void set_enabled(bool enable);
void update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
void reset_all_states();
void set_hover_id(int id);
bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const;
bool grabber_contains_mouse() const;
void update(const Linef3& mouse_ray);
EType get_current_type() const;
bool is_running() const;
bool is_dragging() const;
void start_dragging(const BoundingBoxf3& box);
void stop_dragging();
Vec3d get_position() const;
void set_position(const Vec3d& position);
float get_scale() const;
void set_scale(float scale);
float get_angle_z() const;
void set_angle_z(float angle_z);
void set_flattening_data(const ModelObject* model_object);
Vec3d get_flattening_normal() const;
void render_current_gizmo(const BoundingBoxf3& box) const;
void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const;
void render_overlay(const GLCanvas3D& canvas) const;
private:
void _reset();
void _render_overlay(const GLCanvas3D& canvas) const;
void _render_current_gizmo(const BoundingBoxf3& box) const;
float _get_total_overlay_height() const;
GLGizmoBase* _get_current() const;
};
class WarningTexture : public GUI::GLTexture
{
static const unsigned char Background_Color[3];
static const unsigned char Opacity;
int m_original_width;
int m_original_height;
public:
WarningTexture();
bool generate(const std::string& msg);
void render(const GLCanvas3D& canvas) const;
};
class LegendTexture : public GUI::GLTexture
{
static const int Px_Title_Offset = 5;
static const int Px_Text_Offset = 5;
static const int Px_Square = 20;
static const int Px_Square_Contour = 1;
static const int Px_Border = Px_Square / 2;
static const unsigned char Squares_Border_Color[3];
static const unsigned char Background_Color[3];
static const unsigned char Opacity;
int m_original_width;
int m_original_height;
public:
LegendTexture();
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
void render(const GLCanvas3D& canvas) const;
};
wxGLCanvas* m_canvas;
wxGLContext* m_context;
LegendTexture m_legend_texture;
WarningTexture m_warning_texture;
wxTimer* m_timer;
Camera m_camera;
Bed m_bed;
Axes m_axes;
CuttingPlane m_cutting_plane;
LayersEditing m_layers_editing;
Shader m_shader;
Mouse m_mouse;
mutable Gizmos m_gizmos;
mutable GLToolbar m_toolbar;
mutable GLVolumeCollection m_volumes;
DynamicPrintConfig* m_config;
Print* m_print;
Model* m_model;
bool m_dirty;
bool m_initialized;
bool m_use_VBOs;
bool m_force_zoom_to_bed_enabled;
bool m_apply_zoom_to_volumes_filter;
mutable int m_hover_volume_id;
bool m_toolbar_action_running;
bool m_warning_texture_enabled;
bool m_legend_texture_enabled;
bool m_picking_enabled;
bool m_moving_enabled;
bool m_shader_enabled;
bool m_dynamic_background_enabled;
bool m_multisample_allowed;
std::string m_color_by;
std::string m_select_by;
std::string m_drag_by;
bool m_reload_delayed;
std::vector<std::vector<int>> m_objects_volumes_idxs;
std::vector<int> m_objects_selections;
GCodePreviewVolumeIndex m_gcode_preview_volume_index;
PerlCallback m_on_viewport_changed_callback;
PerlCallback m_on_double_click_callback;
PerlCallback m_on_right_click_callback;
PerlCallback m_on_select_object_callback;
PerlCallback m_on_model_update_callback;
PerlCallback m_on_remove_object_callback;
PerlCallback m_on_arrange_callback;
PerlCallback m_on_rotate_object_left_callback;
PerlCallback m_on_rotate_object_right_callback;
PerlCallback m_on_scale_object_uniformly_callback;
PerlCallback m_on_increase_objects_callback;
PerlCallback m_on_decrease_objects_callback;
PerlCallback m_on_instance_moved_callback;
PerlCallback m_on_wipe_tower_moved_callback;
PerlCallback m_on_enable_action_buttons_callback;
PerlCallback m_on_gizmo_scale_uniformly_callback;
PerlCallback m_on_gizmo_rotate_callback;
PerlCallback m_on_gizmo_flatten_callback;
PerlCallback m_on_update_geometry_info_callback;
PerlCallback m_action_add_callback;
PerlCallback m_action_delete_callback;
PerlCallback m_action_deleteall_callback;
PerlCallback m_action_arrange_callback;
PerlCallback m_action_more_callback;
PerlCallback m_action_fewer_callback;
PerlCallback m_action_split_callback;
PerlCallback m_action_cut_callback;
PerlCallback m_action_settings_callback;
PerlCallback m_action_layersediting_callback;
PerlCallback m_action_selectbyparts_callback;
public:
GLCanvas3D(wxGLCanvas* canvas);
~GLCanvas3D();
bool init(bool useVBOs, bool use_legacy_opengl);
bool set_current();
void set_as_dirty();
unsigned int get_volumes_count() const;
void reset_volumes();
void deselect_volumes();
void select_volume(unsigned int id);
void update_volumes_selection(const std::vector<int>& selections);
int check_volumes_outside_state(const DynamicPrintConfig* config) const;
bool move_volume_up(unsigned int id);
bool move_volume_down(unsigned int id);
void set_objects_selections(const std::vector<int>& selections);
void set_config(DynamicPrintConfig* config);
void set_print(Print* print);
void set_model(Model* model);
// Set the bed shape to a single closed 2D polygon(array of two element arrays),
// triangulate the bed and store the triangles into m_bed.m_triangles,
// fills the m_bed.m_grid_lines and sets m_bed.m_origin.
// Sets m_bed.m_polygon to limit the object placement.
void set_bed_shape(const Pointfs& shape);
// Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane to support the scene objects.
void set_auto_bed_shape();
void set_axes_length(float length);
void set_cutting_plane(float z, const ExPolygons& polygons);
void set_color_by(const std::string& value);
void set_select_by(const std::string& value);
void set_drag_by(const std::string& value);
const std::string& get_select_by() const;
const std::string& get_drag_by() const;
float get_camera_zoom() const;
BoundingBoxf3 volumes_bounding_box() const;
bool is_layers_editing_enabled() const;
bool is_layers_editing_allowed() const;
bool is_shader_enabled() const;
bool is_reload_delayed() const;
void enable_layers_editing(bool enable);
void enable_warning_texture(bool enable);
void enable_legend_texture(bool enable);
void enable_picking(bool enable);
void enable_moving(bool enable);
void enable_gizmos(bool enable);
void enable_toolbar(bool enable);
void enable_shader(bool enable);
void enable_force_zoom_to_bed(bool enable);
void enable_dynamic_background(bool enable);
void allow_multisample(bool allow);
void enable_toolbar_item(const std::string& name, bool enable);
bool is_toolbar_item_pressed(const std::string& name) const;
void zoom_to_bed();
void zoom_to_volumes();
void select_view(const std::string& direction);
void set_viewport_from_scene(const GLCanvas3D& other);
void update_volumes_colors_by_extruder();
void update_gizmos_data();
void render();
std::vector<double> get_current_print_zs(bool active_only) const;
void set_toolpaths_range(double low, double high);
std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs);
std::vector<int> load_object(const Model& model, int obj_idx);
int get_first_volume_id(int obj_idx) const;
int get_in_object_volume_id(int scene_vol_idx) const;
void reload_scene(bool force);
void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
void load_preview(const std::vector<std::string>& str_tool_colors);
void register_on_viewport_changed_callback(void* callback);
void register_on_double_click_callback(void* callback);
void register_on_right_click_callback(void* callback);
void register_on_select_object_callback(void* callback);
void register_on_model_update_callback(void* callback);
void register_on_remove_object_callback(void* callback);
void register_on_arrange_callback(void* callback);
void register_on_rotate_object_left_callback(void* callback);
void register_on_rotate_object_right_callback(void* callback);
void register_on_scale_object_uniformly_callback(void* callback);
void register_on_increase_objects_callback(void* callback);
void register_on_decrease_objects_callback(void* callback);
void register_on_instance_moved_callback(void* callback);
void register_on_wipe_tower_moved_callback(void* callback);
void register_on_enable_action_buttons_callback(void* callback);
void register_on_gizmo_scale_uniformly_callback(void* callback);
void register_on_gizmo_rotate_callback(void* callback);
void register_on_gizmo_flatten_callback(void* callback);
void register_on_update_geometry_info_callback(void* callback);
void register_action_add_callback(void* callback);
void register_action_delete_callback(void* callback);
void register_action_deleteall_callback(void* callback);
void register_action_arrange_callback(void* callback);
void register_action_more_callback(void* callback);
void register_action_fewer_callback(void* callback);
void register_action_split_callback(void* callback);
void register_action_cut_callback(void* callback);
void register_action_settings_callback(void* callback);
void register_action_layersediting_callback(void* callback);
void register_action_selectbyparts_callback(void* callback);
void bind_event_handlers();
void unbind_event_handlers();
void on_size(wxSizeEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_char(wxKeyEvent& evt);
void on_mouse_wheel(wxMouseEvent& evt);
void on_timer(wxTimerEvent& evt);
void on_mouse(wxMouseEvent& evt);
void on_paint(wxPaintEvent& evt);
void on_key_down(wxKeyEvent& evt);
Size get_canvas_size() const;
Point get_local_mouse_position() const;
void reset_legend_texture();
void set_tooltip(const std::string& tooltip);
private:
bool _is_shown_on_screen() const;
void _force_zoom_to_bed();
bool _init_toolbar();
void _resize(unsigned int w, unsigned int h);
BoundingBoxf3 _max_bounding_box() const;
BoundingBoxf3 _selected_volumes_bounding_box() const;
void _zoom_to_bounding_box(const BoundingBoxf3& bbox);
float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const;
void _deregister_callbacks();
void _mark_volumes_for_layer_height() const;
void _refresh_if_shown_on_screen();
void _camera_tranform() const;
void _picking_pass() const;
void _render_background() const;
void _render_bed(float theta) const;
void _render_axes(bool depth_test) const;
void _render_objects() const;
void _render_cutting_plane() const;
void _render_warning_texture() const;
void _render_legend_texture() const;
void _render_layer_editing_overlay() const;
void _render_volumes(bool fake_colors) const;
void _render_current_gizmo() const;
void _render_gizmos_overlay() const;
void _render_toolbar() const;
float _get_layers_editing_cursor_z_relative() const;
void _perform_layer_editing_action(wxMouseEvent* evt = nullptr);
// Convert the screen space coordinate to an object space coordinate.
// If the Z screen space coordinate is not provided, a depth buffer value is substituted.
Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
// Convert the screen space coordinate to world coordinate on the bed.
Vec3d _mouse_to_bed_3d(const Point& mouse_pos);
// Returns the view ray line, in world coordinate, at the given mouse position.
Linef3 mouse_ray(const Point& mouse_pos);
void _start_timer();
void _stop_timer();
int _get_first_selected_object_id() const;
int _get_first_selected_volume_id(int object_id) const;
// Create 3D thick extrusion lines for a skirt and brim.
// Adds a new Slic3r::GUI::3DScene::Volume to volumes.
void _load_print_toolpaths();
// Create 3D thick extrusion lines for object forming extrusions.
// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
// one for perimeters, one for infill and one for supports.
void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors);
// Create 3D thick extrusion lines for wipe tower extrusions
void _load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
// generates gcode extrusion paths geometry
void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates gcode travel paths geometry
void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
bool _travel_paths_by_type(const GCodePreviewData& preview_data);
bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data);
bool _travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates gcode retractions geometry
void _load_gcode_retractions(const GCodePreviewData& preview_data);
// generates gcode unretractions geometry
void _load_gcode_unretractions(const GCodePreviewData& preview_data);
// generates objects and wipe tower geometry
void _load_shells();
// sets gcode geometry visibility according to user selection
void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
void _update_toolpath_volumes_outside_state();
void _show_warning_texture_if_needed();
void _on_move(const std::vector<int>& volume_idxs);
void _on_select(int volume_idx, int object_idx);
// generates the legend texture in dependence of the current shown view type
void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates a warning texture containing the given message
void _generate_warning_texture(const std::string& msg);
void _reset_warning_texture();
bool _is_any_volume_outside() const;
void _resize_toolbar() const;
static std::vector<float> _parse_colors(const std::vector<std::string>& colors);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLCanvas3D_hpp_

View file

@ -0,0 +1,819 @@
#include "GLCanvas3DManager.hpp"
#include "../../slic3r/GUI/GUI.hpp"
#include "../../slic3r/GUI/AppConfig.hpp"
#include "../../slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <wx/glcanvas.h>
#include <wx/timer.h>
#include <vector>
#include <string>
#include <iostream>
namespace Slic3r {
namespace GUI {
GLCanvas3DManager::GLInfo::GLInfo()
: version("")
, glsl_version("")
, vendor("")
, renderer("")
{
}
void GLCanvas3DManager::GLInfo::detect()
{
const char* data = (const char*)::glGetString(GL_VERSION);
if (data != nullptr)
version = data;
data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION);
if (data != nullptr)
glsl_version = data;
data = (const char*)::glGetString(GL_VENDOR);
if (data != nullptr)
vendor = data;
data = (const char*)::glGetString(GL_RENDERER);
if (data != nullptr)
renderer = data;
}
bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
{
std::vector<std::string> tokens;
boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
if (tokens.empty())
return false;
std::vector<std::string> numbers;
boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on);
unsigned int gl_major = 0;
unsigned int gl_minor = 0;
if (numbers.size() > 0)
gl_major = ::atoi(numbers[0].c_str());
if (numbers.size() > 1)
gl_minor = ::atoi(numbers[1].c_str());
if (gl_major < major)
return false;
else if (gl_major > major)
return true;
else
return gl_minor >= minor;
}
std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const
{
std::stringstream out;
std::string h2_start = format_as_html ? "<b>" : "";
std::string h2_end = format_as_html ? "</b>" : "";
std::string b_start = format_as_html ? "<b>" : "";
std::string b_end = format_as_html ? "</b>" : "";
std::string line_end = format_as_html ? "<br>" : "\n";
out << h2_start << "OpenGL installation" << h2_end << line_end;
out << b_start << "GL version: " << b_end << (version.empty() ? "N/A" : version) << line_end;
out << b_start << "Vendor: " << b_end << (vendor.empty() ? "N/A" : vendor) << line_end;
out << b_start << "Renderer: " << b_end << (renderer.empty() ? "N/A" : renderer) << line_end;
out << b_start << "GLSL version: " << b_end << (glsl_version.empty() ? "N/A" : glsl_version) << line_end;
if (extensions)
{
std::vector<std::string> extensions_list;
std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS);
boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off);
if (!extensions_list.empty())
{
out << h2_start << "Installed extensions:" << h2_end << line_end;
std::sort(extensions_list.begin(), extensions_list.end());
for (const std::string& ext : extensions_list)
{
out << ext << line_end;
}
}
}
return out.str();
}
GLCanvas3DManager::GLCanvas3DManager()
: m_current(nullptr)
, m_gl_initialized(false)
, m_use_legacy_opengl(false)
, m_use_VBOs(false)
{
}
bool GLCanvas3DManager::add(wxGLCanvas* canvas)
{
if (canvas == nullptr)
return false;
if (_get_canvas(canvas) != m_canvases.end())
return false;
GLCanvas3D* canvas3D = new GLCanvas3D(canvas);
if (canvas3D == nullptr)
return false;
canvas3D->bind_event_handlers();
m_canvases.insert(CanvasesMap::value_type(canvas, canvas3D));
return true;
}
bool GLCanvas3DManager::remove(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it == m_canvases.end())
return false;
it->second->unbind_event_handlers();
delete it->second;
m_canvases.erase(it);
return true;
}
void GLCanvas3DManager::remove_all()
{
for (CanvasesMap::value_type& item : m_canvases)
{
item.second->unbind_event_handlers();
delete item.second;
}
m_canvases.clear();
}
unsigned int GLCanvas3DManager::count() const
{
return (unsigned int)m_canvases.size();
}
void GLCanvas3DManager::init_gl()
{
if (!m_gl_initialized)
{
glewInit();
m_gl_info.detect();
const AppConfig* config = GUI::get_app_config();
m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1");
m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0);
m_gl_initialized = true;
}
}
std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const
{
return m_gl_info.to_string(format_as_html, extensions);
}
bool GLCanvas3DManager::use_VBOs() const
{
return m_use_VBOs;
}
bool GLCanvas3DManager::init(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
return (it->second != nullptr) ? _init(*it->second) : false;
else
return false;
}
void GLCanvas3DManager::set_as_dirty(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_as_dirty();
}
unsigned int GLCanvas3DManager::get_volumes_count(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_volumes_count() : 0;
}
void GLCanvas3DManager::reset_volumes(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->reset_volumes();
}
void GLCanvas3DManager::deselect_volumes(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->deselect_volumes();
}
void GLCanvas3DManager::select_volume(wxGLCanvas* canvas, unsigned int id)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->select_volume(id);
}
void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->update_volumes_selection(selections);
}
int GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false;
}
bool GLCanvas3DManager::move_volume_up(wxGLCanvas* canvas, unsigned int id)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->move_volume_up(id) : false;
}
bool GLCanvas3DManager::move_volume_down(wxGLCanvas* canvas, unsigned int id)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->move_volume_down(id) : false;
}
void GLCanvas3DManager::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_objects_selections(selections);
}
void GLCanvas3DManager::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_config(config);
}
void GLCanvas3DManager::set_print(wxGLCanvas* canvas, Print* print)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_print(print);
}
void GLCanvas3DManager::set_model(wxGLCanvas* canvas, Model* model)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_model(model);
}
void GLCanvas3DManager::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_bed_shape(shape);
}
void GLCanvas3DManager::set_auto_bed_shape(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_auto_bed_shape();
}
BoundingBoxf3 GLCanvas3DManager::get_volumes_bounding_box(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->volumes_bounding_box() : BoundingBoxf3();
}
void GLCanvas3DManager::set_axes_length(wxGLCanvas* canvas, float length)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_axes_length(length);
}
void GLCanvas3DManager::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_cutting_plane(z, polygons);
}
void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& value)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_color_by(value);
}
void GLCanvas3DManager::set_select_by(wxGLCanvas* canvas, const std::string& value)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_select_by(value);
}
void GLCanvas3DManager::set_drag_by(wxGLCanvas* canvas, const std::string& value)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_drag_by(value);
}
std::string GLCanvas3DManager::get_select_by(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_select_by() : "";
}
bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_layers_editing_enabled() : false;
}
bool GLCanvas3DManager::is_layers_editing_allowed(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_layers_editing_allowed() : false;
}
bool GLCanvas3DManager::is_shader_enabled(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_shader_enabled() : false;
}
bool GLCanvas3DManager::is_reload_delayed(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_reload_delayed() : false;
}
void GLCanvas3DManager::enable_layers_editing(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_layers_editing(enable);
}
void GLCanvas3DManager::enable_warning_texture(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_warning_texture(enable);
}
void GLCanvas3DManager::enable_legend_texture(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_legend_texture(enable);
}
void GLCanvas3DManager::enable_picking(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_picking(enable);
}
void GLCanvas3DManager::enable_moving(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_moving(enable);
}
void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_gizmos(enable);
}
void GLCanvas3DManager::enable_toolbar(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_toolbar(enable);
}
void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_shader(enable);
}
void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_force_zoom_to_bed(enable);
}
void GLCanvas3DManager::enable_dynamic_background(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_dynamic_background(enable);
}
void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->allow_multisample(allow);
}
void GLCanvas3DManager::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_toolbar_item(name, enable);
}
bool GLCanvas3DManager::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_toolbar_item_pressed(name) : false;
}
void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->zoom_to_bed();
}
void GLCanvas3DManager::zoom_to_volumes(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->zoom_to_volumes();
}
void GLCanvas3DManager::select_view(wxGLCanvas* canvas, const std::string& direction)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->select_view(direction);
}
void GLCanvas3DManager::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
{
CanvasesMap::iterator other_it = _get_canvas(other);
if (other_it != m_canvases.end())
it->second->set_viewport_from_scene(*other_it->second);
}
}
void GLCanvas3DManager::update_volumes_colors_by_extruder(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->update_volumes_colors_by_extruder();
}
void GLCanvas3DManager::update_gizmos_data(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->update_gizmos_data();
}
void GLCanvas3DManager::render(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->render();
}
std::vector<double> GLCanvas3DManager::get_current_print_zs(wxGLCanvas* canvas, bool active_only) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_current_print_zs(active_only) : std::vector<double>();
}
void GLCanvas3DManager::set_toolpaths_range(wxGLCanvas* canvas, double low, double high)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_toolpaths_range(low, high);
}
std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs)
{
if (model_object == nullptr)
return std::vector<int>();
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector<int>();
}
std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx)
{
if (model == nullptr)
return std::vector<int>();
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector<int>();
}
int GLCanvas3DManager::get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_first_volume_id(obj_idx) : -1;
}
int GLCanvas3DManager::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_in_object_volume_id(scene_vol_idx) : -1;
}
void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->reload_scene(force);
}
void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
{
if (preview_data == nullptr)
return;
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->load_gcode_preview(*preview_data, str_tool_colors);
}
void GLCanvas3DManager::load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->load_preview(str_tool_colors);
}
void GLCanvas3DManager::reset_legend_texture()
{
for (CanvasesMap::value_type& canvas : m_canvases)
{
if (canvas.second != nullptr)
canvas.second->reset_legend_texture();
}
}
void GLCanvas3DManager::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_viewport_changed_callback(callback);
}
void GLCanvas3DManager::register_on_double_click_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_double_click_callback(callback);
}
void GLCanvas3DManager::register_on_right_click_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_right_click_callback(callback);
}
void GLCanvas3DManager::register_on_select_object_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_select_object_callback(callback);
}
void GLCanvas3DManager::register_on_model_update_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_model_update_callback(callback);
}
void GLCanvas3DManager::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_remove_object_callback(callback);
}
void GLCanvas3DManager::register_on_arrange_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_arrange_callback(callback);
}
void GLCanvas3DManager::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_rotate_object_left_callback(callback);
}
void GLCanvas3DManager::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_rotate_object_right_callback(callback);
}
void GLCanvas3DManager::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_scale_object_uniformly_callback(callback);
}
void GLCanvas3DManager::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_increase_objects_callback(callback);
}
void GLCanvas3DManager::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_decrease_objects_callback(callback);
}
void GLCanvas3DManager::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_instance_moved_callback(callback);
}
void GLCanvas3DManager::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_wipe_tower_moved_callback(callback);
}
void GLCanvas3DManager::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_enable_action_buttons_callback(callback);
}
void GLCanvas3DManager::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_gizmo_scale_uniformly_callback(callback);
}
void GLCanvas3DManager::register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_gizmo_rotate_callback(callback);
}
void GLCanvas3DManager::register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_gizmo_flatten_callback(callback);
}
void GLCanvas3DManager::register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_update_geometry_info_callback(callback);
}
void GLCanvas3DManager::register_action_add_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_add_callback(callback);
}
void GLCanvas3DManager::register_action_delete_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_delete_callback(callback);
}
void GLCanvas3DManager::register_action_deleteall_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_deleteall_callback(callback);
}
void GLCanvas3DManager::register_action_arrange_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_arrange_callback(callback);
}
void GLCanvas3DManager::register_action_more_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_more_callback(callback);
}
void GLCanvas3DManager::register_action_fewer_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_fewer_callback(callback);
}
void GLCanvas3DManager::register_action_split_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_split_callback(callback);
}
void GLCanvas3DManager::register_action_cut_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_cut_callback(callback);
}
void GLCanvas3DManager::register_action_settings_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_settings_callback(callback);
}
void GLCanvas3DManager::register_action_layersediting_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_layersediting_callback(callback);
}
void GLCanvas3DManager::register_action_selectbyparts_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_selectbyparts_callback(callback);
}
GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas)
{
return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
}
GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const
{
return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
}
bool GLCanvas3DManager::_init(GLCanvas3D& canvas)
{
if (!m_gl_initialized)
init_gl();
return canvas.init(m_use_VBOs, m_use_legacy_opengl);
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,192 @@
#ifndef slic3r_GLCanvas3DManager_hpp_
#define slic3r_GLCanvas3DManager_hpp_
#include "../../libslic3r/BoundingBox.hpp"
#include <map>
#include <vector>
class wxGLCanvas;
class wxGLContext;
namespace Slic3r {
class DynamicPrintConfig;
class Print;
class Model;
class ExPolygon;
typedef std::vector<ExPolygon> ExPolygons;
class ModelObject;
class PrintObject;
class GCodePreviewData;
namespace GUI {
class GLCanvas3D;
class GLCanvas3DManager
{
struct GLInfo
{
std::string version;
std::string glsl_version;
std::string vendor;
std::string renderer;
GLInfo();
void detect();
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
std::string to_string(bool format_as_html, bool extensions) const;
};
typedef std::map<wxGLCanvas*, GLCanvas3D*> CanvasesMap;
CanvasesMap m_canvases;
wxGLCanvas* m_current;
GLInfo m_gl_info;
bool m_gl_initialized;
bool m_use_legacy_opengl;
bool m_use_VBOs;
public:
GLCanvas3DManager();
bool add(wxGLCanvas* canvas);
bool remove(wxGLCanvas* canvas);
void remove_all();
unsigned int count() const;
void init_gl();
std::string get_gl_info(bool format_as_html, bool extensions) const;
bool use_VBOs() const;
bool layer_editing_allowed() const;
bool init(wxGLCanvas* canvas);
void set_as_dirty(wxGLCanvas* canvas);
unsigned int get_volumes_count(wxGLCanvas* canvas) const;
void reset_volumes(wxGLCanvas* canvas);
void deselect_volumes(wxGLCanvas* canvas);
void select_volume(wxGLCanvas* canvas, unsigned int id);
void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const;
bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
void set_print(wxGLCanvas* canvas, Print* print);
void set_model(wxGLCanvas* canvas, Model* model);
void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape);
void set_auto_bed_shape(wxGLCanvas* canvas);
BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas);
void set_axes_length(wxGLCanvas* canvas, float length);
void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
void set_color_by(wxGLCanvas* canvas, const std::string& value);
void set_select_by(wxGLCanvas* canvas, const std::string& value);
void set_drag_by(wxGLCanvas* canvas, const std::string& value);
std::string get_select_by(wxGLCanvas* canvas) const;
bool is_layers_editing_enabled(wxGLCanvas* canvas) const;
bool is_layers_editing_allowed(wxGLCanvas* canvas) const;
bool is_shader_enabled(wxGLCanvas* canvas) const;
bool is_reload_delayed(wxGLCanvas* canvas) const;
void enable_layers_editing(wxGLCanvas* canvas, bool enable);
void enable_warning_texture(wxGLCanvas* canvas, bool enable);
void enable_legend_texture(wxGLCanvas* canvas, bool enable);
void enable_picking(wxGLCanvas* canvas, bool enable);
void enable_moving(wxGLCanvas* canvas, bool enable);
void enable_gizmos(wxGLCanvas* canvas, bool enable);
void enable_toolbar(wxGLCanvas* canvas, bool enable);
void enable_shader(wxGLCanvas* canvas, bool enable);
void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
void enable_dynamic_background(wxGLCanvas* canvas, bool enable);
void allow_multisample(wxGLCanvas* canvas, bool allow);
void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable);
bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const;
void zoom_to_bed(wxGLCanvas* canvas);
void zoom_to_volumes(wxGLCanvas* canvas);
void select_view(wxGLCanvas* canvas, const std::string& direction);
void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
void update_gizmos_data(wxGLCanvas* canvas);
void render(wxGLCanvas* canvas) const;
std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only) const;
void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
int get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const;
int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const;
void reload_scene(wxGLCanvas* canvas, bool force);
void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
void load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
void reset_legend_texture();
void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback);
void register_on_double_click_callback(wxGLCanvas* canvas, void* callback);
void register_on_right_click_callback(wxGLCanvas* canvas, void* callback);
void register_on_select_object_callback(wxGLCanvas* canvas, void* callback);
void register_on_model_update_callback(wxGLCanvas* canvas, void* callback);
void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback);
void register_on_arrange_callback(wxGLCanvas* canvas, void* callback);
void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback);
void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback);
void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback);
void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback);
void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback);
void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback);
void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback);
void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback);
void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback);
void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback);
void register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback);
void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback);
void register_action_add_callback(wxGLCanvas* canvas, void* callback);
void register_action_delete_callback(wxGLCanvas* canvas, void* callback);
void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback);
void register_action_arrange_callback(wxGLCanvas* canvas, void* callback);
void register_action_more_callback(wxGLCanvas* canvas, void* callback);
void register_action_fewer_callback(wxGLCanvas* canvas, void* callback);
void register_action_split_callback(wxGLCanvas* canvas, void* callback);
void register_action_cut_callback(wxGLCanvas* canvas, void* callback);
void register_action_settings_callback(wxGLCanvas* canvas, void* callback);
void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback);
void register_action_selectbyparts_callback(wxGLCanvas* canvas, void* callback);
private:
CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas);
CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const;
bool _init(GLCanvas3D& canvas);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLCanvas3DManager_hpp_

1503
src/slic3r/GUI/GLGizmo.cpp Normal file

File diff suppressed because it is too large Load diff

366
src/slic3r/GUI/GLGizmo.hpp Normal file
View file

@ -0,0 +1,366 @@
#ifndef slic3r_GLGizmo_hpp_
#define slic3r_GLGizmo_hpp_
#include "../../slic3r/GUI/GLTexture.hpp"
#include "../../libslic3r/Point.hpp"
#include "../../libslic3r/BoundingBox.hpp"
#include <vector>
namespace Slic3r {
class BoundingBoxf3;
class Linef3;
class ModelObject;
namespace GUI {
class GLCanvas3D;
class GLGizmoBase
{
protected:
struct Grabber
{
static const float SizeFactor;
static const float MinHalfSize;
static const float DraggingScaleFactor;
Vec3d center;
Vec3d angles;
float color[3];
bool enabled;
bool dragging;
Grabber();
void render(bool hover, const BoundingBoxf3& box) const;
void render_for_picking(const BoundingBoxf3& box) const { render(box, color, false); }
private:
void render(const BoundingBoxf3& box, const float* render_color, bool use_lighting) const;
void render_face(float half_size) const;
};
public:
enum EState
{
Off,
Hover,
On,
Num_States
};
protected:
GLCanvas3D& m_parent;
int m_group_id;
EState m_state;
// textures are assumed to be square and all with the same size in pixels, no internal check is done
GLTexture m_textures[Num_States];
int m_hover_id;
bool m_dragging;
float m_base_color[3];
float m_drag_color[3];
float m_highlight_color[3];
mutable std::vector<Grabber> m_grabbers;
public:
explicit GLGizmoBase(GLCanvas3D& parent);
virtual ~GLGizmoBase() {}
bool init() { return on_init(); }
int get_group_id() const { return m_group_id; }
void set_group_id(int id) { m_group_id = id; }
EState get_state() const { return m_state; }
void set_state(EState state) { m_state = state; on_set_state(); }
unsigned int get_texture_id() const { return m_textures[m_state].get_id(); }
int get_textures_size() const { return m_textures[Off].get_width(); }
int get_hover_id() const { return m_hover_id; }
void set_hover_id(int id);
void set_highlight_color(const float* color);
void enable_grabber(unsigned int id);
void disable_grabber(unsigned int id);
void start_dragging(const BoundingBoxf3& box);
void stop_dragging();
bool is_dragging() const { return m_dragging; }
void update(const Linef3& mouse_ray);
void render(const BoundingBoxf3& box) const { on_render(box); }
void render_for_picking(const BoundingBoxf3& box) const { on_render_for_picking(box); }
protected:
virtual bool on_init() = 0;
virtual void on_set_state() {}
virtual void on_set_hover_id() {}
virtual void on_enable_grabber(unsigned int id) {}
virtual void on_disable_grabber(unsigned int id) {}
virtual void on_start_dragging(const BoundingBoxf3& box) {}
virtual void on_stop_dragging() {}
virtual void on_update(const Linef3& mouse_ray) = 0;
virtual void on_render(const BoundingBoxf3& box) const = 0;
virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
float picking_color_component(unsigned int id) const;
void render_grabbers(const BoundingBoxf3& box) const;
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
void set_tooltip(const std::string& tooltip) const;
std::string format(float value, unsigned int decimals) const;
};
class GLGizmoRotate : public GLGizmoBase
{
static const float Offset;
static const unsigned int CircleResolution;
static const unsigned int AngleResolution;
static const unsigned int ScaleStepsCount;
static const float ScaleStepRad;
static const unsigned int ScaleLongEvery;
static const float ScaleLongTooth;
static const float ScaleShortTooth;
static const unsigned int SnapRegionsCount;
static const float GrabberOffset;
public:
enum Axis : unsigned char
{
X,
Y,
Z
};
private:
Axis m_axis;
double m_angle;
mutable Vec3d m_center;
mutable float m_radius;
public:
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
double get_angle() const { return m_angle; }
void set_angle(double angle);
protected:
virtual bool on_init();
virtual void on_start_dragging(const BoundingBoxf3& box);
virtual void on_update(const Linef3& mouse_ray);
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
private:
void render_circle() const;
void render_scale() const;
void render_snap_radii() const;
void render_reference_radius() const;
void render_angle() const;
void render_grabber(const BoundingBoxf3& box) const;
void transform_to_local() const;
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray) const;
};
class GLGizmoRotate3D : public GLGizmoBase
{
std::vector<GLGizmoRotate> m_gizmos;
public:
explicit GLGizmoRotate3D(GLCanvas3D& parent);
double get_angle_x() const { return m_gizmos[X].get_angle(); }
void set_angle_x(double angle) { m_gizmos[X].set_angle(angle); }
double get_angle_y() const { return m_gizmos[Y].get_angle(); }
void set_angle_y(double angle) { m_gizmos[Y].set_angle(angle); }
double get_angle_z() const { return m_gizmos[Z].get_angle(); }
void set_angle_z(double angle) { m_gizmos[Z].set_angle(angle); }
protected:
virtual bool on_init();
virtual void on_set_state()
{
for (GLGizmoRotate& g : m_gizmos)
{
g.set_state(m_state);
}
}
virtual void on_set_hover_id()
{
for (unsigned int i = 0; i < 3; ++i)
{
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
}
}
virtual void on_enable_grabber(unsigned int id)
{
if ((0 <= id) && (id < 3))
m_gizmos[id].enable_grabber(0);
}
virtual void on_disable_grabber(unsigned int id)
{
if ((0 <= id) && (id < 3))
m_gizmos[id].disable_grabber(0);
}
virtual void on_start_dragging(const BoundingBoxf3& box);
virtual void on_stop_dragging();
virtual void on_update(const Linef3& mouse_ray)
{
for (GLGizmoRotate& g : m_gizmos)
{
g.update(mouse_ray);
}
}
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const
{
for (const GLGizmoRotate& g : m_gizmos)
{
g.render_for_picking(box);
}
}
};
class GLGizmoScale3D : public GLGizmoBase
{
static const float Offset;
static const Vec3d OffsetVec;
mutable BoundingBoxf3 m_box;
Vec3d m_scale;
Vec3d m_starting_scale;
Vec3d m_starting_drag_position;
bool m_show_starting_box;
BoundingBoxf3 m_starting_box;
public:
explicit GLGizmoScale3D(GLCanvas3D& parent);
double get_scale_x() const { return m_scale(0); }
void set_scale_x(double scale) { m_starting_scale(0) = scale; }
double get_scale_y() const { return m_scale(1); }
void set_scale_y(double scale) { m_starting_scale(1) = scale; }
double get_scale_z() const { return m_scale(2); }
void set_scale_z(double scale) { m_starting_scale(2) = scale; }
void set_scale(double scale) { m_starting_scale = scale * Vec3d::Ones(); }
protected:
virtual bool on_init();
virtual void on_start_dragging(const BoundingBoxf3& box);
virtual void on_stop_dragging() { m_show_starting_box = false; }
virtual void on_update(const Linef3& mouse_ray);
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
private:
void render_box(const BoundingBoxf3& box) const;
void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const;
void do_scale_x(const Linef3& mouse_ray);
void do_scale_y(const Linef3& mouse_ray);
void do_scale_z(const Linef3& mouse_ray);
void do_scale_uniform(const Linef3& mouse_ray);
double calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Vec3d& center) const;
};
class GLGizmoMove3D : public GLGizmoBase
{
static const double Offset;
Vec3d m_position;
Vec3d m_starting_drag_position;
Vec3d m_starting_box_center;
Vec3d m_starting_box_bottom_center;
public:
explicit GLGizmoMove3D(GLCanvas3D& parent);
const Vec3d& get_position() const { return m_position; }
void set_position(const Vec3d& position) { m_position = position; }
protected:
virtual bool on_init();
virtual void on_start_dragging(const BoundingBoxf3& box);
virtual void on_update(const Linef3& mouse_ray);
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
private:
double calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const;
};
class GLGizmoFlatten : public GLGizmoBase
{
// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself.
private:
mutable Vec3d m_normal;
struct PlaneData {
std::vector<Vec3d> vertices;
Vec3d normal;
float area;
};
struct SourceDataSummary {
std::vector<BoundingBoxf3> bounding_boxes; // bounding boxes of convex hulls of individual volumes
float scaling_factor;
float rotation;
Vec3d mesh_first_point;
};
// This holds information to decide whether recalculation is necessary:
SourceDataSummary m_source_data;
std::vector<PlaneData> m_planes;
#if ENABLE_MODELINSTANCE_3D_OFFSET
Pointf3s m_instances_positions;
#else
std::vector<Vec2d> m_instances_positions;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
Vec3d m_starting_center;
const ModelObject* m_model_object = nullptr;
void update_planes();
bool is_plane_update_necessary() const;
public:
explicit GLGizmoFlatten(GLCanvas3D& parent);
void set_flattening_data(const ModelObject* model_object);
Vec3d get_flattening_normal() const;
protected:
virtual bool on_init();
virtual void on_start_dragging(const BoundingBoxf3& box);
virtual void on_update(const Linef3& mouse_ray) {}
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
virtual void on_set_state()
{
if (m_state == On && is_plane_update_necessary())
update_planes();
}
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmo_hpp_

256
src/slic3r/GUI/GLShader.cpp Normal file
View file

@ -0,0 +1,256 @@
#include <GL/glew.h>
#include "GLShader.hpp"
#include "../../libslic3r/Utils.hpp"
#include <boost/nowide/fstream.hpp>
#include <string>
#include <utility>
#include <assert.h>
namespace Slic3r {
GLShader::~GLShader()
{
assert(fragment_program_id == 0);
assert(vertex_program_id == 0);
assert(shader_program_id == 0);
}
// A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr.
inline std::string gl_get_string_safe(GLenum param)
{
const char *value = (const char*)glGetString(param);
return std::string(value ? value : "N/A");
}
bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader)
{
std::string gl_version = gl_get_string_safe(GL_VERSION);
int major = atoi(gl_version.c_str());
//int minor = atoi(gl_version.c_str() + gl_version.find('.') + 1);
if (major < 2) {
// Cannot create a shader object on OpenGL 1.x.
// Form an error message.
std::string gl_vendor = gl_get_string_safe(GL_VENDOR);
std::string gl_renderer = gl_get_string_safe(GL_RENDERER);
std::string glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION);
last_error = "Your computer does not support OpenGL shaders.\n";
#ifdef _WIN32
if (gl_vendor == "Microsoft Corporation" && gl_renderer == "GDI Generic") {
last_error = "Windows is using a software OpenGL renderer.\n"
"You are either connected over remote desktop,\n"
"or a hardware acceleration is not available.\n";
}
#endif
last_error += "GL version: " + gl_version + "\n";
last_error += "vendor: " + gl_vendor + "\n";
last_error += "renderer: " + gl_renderer + "\n";
last_error += "GLSL version: " + glsl_version + "\n";
return false;
}
if (fragment_shader != nullptr) {
this->fragment_program_id = glCreateShader(GL_FRAGMENT_SHADER);
if (this->fragment_program_id == 0) {
last_error = "glCreateShader(GL_FRAGMENT_SHADER) failed.";
return false;
}
GLint len = (GLint)strlen(fragment_shader);
glShaderSource(this->fragment_program_id, 1, &fragment_shader, &len);
glCompileShader(this->fragment_program_id);
GLint params;
glGetShaderiv(this->fragment_program_id, GL_COMPILE_STATUS, &params);
if (params == GL_FALSE) {
// Compilation failed. Get the log.
glGetShaderiv(this->fragment_program_id, GL_INFO_LOG_LENGTH, &params);
std::vector<char> msg(params);
glGetShaderInfoLog(this->fragment_program_id, params, &params, msg.data());
this->last_error = std::string("Fragment shader compilation failed:\n") + msg.data();
this->release();
return false;
}
}
if (vertex_shader != nullptr) {
this->vertex_program_id = glCreateShader(GL_VERTEX_SHADER);
if (this->vertex_program_id == 0) {
last_error = "glCreateShader(GL_VERTEX_SHADER) failed.";
this->release();
return false;
}
GLint len = (GLint)strlen(vertex_shader);
glShaderSource(this->vertex_program_id, 1, &vertex_shader, &len);
glCompileShader(this->vertex_program_id);
GLint params;
glGetShaderiv(this->vertex_program_id, GL_COMPILE_STATUS, &params);
if (params == GL_FALSE) {
// Compilation failed. Get the log.
glGetShaderiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, &params);
std::vector<char> msg(params);
glGetShaderInfoLog(this->vertex_program_id, params, &params, msg.data());
this->last_error = std::string("Vertex shader compilation failed:\n") + msg.data();
this->release();
return false;
}
}
// Link shaders
this->shader_program_id = glCreateProgram();
if (this->shader_program_id == 0) {
last_error = "glCreateProgram() failed.";
this->release();
return false;
}
if (this->fragment_program_id)
glAttachShader(this->shader_program_id, this->fragment_program_id);
if (this->vertex_program_id)
glAttachShader(this->shader_program_id, this->vertex_program_id);
glLinkProgram(this->shader_program_id);
GLint params;
glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, &params);
if (params == GL_FALSE) {
// Linking failed. Get the log.
glGetProgramiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, &params);
std::vector<char> msg(params);
glGetProgramInfoLog(this->vertex_program_id, params, &params, msg.data());
this->last_error = std::string("Shader linking failed:\n") + msg.data();
this->release();
return false;
}
last_error.clear();
return true;
}
bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename)
{
const std::string& path = resources_dir() + "/shaders/";
boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary);
if (!vs.good())
return false;
vs.seekg(0, vs.end);
int file_length = vs.tellg();
vs.seekg(0, vs.beg);
std::string vertex_shader(file_length, '\0');
vs.read(const_cast<char*>(vertex_shader.data()), file_length);
if (!vs.good())
return false;
vs.close();
boost::nowide::ifstream fs(path + std::string(fragment_shader_filename), boost::nowide::ifstream::binary);
if (!fs.good())
return false;
fs.seekg(0, fs.end);
file_length = fs.tellg();
fs.seekg(0, fs.beg);
std::string fragment_shader(file_length, '\0');
fs.read(const_cast<char*>(fragment_shader.data()), file_length);
if (!fs.good())
return false;
fs.close();
return load_from_text(fragment_shader.c_str(), vertex_shader.c_str());
}
void GLShader::release()
{
if (this->shader_program_id) {
if (this->vertex_program_id)
glDetachShader(this->shader_program_id, this->vertex_program_id);
if (this->fragment_program_id)
glDetachShader(this->shader_program_id, this->fragment_program_id);
glDeleteProgram(this->shader_program_id);
this->shader_program_id = 0;
}
if (this->vertex_program_id) {
glDeleteShader(this->vertex_program_id);
this->vertex_program_id = 0;
}
if (this->fragment_program_id) {
glDeleteShader(this->fragment_program_id);
this->fragment_program_id = 0;
}
}
void GLShader::enable() const
{
glUseProgram(this->shader_program_id);
}
void GLShader::disable() const
{
glUseProgram(0);
}
// Return shader vertex attribute ID
int GLShader::get_attrib_location(const char *name) const
{
return this->shader_program_id ? glGetAttribLocation(this->shader_program_id, name) : -1;
}
// Return shader uniform variable ID
int GLShader::get_uniform_location(const char *name) const
{
return this->shader_program_id ? glGetUniformLocation(this->shader_program_id, name) : -1;
}
bool GLShader::set_uniform(const char *name, float value) const
{
int id = this->get_uniform_location(name);
if (id >= 0) {
glUniform1fARB(id, value);
return true;
}
return false;
}
bool GLShader::set_uniform(const char* name, const float* matrix) const
{
int id = get_uniform_location(name);
if (id >= 0)
{
::glUniformMatrix4fv(id, 1, GL_FALSE, (const GLfloat*)matrix);
return true;
}
return false;
}
/*
# Set shader vector
sub SetVector
{
my($self,$var,@values) = @_;
my $id = $self->Map($var);
return 'Unable to map $var' if (!defined($id));
my $count = scalar(@values);
eval('glUniform'.$count.'fARB($id,@values)');
return '';
}
# Set shader 4x4 matrix
sub SetMatrix
{
my($self,$var,$oga) = @_;
my $id = $self->Map($var);
return 'Unable to map $var' if (!defined($id));
glUniformMatrix4fvARB_c($id,1,0,$oga->ptr());
return '';
}
*/
} // namespace Slic3r

View file

@ -0,0 +1,41 @@
#ifndef slic3r_GLShader_hpp_
#define slic3r_GLShader_hpp_
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Point.hpp"
namespace Slic3r {
class GLShader
{
public:
GLShader() :
fragment_program_id(0),
vertex_program_id(0),
shader_program_id(0)
{}
~GLShader();
bool load_from_text(const char *fragment_shader, const char *vertex_shader);
bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename);
void release();
int get_attrib_location(const char *name) const;
int get_uniform_location(const char *name) const;
bool set_uniform(const char *name, float value) const;
bool set_uniform(const char* name, const float* matrix) const;
void enable() const;
void disable() const;
unsigned int fragment_program_id;
unsigned int vertex_program_id;
unsigned int shader_program_id;
std::string last_error;
};
}
#endif /* slic3r_GLShader_hpp_ */

View file

@ -0,0 +1,199 @@
#include "GLTexture.hpp"
#include <GL/glew.h>
#include <wx/image.h>
#include <boost/filesystem.hpp>
#include <vector>
#include <algorithm>
namespace Slic3r {
namespace GUI {
GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };
GLTexture::GLTexture()
: m_id(0)
, m_width(0)
, m_height(0)
, m_source("")
{
}
GLTexture::~GLTexture()
{
reset();
}
bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmaps)
{
reset();
if (!boost::filesystem::exists(filename))
return false;
// Load a PNG with an alpha channel.
wxImage image;
if (!image.LoadFile(filename, wxBITMAP_TYPE_PNG))
{
reset();
return false;
}
m_width = image.GetWidth();
m_height = image.GetHeight();
int n_pixels = m_width * m_height;
if (n_pixels <= 0)
{
reset();
return false;
}
// Get RGB & alpha raw data from wxImage, pack them into an array.
unsigned char* img_rgb = image.GetData();
if (img_rgb == nullptr)
{
reset();
return false;
}
unsigned char* img_alpha = image.GetAlpha();
std::vector<unsigned char> data(n_pixels * 4, 0);
for (int i = 0; i < n_pixels; ++i)
{
int data_id = i * 4;
int img_id = i * 3;
data[data_id + 0] = img_rgb[img_id + 0];
data[data_id + 1] = img_rgb[img_id + 1];
data[data_id + 2] = img_rgb[img_id + 2];
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
}
// sends data to gpu
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
::glGenTextures(1, &m_id);
::glBindTexture(GL_TEXTURE_2D, m_id);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
if (generate_mipmaps)
{
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
unsigned int levels_count = _generate_mipmaps(image);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
else
{
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
}
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
::glBindTexture(GL_TEXTURE_2D, 0);
m_source = filename;
return true;
}
void GLTexture::reset()
{
if (m_id != 0)
::glDeleteTextures(1, &m_id);
m_id = 0;
m_width = 0;
m_height = 0;
m_source = "";
}
unsigned int GLTexture::get_id() const
{
return m_id;
}
int GLTexture::get_width() const
{
return m_width;
}
int GLTexture::get_height() const
{
return m_height;
}
const std::string& GLTexture::get_source() const
{
return m_source;
}
void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top)
{
render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs);
}
void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs)
{
::glEnable(GL_BLEND);
::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
::glEnable(GL_TEXTURE_2D);
::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id);
::glBegin(GL_QUADS);
::glTexCoord2f(uvs.left_bottom.u, uvs.left_bottom.v); ::glVertex2f(left, bottom);
::glTexCoord2f(uvs.right_bottom.u, uvs.right_bottom.v); ::glVertex2f(right, bottom);
::glTexCoord2f(uvs.right_top.u, uvs.right_top.v); ::glVertex2f(right, top);
::glTexCoord2f(uvs.left_top.u, uvs.left_top.v); ::glVertex2f(left, top);
::glEnd();
::glBindTexture(GL_TEXTURE_2D, 0);
::glDisable(GL_TEXTURE_2D);
::glDisable(GL_BLEND);
}
unsigned int GLTexture::_generate_mipmaps(wxImage& image)
{
int w = image.GetWidth();
int h = image.GetHeight();
GLint level = 0;
std::vector<unsigned char> data(w * h * 4, 0);
while ((w > 1) || (h > 1))
{
++level;
w = std::max(w / 2, 1);
h = std::max(h / 2, 1);
int n_pixels = w * h;
image = image.ResampleBicubic(w, h);
unsigned char* img_rgb = image.GetData();
unsigned char* img_alpha = image.GetAlpha();
data.resize(n_pixels * 4);
for (int i = 0; i < n_pixels; ++i)
{
int data_id = i * 4;
int img_id = i * 3;
data[data_id + 0] = img_rgb[img_id + 0];
data[data_id + 1] = img_rgb[img_id + 1];
data[data_id + 2] = img_rgb[img_id + 2];
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
}
::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
}
return (unsigned int)level;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,60 @@
#ifndef slic3r_GLTexture_hpp_
#define slic3r_GLTexture_hpp_
#include <string>
class wxImage;
namespace Slic3r {
namespace GUI {
class GLTexture
{
public:
struct UV
{
float u;
float v;
};
struct Quad_UVs
{
UV left_bottom;
UV right_bottom;
UV right_top;
UV left_top;
};
static Quad_UVs FullTextureUVs;
protected:
unsigned int m_id;
int m_width;
int m_height;
std::string m_source;
public:
GLTexture();
virtual ~GLTexture();
bool load_from_file(const std::string& filename, bool generate_mipmaps);
void reset();
unsigned int get_id() const;
int get_width() const;
int get_height() const;
const std::string& get_source() const;
static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs);
protected:
unsigned int _generate_mipmaps(wxImage& image);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLTexture_hpp_

View file

@ -0,0 +1,722 @@
#include "../../libslic3r/Point.hpp"
#include "GLToolbar.hpp"
#include "../../libslic3r/libslic3r.h"
#include "../../slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
#include <wx/bitmap.h>
#include <wx/dcmemory.h>
#include <wx/settings.h>
namespace Slic3r {
namespace GUI {
GLToolbarItem::Data::Data()
: name("")
, tooltip("")
, sprite_id(-1)
, is_toggable(false)
, action_callback(nullptr)
{
}
GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Data& data)
: m_type(type)
, m_state(Disabled)
, m_data(data)
{
}
GLToolbarItem::EState GLToolbarItem::get_state() const
{
return m_state;
}
void GLToolbarItem::set_state(GLToolbarItem::EState state)
{
m_state = state;
}
const std::string& GLToolbarItem::get_name() const
{
return m_data.name;
}
const std::string& GLToolbarItem::get_tooltip() const
{
return m_data.tooltip;
}
void GLToolbarItem::do_action()
{
if (m_data.action_callback != nullptr)
m_data.action_callback->call();
}
bool GLToolbarItem::is_enabled() const
{
return m_state != Disabled;
}
bool GLToolbarItem::is_hovered() const
{
return (m_state == Hover) || (m_state == HoverPressed);
}
bool GLToolbarItem::is_pressed() const
{
return (m_state == Pressed) || (m_state == HoverPressed);
}
bool GLToolbarItem::is_toggable() const
{
return m_data.is_toggable;
}
bool GLToolbarItem::is_separator() const
{
return m_type == Separator;
}
void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
{
GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(texture_size, border_size, icon_size, gap_size));
}
GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
{
GLTexture::Quad_UVs uvs;
float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f;
float scaled_icon_size = (float)icon_size * inv_texture_size;
float scaled_border_size = (float)border_size * inv_texture_size;
float scaled_gap_size = (float)gap_size * inv_texture_size;
float stride = scaled_icon_size + scaled_gap_size;
float left = scaled_border_size + (float)m_state * stride;
float right = left + scaled_icon_size;
float top = scaled_border_size + (float)m_data.sprite_id * stride;
float bottom = top + scaled_icon_size;
uvs.left_top = { left, top };
uvs.left_bottom = { left, bottom };
uvs.right_bottom = { right, bottom };
uvs.right_top = { right, top };
return uvs;
}
GLToolbar::ItemsIconsTexture::ItemsIconsTexture()
: items_icon_size(0)
, items_icon_border_size(0)
, items_icon_gap_size(0)
{
}
GLToolbar::Layout::Layout()
: type(Horizontal)
, top(0.0f)
, left(0.0f)
, separator_size(0.0f)
, gap_size(0.0f)
{
}
GLToolbar::GLToolbar(GLCanvas3D& parent)
: m_parent(parent)
, m_enabled(false)
{
}
bool GLToolbar::init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size)
{
std::string path = resources_dir() + "/icons/";
bool res = !icons_texture_filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture_filename, false);
if (res)
{
m_icons_texture.items_icon_size = items_icon_size;
m_icons_texture.items_icon_border_size = items_icon_border_size;
m_icons_texture.items_icon_gap_size = items_icon_gap_size;
}
return res;
}
GLToolbar::Layout::Type GLToolbar::get_layout_type() const
{
return m_layout.type;
}
void GLToolbar::set_layout_type(GLToolbar::Layout::Type type)
{
m_layout.type = type;
}
void GLToolbar::set_position(float top, float left)
{
m_layout.top = top;
m_layout.left = left;
}
void GLToolbar::set_separator_size(float size)
{
m_layout.separator_size = size;
}
void GLToolbar::set_gap_size(float size)
{
m_layout.gap_size = size;
}
bool GLToolbar::is_enabled() const
{
return m_enabled;
}
void GLToolbar::set_enabled(bool enable)
{
m_enabled = true;
}
bool GLToolbar::add_item(const GLToolbarItem::Data& data)
{
GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data);
if (item == nullptr)
return false;
m_items.push_back(item);
return true;
}
bool GLToolbar::add_separator()
{
GLToolbarItem::Data data;
GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, data);
if (item == nullptr)
return false;
m_items.push_back(item);
return true;
}
float GLToolbar::get_width() const
{
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
return get_width_horizontal();
}
case Layout::Vertical:
{
return get_width_vertical();
}
}
}
float GLToolbar::get_height() const
{
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
return get_height_horizontal();
}
case Layout::Vertical:
{
return get_height_vertical();
}
}
}
void GLToolbar::enable_item(const std::string& name)
{
for (GLToolbarItem* item : m_items)
{
if ((item->get_name() == name) && (item->get_state() == GLToolbarItem::Disabled))
{
item->set_state(GLToolbarItem::Normal);
return;
}
}
}
void GLToolbar::disable_item(const std::string& name)
{
for (GLToolbarItem* item : m_items)
{
if (item->get_name() == name)
{
item->set_state(GLToolbarItem::Disabled);
return;
}
}
}
bool GLToolbar::is_item_pressed(const std::string& name) const
{
for (GLToolbarItem* item : m_items)
{
if (item->get_name() == name)
return item->is_pressed();
}
return false;
}
void GLToolbar::update_hover_state(const Vec2d& mouse_pos)
{
if (!m_enabled)
return;
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
update_hover_state_horizontal(mouse_pos);
break;
}
case Layout::Vertical:
{
update_hover_state_vertical(mouse_pos);
break;
}
}
}
int GLToolbar::contains_mouse(const Vec2d& mouse_pos) const
{
if (!m_enabled)
return -1;
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
return contains_mouse_horizontal(mouse_pos);
}
case Layout::Vertical:
{
return contains_mouse_vertical(mouse_pos);
}
}
}
void GLToolbar::do_action(unsigned int item_id)
{
if (item_id < (unsigned int)m_items.size())
{
GLToolbarItem* item = m_items[item_id];
if ((item != nullptr) && !item->is_separator() && item->is_hovered())
{
if (item->is_toggable())
{
GLToolbarItem::EState state = item->get_state();
if (state == GLToolbarItem::Hover)
item->set_state(GLToolbarItem::HoverPressed);
else if (state == GLToolbarItem::HoverPressed)
item->set_state(GLToolbarItem::Hover);
m_parent.render();
item->do_action();
}
else
{
item->set_state(GLToolbarItem::HoverPressed);
m_parent.render();
item->do_action();
if (item->get_state() != GLToolbarItem::Disabled)
{
// the item may get disabled during the action, if not, set it back to hover state
item->set_state(GLToolbarItem::Hover);
m_parent.render();
}
}
}
}
}
void GLToolbar::render() const
{
if (!m_enabled || m_items.empty())
return;
::glDisable(GL_DEPTH_TEST);
::glPushMatrix();
::glLoadIdentity();
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
render_horizontal();
break;
}
case Layout::Vertical:
{
render_vertical();
break;
}
}
::glPopMatrix();
}
float GLToolbar::get_width_horizontal() const
{
return get_main_size();
}
float GLToolbar::get_width_vertical() const
{
return m_icons_texture.items_icon_size;
}
float GLToolbar::get_height_horizontal() const
{
return m_icons_texture.items_icon_size;
}
float GLToolbar::get_height_vertical() const
{
return get_main_size();
}
float GLToolbar::get_main_size() const
{
float size = 0.0f;
for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
{
if (m_items[i]->is_separator())
size += m_layout.separator_size;
else
size += (float)m_icons_texture.items_icon_size;
}
if (m_items.size() > 1)
size += ((float)m_items.size() - 1.0f) * m_layout.gap_size;
return size;
}
void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos)
{
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
std::string tooltip = "";
for (GLToolbarItem* item : m_items)
{
if (item->is_separator())
left += separator_stride;
else
{
float right = left + scaled_icons_size;
float bottom = top - scaled_icons_size;
GLToolbarItem::EState state = item->get_state();
bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top);
switch (state)
{
case GLToolbarItem::Normal:
{
if (inside)
item->set_state(GLToolbarItem::Hover);
break;
}
case GLToolbarItem::Hover:
{
if (inside)
tooltip = item->get_tooltip();
else
item->set_state(GLToolbarItem::Normal);
break;
}
case GLToolbarItem::Pressed:
{
if (inside)
item->set_state(GLToolbarItem::HoverPressed);
break;
}
case GLToolbarItem::HoverPressed:
{
if (inside)
tooltip = item->get_tooltip();
else
item->set_state(GLToolbarItem::Pressed);
break;
}
default:
case GLToolbarItem::Disabled:
{
break;
}
}
left += icon_stride;
}
}
m_parent.set_tooltip(tooltip);
}
void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos)
{
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
std::string tooltip = "";
for (GLToolbarItem* item : m_items)
{
if (item->is_separator())
top -= separator_stride;
else
{
float right = left + scaled_icons_size;
float bottom = top - scaled_icons_size;
GLToolbarItem::EState state = item->get_state();
bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top);
switch (state)
{
case GLToolbarItem::Normal:
{
if (inside)
item->set_state(GLToolbarItem::Hover);
break;
}
case GLToolbarItem::Hover:
{
if (inside)
tooltip = item->get_tooltip();
else
item->set_state(GLToolbarItem::Normal);
break;
}
case GLToolbarItem::Pressed:
{
if (inside)
item->set_state(GLToolbarItem::HoverPressed);
break;
}
case GLToolbarItem::HoverPressed:
{
if (inside)
tooltip = item->get_tooltip();
else
item->set_state(GLToolbarItem::Pressed);
break;
}
default:
case GLToolbarItem::Disabled:
{
break;
}
}
top -= icon_stride;
}
}
m_parent.set_tooltip(tooltip);
}
int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const
{
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
int id = -1;
for (GLToolbarItem* item : m_items)
{
++id;
if (item->is_separator())
left += separator_stride;
else
{
float right = left + scaled_icons_size;
float bottom = top - scaled_icons_size;
if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
return id;
left += icon_stride;
}
}
return -1;
}
int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos) const
{
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
int id = -1;
for (GLToolbarItem* item : m_items)
{
++id;
if (item->is_separator())
top -= separator_stride;
else
{
float right = left + scaled_icons_size;
float bottom = top - scaled_icons_size;
if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
return id;
top -= icon_stride;
}
}
return -1;
}
void GLToolbar::render_horizontal() const
{
unsigned int tex_id = m_icons_texture.texture.get_id();
int tex_size = m_icons_texture.texture.get_width();
if ((tex_id == 0) || (tex_size <= 0))
return;
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
// renders icons
for (const GLToolbarItem* item : m_items)
{
if (item->is_separator())
left += separator_stride;
else
{
item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size);
left += icon_stride;
}
}
}
void GLToolbar::render_vertical() const
{
unsigned int tex_id = m_icons_texture.texture.get_id();
int tex_size = m_icons_texture.texture.get_width();
if ((tex_id == 0) || (tex_size <= 0))
return;
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
// renders icons
for (const GLToolbarItem* item : m_items)
{
if (item->is_separator())
top -= separator_stride;
else
{
item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size);
top -= icon_stride;
}
}
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,175 @@
#ifndef slic3r_GLToolbar_hpp_
#define slic3r_GLToolbar_hpp_
#include "../../slic3r/GUI/GLTexture.hpp"
#include "../../callback.hpp"
#include <string>
#include <vector>
namespace Slic3r {
namespace GUI {
class GLCanvas3D;
class GLToolbarItem
{
public:
enum EType : unsigned char
{
Action,
Separator,
Num_Types
};
enum EState : unsigned char
{
Normal,
Pressed,
Disabled,
Hover,
HoverPressed,
Num_States
};
struct Data
{
std::string name;
std::string tooltip;
unsigned int sprite_id;
bool is_toggable;
PerlCallback* action_callback;
Data();
};
private:
EType m_type;
EState m_state;
Data m_data;
public:
GLToolbarItem(EType type, const Data& data);
EState get_state() const;
void set_state(EState state);
const std::string& get_name() const;
const std::string& get_tooltip() const;
void do_action();
bool is_enabled() const;
bool is_hovered() const;
bool is_pressed() const;
bool is_toggable() const;
bool is_separator() const;
void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
private:
GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
};
class GLToolbar
{
public:
// items icon textures are assumed to be square and all with the same size in pixels, no internal check is done
// icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState
// from left to right
struct ItemsIconsTexture
{
GLTexture texture;
// size of the square icons, in pixels
unsigned int items_icon_size;
// distance from the border, in pixels
unsigned int items_icon_border_size;
// distance between two adjacent icons (to avoid filtering artifacts), in pixels
unsigned int items_icon_gap_size;
ItemsIconsTexture();
};
struct Layout
{
enum Type : unsigned char
{
Horizontal,
Vertical,
Num_Types
};
Type type;
float top;
float left;
float separator_size;
float gap_size;
Layout();
};
private:
typedef std::vector<GLToolbarItem*> ItemsList;
GLCanvas3D& m_parent;
bool m_enabled;
ItemsIconsTexture m_icons_texture;
Layout m_layout;
ItemsList m_items;
public:
explicit GLToolbar(GLCanvas3D& parent);
bool init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size);
Layout::Type get_layout_type() const;
void set_layout_type(Layout::Type type);
void set_position(float top, float left);
void set_separator_size(float size);
void set_gap_size(float size);
bool is_enabled() const;
void set_enabled(bool enable);
bool add_item(const GLToolbarItem::Data& data);
bool add_separator();
float get_width() const;
float get_height() const;
void enable_item(const std::string& name);
void disable_item(const std::string& name);
bool is_item_pressed(const std::string& name) const;
void update_hover_state(const Vec2d& mouse_pos);
// returns the id of the item under the given mouse position or -1 if none
int contains_mouse(const Vec2d& mouse_pos) const;
void do_action(unsigned int item_id);
void render() const;
private:
float get_width_horizontal() const;
float get_width_vertical() const;
float get_height_horizontal() const;
float get_height_vertical() const;
float get_main_size() const;
void update_hover_state_horizontal(const Vec2d& mouse_pos);
void update_hover_state_vertical(const Vec2d& mouse_pos);
int contains_mouse_horizontal(const Vec2d& mouse_pos) const;
int contains_mouse_vertical(const Vec2d& mouse_pos) const;
void render_horizontal() const;
void render_vertical() const;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLToolbar_hpp_

1402
src/slic3r/GUI/GUI.cpp Normal file

File diff suppressed because it is too large Load diff

260
src/slic3r/GUI/GUI.hpp Normal file
View file

@ -0,0 +1,260 @@
#ifndef slic3r_GUI_hpp_
#define slic3r_GUI_hpp_
#include <string>
#include <vector>
#include "PrintConfig.hpp"
#include "../../callback.hpp"
#include "GUI_ObjectParts.hpp"
#include <wx/intl.h>
#include <wx/string.h>
class wxApp;
class wxWindow;
class wxFrame;
class wxMenuBar;
class wxNotebook;
class wxPanel;
class wxComboCtrl;
class wxString;
class wxArrayString;
class wxArrayLong;
class wxColour;
class wxBoxSizer;
class wxFlexGridSizer;
class wxButton;
class wxFileDialog;
class wxStaticBitmap;
class wxFont;
class wxTopLevelWindow;
namespace Slic3r {
class PresetBundle;
class PresetCollection;
class Print;
class ProgressStatusBar;
class AppConfig;
class PresetUpdater;
class DynamicPrintConfig;
class TabIface;
class PreviewIface;
class Print;
class GCodePreviewData;
#define _(s) Slic3r::GUI::I18N::translate((s))
namespace GUI { namespace I18N {
inline wxString translate(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)); }
inline wxString translate(const wchar_t *s) { return wxGetTranslation(s); }
inline wxString translate(const std::string &s) { return wxGetTranslation(wxString(s.c_str(), wxConvUTF8)); }
inline wxString translate(const std::wstring &s) { return wxGetTranslation(s.c_str()); }
} }
// !!! If you needed to translate some wxString,
// !!! please use _(L(string))
// !!! _() - is a standard wxWidgets macro to translate
// !!! L() is used only for marking localizable string
// !!! It will be used in "xgettext" to create a Locating Message Catalog.
#define L(s) s
//! macro used to localization, return wxScopedCharBuffer
//! With wxConvUTF8 explicitly specify that the source string is already in UTF-8 encoding
#define _CHB(s) wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str()
// Minimal buffer length for translated string (char buf[MIN_BUF_LENGTH_FOR_L])
#define MIN_BUF_LENGTH_FOR_L 512
namespace GUI {
class Tab;
class ConfigOptionsGroup;
// Map from an file_type name to full file wildcard name.
typedef std::map<std::string, std::string> t_file_wild_card;
inline t_file_wild_card& get_file_wild_card() {
static t_file_wild_card FILE_WILDCARDS;
if (FILE_WILDCARDS.empty()){
FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA";
FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL";
FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ";
FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML";
FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;";
FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA";
FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI";
FILE_WILDCARDS["gcode"] = "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC";
FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG";
}
return FILE_WILDCARDS;
}
struct PresetTab {
std::string name;
Tab* panel;
PrinterTechnology technology;
};
void disable_screensaver();
void enable_screensaver();
bool debugged();
void break_to_debugger();
// Passing the wxWidgets GUI classes instantiated by the Perl part to C++.
void set_wxapp(wxApp *app);
void set_main_frame(wxFrame *main_frame);
void set_progress_status_bar(ProgressStatusBar *prsb);
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);
void set_preset_updater(PresetUpdater *updater);
void set_objects_from_perl( wxWindow* parent,
wxBoxSizer *frequently_changed_parameters_sizer,
wxBoxSizer *info_sizer,
wxButton *btn_export_gcode,
wxButton *btn_reslice,
wxButton *btn_print,
wxButton *btn_send_gcode,
wxStaticBitmap *manifold_warning_icon);
void set_show_print_info(bool show);
void set_show_manifold_warning_icon(bool show);
void set_objects_list_sizer(wxBoxSizer *objects_list_sizer);
AppConfig* get_app_config();
wxApp* get_app();
PresetBundle* get_preset_bundle();
wxFrame* get_main_frame();
ProgressStatusBar* get_progress_status_bar();
wxNotebook * get_tab_panel();
wxNotebook* get_tab_panel();
const wxColour& get_label_clr_modified();
const wxColour& get_label_clr_sys();
const wxColour& get_label_clr_default();
unsigned get_colour_approx_luma(const wxColour &colour);
void set_label_clr_modified(const wxColour& clr);
void set_label_clr_sys(const wxColour& clr);
const wxFont& small_font();
const wxFont& bold_font();
void open_model(wxWindow *parent, wxArrayString& input_files);
wxWindow* get_right_panel();
const size_t& label_width();
Tab* get_tab(const std::string& name);
const std::vector<PresetTab>& get_preset_tabs();
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
// This is called when closing the application, when loading a config file or when starting the config wizard
// to notify the user whether he is aware that some preset changes will be lost.
extern bool check_unsaved_changes();
// Checks if configuration wizard needs to run, calls config_wizard if so.
// Returns whether the Wizard ran.
extern bool config_wizard_startup(bool app_config_exists);
// Opens the configuration wizard, returns true if wizard is finished & accepted.
// The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl.
extern void config_wizard(int run_reason);
// Create "Preferences" dialog after selecting menu "Preferences" in Perl part
extern void open_preferences_dialog(int event_preferences);
// Create a new preset tab (print, filament and printer),
void create_preset_tabs(int event_value_change, int event_presets_changed);
TabIface* get_preset_tab_iface(char *name);
PreviewIface* create_preview_iface(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data);
// add it at the end of the tab panel.
void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed);
// Change option value in config
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
// Update UI / Tabs to reflect changes in the currently loaded presets
void load_current_presets();
void show_error(wxWindow* parent, const wxString& message);
void show_error_id(int id, const std::string& message); // For Perl
void show_info(wxWindow* parent, const wxString& message, const wxString& title);
void warning_catcher(wxWindow* parent, const 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
void save_language();
// get list of installed languages
void get_installed_languages(wxArrayString & names, wxArrayLong & identifiers);
// select language from the list of installed languages
bool select_language(wxArrayString & names, wxArrayLong & identifiers);
// update right panel of the Plater according to view mode
void update_mode();
void show_info_sizer(const bool show);
std::vector<Tab *>& get_tabs_list();
bool checked_tab(Tab* tab);
void delete_tab_from_list(Tab* tab);
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
// Items are all initialized to the given value.
// Items must be separated by '|', for example "Item1|Item2|Item3", and so on.
void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value);
// Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
// encoded inside an int.
int combochecklist_get_flags(wxComboCtrl* comboCtrl);
// Return translated std::string as a wxString
wxString L_str(const std::string &str);
// Return wxString from std::string in UTF8
wxString from_u8(const std::string &str);
void set_model_events_from_perl(Model &model,
int event_object_selection_changed,
int event_object_settings_changed,
int event_remove_object,
int event_update_scene);
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer);
// Update view mode according to selected menu
void update_mode();
bool is_expert_mode();
// Callback to trigger a configuration update timer on the Plater.
static PerlCallback g_on_request_update_callback;
ConfigOptionsGroup* get_optgroup(size_t i);
std::vector <std::shared_ptr<ConfigOptionsGroup>>& get_optgroups();
wxButton* get_wiping_dialog_button();
void add_export_option(wxFileDialog* dlg, const std::string& format);
int get_export_option(wxFileDialog* dlg);
// Returns the dimensions of the screen on which the main frame is displayed
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height);
// Save window size and maximized status into AppConfig
void save_window_size(wxTopLevelWindow *window, const std::string &name);
// Restore the above
void restore_window_size(wxTopLevelWindow *window, const std::string &name);
// Update buttons view according to enable/disable
void enable_action_buttons(bool enable);
// Display an About dialog
extern void about();
// Ask the destop to open the datadir using the default file explorer.
extern void desktop_open_datadir_folder();
} // namespace GUI
} // namespace Slic3r
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,147 @@
#ifndef slic3r_GUI_ObjectParts_hpp_
#define slic3r_GUI_ObjectParts_hpp_
class wxWindow;
class wxSizer;
class wxBoxSizer;
class wxString;
class wxArrayString;
class wxMenu;
class wxDataViewEvent;
class wxKeyEvent;
class wxGLCanvas;
class wxBitmap;
namespace Slic3r {
class ModelObject;
class Model;
namespace GUI {
//class wxGLCanvas;
enum ogGroup{
ogFrequentlyChangingParameters,
ogFrequentlyObjectSettings,
ogCurrentSettings
// ogObjectSettings,
// ogObjectMovers,
// ogPartSettings
};
enum LambdaTypeIDs{
LambdaTypeBox,
LambdaTypeCylinder,
LambdaTypeSphere,
LambdaTypeSlab
};
struct OBJECT_PARAMETERS
{
LambdaTypeIDs type = LambdaTypeBox;
double dim[3];// = { 1.0, 1.0, 1.0 };
int cyl_r = 1;
int cyl_h = 1;
double sph_rho = 1.0;
double slab_h = 1.0;
double slab_z = 0.0;
};
typedef std::map<std::string, wxBitmap> t_category_icon;
inline t_category_icon& get_category_icon();
void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer);
void add_objects_list(wxWindow* parent, wxBoxSizer* sizer);
void add_object_settings(wxWindow* parent, wxBoxSizer* sizer);
void show_collpane_settings(bool expert_mode);
wxMenu *create_add_settings_popupmenu(bool is_part);
wxMenu *create_add_part_popupmenu();
wxMenu *create_part_settings_popupmenu();
// Add object to the list
//void add_object(const std::string &name);
void add_object_to_list(const std::string &name, ModelObject* model_object);
// Delete object from the list
void delete_object_from_list();
// Delete all objects from the list
void delete_all_objects_from_list();
// Set count of object on c++ side
void set_object_count(int idx, int count);
// Unselect all objects in the list on c++ side
void unselect_objects();
// Select current object in the list on c++ side
void select_current_object(int idx);
// Select current volume in the list on c++ side
void select_current_volume(int idx, int vol_idx);
// Remove objects/sub-object from the list
void remove();
void object_ctrl_selection_changed();
void object_ctrl_context_menu();
void object_ctrl_key_event(wxKeyEvent& event);
void object_ctrl_item_value_change(wxDataViewEvent& event);
void show_context_menu();
bool is_splittable_object(const bool split_part);
void init_mesh_icons();
void set_event_object_selection_changed(const int& event);
void set_event_object_settings_changed(const int& event);
void set_event_remove_object(const int& event);
void set_event_update_scene(const int& event);
void set_objects_from_model(Model &model);
bool is_parts_changed();
bool is_part_settings_changed();
void load_part( ModelObject* model_object,
wxArrayString& part_names, const bool is_modifier);
void load_lambda( ModelObject* model_object,
wxArrayString& part_names, const bool is_modifier);
void load_lambda( const std::string& type_name);
void on_btn_load(bool is_modifier = false, bool is_lambda = false);
void on_btn_del();
void on_btn_split(const bool split_part);
void on_btn_move_up();
void on_btn_move_down();
void parts_changed(int obj_idx);
void part_selection_changed();
void update_settings_value();
// show/hide "Extruder" column for Objects List
void set_extruder_column_hidden(bool hide);
// update extruder in current config
void update_extruder_in_config(const wxString& selection);
// update position values displacements or "gizmos"
void update_position_values();
void update_position_values(const Vec3d& position);
// update scale values after scale unit changing or "gizmos"
void update_scale_values();
void update_scale_values(double scaling_factor);
// update rotation values object selection changing
void update_rotation_values();
// update rotation value after "gizmos"
void update_rotation_value(double angle, Axis axis);
void set_uniform_scaling(const bool uniform_scale);
void on_begin_drag(wxDataViewEvent &event);
void on_drop_possible(wxDataViewEvent &event);
void on_drop(wxDataViewEvent &event);
// update extruder column for objects_ctrl according to extruders count
void update_objects_list_extruder_column(int extruders_count);
// Create/Update/Reset double slider on 3dPreview
void create_double_slider(wxWindow* parent, wxBoxSizer* sizer, wxGLCanvas* canvas);
void update_double_slider(bool force_sliders_full_range);
void reset_double_slider();
// update DoubleSlider after keyDown in canvas
void update_double_slider_from_canvas(wxKeyEvent& event);
void show_manipulation_sizer(const bool is_simple_mode);
} //namespace GUI
} //namespace Slic3r
#endif //slic3r_GUI_ObjectParts_hpp_

View file

@ -0,0 +1,199 @@
#include "LambdaObjectDialog.hpp"
#include <wx/window.h>
#include <wx/button.h>
#include "OptionsGroup.hpp"
namespace Slic3r
{
namespace GUI
{
static wxString dots("", wxConvUTF8);
LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent,
const wxString type_name):
m_type_name(type_name)
{
Create(parent, wxID_ANY, _(L("Lambda Object")),
parent->GetScreenPosition(), wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
// instead of double dim[3] = { 1.0, 1.0, 1.0 };
object_parameters.dim[0] = 1.0;
object_parameters.dim[1] = 1.0;
object_parameters.dim[2] = 1.0;
sizer = new wxBoxSizer(wxVERTICAL);
// modificator options
if (m_type_name == wxEmptyString) {
m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxCHB_TOP);
sizer->Add(m_modificator_options_book, 1, wxEXPAND | wxALL, 10);
}
else {
m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
sizer->Add(m_panel, 1, wxEXPAND | wxALL, 10);
}
ConfigOptionDef def;
def.width = 70;
auto optgroup = init_modificator_options_page(_(L("Box")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
int opt_id = opt_key == "l" ? 0 :
opt_key == "w" ? 1 :
opt_key == "h" ? 2 : -1;
if (opt_id < 0) return;
object_parameters.dim[opt_id] = boost::any_cast<double>(value);
};
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("L");
Option option(def, "l");
optgroup->append_single_option_line(option);
def.label = L("W");
option = Option(def, "w");
optgroup->append_single_option_line(option);
def.label = L("H");
option = Option(def, "h");
optgroup->append_single_option_line(option);
}
optgroup = init_modificator_options_page(_(L("Cylinder")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
int val = boost::any_cast<int>(value);
if (opt_key == "cyl_r")
object_parameters.cyl_r = val;
else if (opt_key == "cyl_h")
object_parameters.cyl_h = val;
else return;
};
def.type = coInt;
def.default_value = new ConfigOptionInt{ 1 };
def.label = L("Radius");
auto option = Option(def, "cyl_r");
optgroup->append_single_option_line(option);
def.label = L("Height");
option = Option(def, "cyl_h");
optgroup->append_single_option_line(option);
}
optgroup = init_modificator_options_page(_(L("Sphere")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
if (opt_key == "sph_rho")
object_parameters.sph_rho = boost::any_cast<double>(value);
else return;
};
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("Rho");
auto option = Option(def, "sph_rho");
optgroup->append_single_option_line(option);
}
optgroup = init_modificator_options_page(_(L("Slab")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
double val = boost::any_cast<double>(value);
if (opt_key == "slab_z")
object_parameters.slab_z = val;
else if (opt_key == "slab_h")
object_parameters.slab_h = val;
else return;
};
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("H");
auto option = Option(def, "slab_h");
optgroup->append_single_option_line(option);
def.label = L("Initial Z");
option = Option(def, "slab_z");
optgroup->append_single_option_line(option);
}
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e)
{
auto page_idx = m_modificator_options_book->GetSelection();
if (page_idx < 0) return;
switch (page_idx)
{
case 0:
object_parameters.type = LambdaTypeBox;
break;
case 1:
object_parameters.type = LambdaTypeCylinder;
break;
case 2:
object_parameters.type = LambdaTypeSphere;
break;
case 3:
object_parameters.type = LambdaTypeSlab;
break;
default:
break;
}
}));
const auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btn_OK = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
// validate user input
if (!CanClose())return;
EndModal(wxID_OK);
Destroy();
});
wxButton* btn_CANCEL = static_cast<wxButton*>(FindWindowById(wxID_CANCEL, this));
btn_CANCEL->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
// validate user input
if (!CanClose())return;
EndModal(wxID_CANCEL);
Destroy();
});
sizer->Add(button_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(sizer);
sizer->Fit(this);
sizer->SetSizeHints(this);
}
// Called from the constructor.
// Create a panel for a rectangular / circular / custom bed shape.
ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wxString& title){
if (!m_type_name.IsEmpty() && m_type_name != title)
return nullptr;
auto panel = m_type_name.IsEmpty() ? new wxPanel(m_modificator_options_book) : m_panel;
ConfigOptionsGroupShp optgroup;
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Add")) + " " +title + " " +dots);
optgroup->label_width = 100;
m_optgroups.push_back(optgroup);
if (m_type_name.IsEmpty()) {
panel->SetSizerAndFit(optgroup->sizer);
m_modificator_options_book->AddPage(panel, title);
}
else
panel->SetSizer(optgroup->sizer);
return optgroup;
}
} //namespace GUI
} //namespace Slic3r

View file

@ -0,0 +1,40 @@
#ifndef slic3r_LambdaObjectDialog_hpp_
#define slic3r_LambdaObjectDialog_hpp_
#include "GUI.hpp"
#include <wx/dialog.h>
#include <wx/sizer.h>
#include <wx/choicebk.h>
class wxPanel;
namespace Slic3r
{
namespace GUI
{
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
class LambdaObjectDialog : public wxDialog
{
wxChoicebook* m_modificator_options_book = nullptr;
std::vector <ConfigOptionsGroupShp> m_optgroups;
wxString m_type_name;
wxPanel* m_panel = nullptr;
public:
LambdaObjectDialog(wxWindow* parent,
const wxString type_name = wxEmptyString);
~LambdaObjectDialog(){}
bool CanClose() { return true; } // ???
OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; }
ConfigOptionsGroupShp init_modificator_options_page(const wxString& title);
// Note whether the window was already closed, so a pending update is not executed.
bool m_already_closed = false;
OBJECT_PARAMETERS object_parameters;
wxBoxSizer* sizer = nullptr;
};
} //namespace GUI
} //namespace Slic3r
#endif //slic3r_LambdaObjectDialog_hpp_

View file

@ -0,0 +1,88 @@
#include "MsgDialog.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/statbmp.h>
#include <wx/scrolwin.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "ConfigWizard.hpp"
namespace Slic3r {
namespace GUI {
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id) :
MsgDialog(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id)
{}
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) :
wxDialog(parent, wxID_ANY, title),
boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)),
content_sizer(new wxBoxSizer(wxVERTICAL)),
btn_sizer(new wxBoxSizer(wxHORIZONTAL))
{
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *rightsizer = new wxBoxSizer(wxVERTICAL);
auto *headtext = new wxStaticText(this, wxID_ANY, headline);
headtext->SetFont(boldfont);
headtext->Wrap(CONTENT_WIDTH);
rightsizer->Add(headtext);
rightsizer->AddSpacer(VERT_SPACING);
rightsizer->Add(content_sizer, 1, wxEXPAND);
if (button_id != wxID_NONE) {
auto *button = new wxButton(this, button_id);
button->SetFocus();
btn_sizer->Add(button);
}
rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL);
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap));
topsizer->Add(logo, 0, wxALL, BORDER);
topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER);
SetSizerAndFit(topsizer);
}
MsgDialog::~MsgDialog() {}
// ErrorDialog
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) :
MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG))
{
auto *panel = new wxScrolledWindow(this);
auto *p_sizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(p_sizer);
auto *text = new wxStaticText(panel, wxID_ANY, msg);
text->Wrap(CONTENT_WIDTH);
p_sizer->Add(text, 1, wxEXPAND);
panel->SetMinSize(wxSize(CONTENT_WIDTH, 0));
panel->SetScrollRate(0, 5);
content_sizer->Add(panel, 1, wxEXPAND);
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT));
Fit();
}
ErrorDialog::~ErrorDialog() {}
}
}

View file

@ -0,0 +1,67 @@
#ifndef slic3r_MsgDialog_hpp_
#define slic3r_MsgDialog_hpp_
#include <string>
#include <unordered_map>
#include <wx/dialog.h>
#include <wx/font.h>
#include <wx/bitmap.h>
#include "slic3r/Utils/Semver.hpp"
class wxBoxSizer;
class wxCheckBox;
namespace Slic3r {
namespace GUI {
// A message / query dialog with a bitmap on the left and any content on the right
// with buttons underneath.
struct MsgDialog : wxDialog
{
MsgDialog(MsgDialog &&) = delete;
MsgDialog(const MsgDialog &) = delete;
MsgDialog &operator=(MsgDialog &&) = delete;
MsgDialog &operator=(const MsgDialog &) = delete;
virtual ~MsgDialog();
// TODO: refactor with CreateStdDialogButtonSizer usage
protected:
enum {
CONTENT_WIDTH = 500,
CONTENT_MAX_HEIGHT = 600,
BORDER = 30,
VERT_SPACING = 15,
HORIZ_SPACING = 5,
};
// button_id is an id of a button that can be added by default, use wxID_NONE to disable
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK);
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id = wxID_OK);
wxFont boldfont;
wxBoxSizer *content_sizer;
wxBoxSizer *btn_sizer;
};
// Generic error dialog, used for displaying exceptions
struct ErrorDialog : MsgDialog
{
ErrorDialog(wxWindow *parent, const wxString &msg);
ErrorDialog(ErrorDialog &&) = delete;
ErrorDialog(const ErrorDialog &) = delete;
ErrorDialog &operator=(ErrorDialog &&) = delete;
ErrorDialog &operator=(const ErrorDialog &) = delete;
virtual ~ErrorDialog();
};
}
}
#endif

View file

@ -0,0 +1,545 @@
#include "OptionsGroup.hpp"
#include "ConfigExceptions.hpp"
#include <utility>
#include <wx/numformatter.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "Utils.hpp"
namespace Slic3r { namespace GUI {
const t_field& OptionsGroup::build_field(const Option& opt, wxStaticText* label/* = nullptr*/) {
return build_field(opt.opt_id, opt.opt, label);
}
const t_field& OptionsGroup::build_field(const t_config_option_key& id, wxStaticText* label/* = nullptr*/) {
const ConfigOptionDef& opt = m_options.at(id).opt;
return build_field(id, opt, label);
}
const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label/* = nullptr*/) {
// Check the gui_type field first, fall through
// is the normal type.
if (opt.gui_type.compare("select") == 0) {
} else if (opt.gui_type.compare("select_open") == 0) {
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
} else if (opt.gui_type.compare("color") == 0) {
m_fields.emplace(id, STDMOVE(ColourPicker::Create<ColourPicker>(parent(), opt, id)));
} else if (opt.gui_type.compare("f_enum_open") == 0 ||
opt.gui_type.compare("i_enum_open") == 0 ||
opt.gui_type.compare("i_enum_closed") == 0) {
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
} else if (opt.gui_type.compare("slider") == 0) {
m_fields.emplace(id, STDMOVE(SliderCtrl::Create<SliderCtrl>(parent(), opt, id)));
} else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl
} else if (opt.gui_type.compare("legend") == 0) { // StaticText
m_fields.emplace(id, STDMOVE(StaticText::Create<StaticText>(parent(), opt, id)));
} else {
switch (opt.type) {
case coFloatOrPercent:
case coFloat:
case coFloats:
case coPercent:
case coPercents:
case coString:
case coStrings:
m_fields.emplace(id, STDMOVE(TextCtrl::Create<TextCtrl>(parent(), opt, id)));
break;
case coBool:
case coBools:
m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(parent(), opt, id)));
break;
case coInt:
case coInts:
m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(parent(), opt, id)));
break;
case coEnum:
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
break;
case coPoints:
m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(parent(), opt, id)));
break;
case coNone: break;
default:
throw /*//!ConfigGUITypeError("")*/std::logic_error("This control doesn't exist till now"); break;
}
}
// Grab a reference to fields for convenience
const t_field& field = m_fields[id];
field->m_on_change = [this](std::string opt_id, boost::any value){
//! This function will be called from Field.
//! Call OptionGroup._on_change(...)
if (!m_disabled)
this->on_change_OG(opt_id, value);
};
field->m_on_kill_focus = [this](){
//! This function will be called from Field.
if (!m_disabled)
this->on_kill_focus();
};
field->m_parent = parent();
//! Label to change background color, when option is modified
field->m_Label = label;
field->m_back_to_initial_value = [this](std::string opt_id){
if (!m_disabled)
this->back_to_initial_value(opt_id);
};
field->m_back_to_sys_value = [this](std::string opt_id){
if (!this->m_disabled)
this->back_to_sys_value(opt_id);
};
// assign function objects for callbacks, etc.
return field;
}
void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field)
{
if (!m_show_modified_btns) {
field->m_Undo_btn->Hide();
field->m_Undo_to_sys_btn->Hide();
return;
}
sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL);
}
void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* = nullptr*/) {
//! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)){
if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width){
if (line.sizer != nullptr) {
sizer->Add(line.sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 15);
return;
}
if (line.widget != nullptr) {
sizer->Add(line.widget(m_parent), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15);
return;
}
}
auto option_set = line.get_options();
for (auto opt : option_set)
m_options.emplace(opt.opt_id, opt);
// if we have a single option with no label, no sidetext just add it directly to sizer
if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width &&
option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr &&
line.get_extra_widgets().size() == 0) {
wxSizer* tmp_sizer;
#ifdef __WXGTK__
tmp_sizer = new wxBoxSizer(wxVERTICAL);
m_panel->SetSizer(tmp_sizer);
m_panel->Layout();
#else
tmp_sizer = sizer;
#endif /* __WXGTK__ */
const auto& option = option_set.front();
const auto& field = build_field(option);
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
add_undo_buttuns_to_sizer(btn_sizer, field);
tmp_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0);
if (is_window_field(field))
tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
if (is_sizer_field(field))
tmp_sizer->Add(field->getSizer(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
return;
}
auto grid_sizer = m_grid_sizer;
#ifdef __WXGTK__
m_panel->SetSizer(m_grid_sizer);
m_panel->Layout();
#endif /* __WXGTK__ */
// if we have an extra column, build it
if (extra_column) {
if (extra_column) {
grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3);
}
else {
// if the callback provides no sizer for the extra cell, put a spacer
grid_sizer->AddSpacer(1);
}
}
// Build a label if we have it
wxStaticText* label=nullptr;
if (label_width != 0) {
long label_style = staticbox ? 0 : wxALIGN_RIGHT;
#ifdef __WXGTK__
// workaround for correct text align of the StaticBox on Linux
// flags wxALIGN_RIGHT and wxALIGN_CENTRE don't work when Ellipsize flags are _not_ given.
// Text is properly aligned only when Ellipsize is checked.
label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END;
#endif /* __WXGTK__ */
label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"),
wxDefaultPosition, wxSize(label_width, -1), label_style);
label->SetFont(label_font);
label->Wrap(label_width); // avoid a Linux/GTK bug
if (!line.near_label_widget)
grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) |
(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxTOP : wxALIGN_CENTER_VERTICAL), 5);
else {
// If we're here, we have some widget near the label
// so we need a horizontal sizer to arrange these things
auto sizer = new wxBoxSizer(wxHORIZONTAL);
grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1);
sizer->Add(line.near_label_widget(parent()), 0, wxRIGHT, 7);
sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) |
(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxTOP : wxALIGN_CENTER_VERTICAL), 5);
}
if (line.label_tooltip.compare("") != 0)
label->SetToolTip(line.label_tooltip);
}
// If there's a widget, build it and add the result to the sizer.
if (line.widget != nullptr) {
auto wgt = line.widget(parent());
// If widget doesn't have label, don't use border
grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5);
if (colored_Label != nullptr) *colored_Label = label;
return;
}
// If we're here, we have more than one option or a single option with sidetext
// so we need a horizontal sizer to arrange these things
auto sizer = new wxBoxSizer(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxVERTICAL : wxHORIZONTAL);
grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1);
// If we have a single option with no sidetext just add it directly to the grid sizer
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
const auto& option = option_set.front();
const auto& field = build_field(option, label);
add_undo_buttuns_to_sizer(sizer, field);
if (is_window_field(field))
sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) |
wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, (wxOSX||!staticbox) ? 0 : 2);
if (is_sizer_field(field))
sizer->Add(field->getSizer(), 1, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
return;
}
for (auto opt : option_set) {
ConfigOptionDef option = opt.opt;
wxSizer* sizer_tmp;
if (m_flag == ogSIDE_OPTIONS_VERTICAL){
auto sz = new wxFlexGridSizer(1, 3, 2, 2);
sz->RemoveGrowableCol(2);
sizer_tmp = sz;
}
else
sizer_tmp = sizer;
// add label if any
if (option.label != "") {
wxString str_label = _(option.label);
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
// wxString str_label = (option.label == "Top" || option.label == "Bottom") ?
// wxGETTEXT_IN_CONTEXT("Layers", wxString(option.label.c_str()):
// L_str(option.label);
label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize);
label->SetFont(label_font);
sizer_tmp->Add(label, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 0);
}
// add field
const Option& opt_ref = opt;
auto& field = build_field(opt_ref, label);
add_undo_buttuns_to_sizer(sizer_tmp, field);
is_sizer_field(field) ?
sizer_tmp->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) :
sizer_tmp->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0);
// add sidetext if any
if (option.sidetext != "") {
auto sidetext = new wxStaticText( parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition,
wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT);
sidetext->SetFont(sidetext_font);
sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, m_flag == ogSIDE_OPTIONS_VERTICAL ? 0 : 4);
field->set_side_text_ptr(sidetext);
}
// add side widget if any
if (opt.side_widget != nullptr) {
sizer_tmp->Add(opt.side_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification
}
if (opt.opt_id != option_set.back().opt_id && m_flag != ogSIDE_OPTIONS_VERTICAL) //! istead of (opt != option_set.back())
{
sizer_tmp->AddSpacer(6);
}
if (m_flag == ogSIDE_OPTIONS_VERTICAL)
sizer->Add(sizer_tmp, 0, wxALIGN_RIGHT|wxALL, 0);
}
// add extra sizers if any
for (auto extra_widget : line.get_extra_widgets()) {
sizer->Add(extra_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification
}
}
Line OptionsGroup::create_single_option_line(const Option& option) const {
Line retval{ _(option.opt.label), _(option.opt.tooltip) };
Option tmp(option);
tmp.opt.label = std::string("");
retval.append_option(tmp);
return retval;
}
void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) {
if (m_on_change != nullptr)
m_on_change(opt_id, value);
}
Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/)
{
if (!m_config->has(opt_key)) {
std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.\n";
}
std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index);
std::pair<std::string, int> pair(opt_key, opt_index);
m_opt_map.emplace(opt_id, pair);
return Option(*m_config->def()->get(opt_key), opt_id);
}
void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value)
{
if (!m_opt_map.empty())
{
auto it = m_opt_map.find(opt_id);
if (it == m_opt_map.end())
{
OptionsGroup::on_change_OG(opt_id, value);
return;
}
auto itOption = it->second;
std::string opt_key = itOption.first;
int opt_index = itOption.second;
auto option = m_options.at(opt_id).opt;
// get value
//! auto field_value = get_value(opt_id);
if (option.gui_flags.compare("serialized")==0) {
if (opt_index != -1){
// die "Can't set serialized option indexed value" ;
}
change_opt_value(*m_config, opt_key, value);
}
else {
if (opt_index == -1) {
// change_opt_value(*m_config, opt_key, field_value);
//!? why field_value?? in this case changed value will be lose! No?
change_opt_value(*m_config, opt_key, value);
}
else {
change_opt_value(*m_config, opt_key, value, opt_index);
// auto value = m_config->get($opt_key);
// $value->[$opt_index] = $field_value;
// $self->config->set($opt_key, $value);
}
}
}
OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this
}
void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key)
{
if (m_get_initial_config == nullptr)
return;
back_to_config_value(m_get_initial_config(), opt_key);
}
void ConfigOptionsGroup::back_to_sys_value(const std::string& opt_key)
{
if (m_get_sys_config == nullptr)
return;
if (!have_sys_config())
return;
back_to_config_value(m_get_sys_config(), opt_key);
}
void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key)
{
boost::any value;
if (opt_key == "extruders_count"){
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
value = int(nozzle_diameter->values.size());
}
else if (m_opt_map.find(opt_key) != m_opt_map.end())
{
auto opt_id = m_opt_map.find(opt_key)->first;
std::string opt_short_key = m_opt_map.at(opt_id).first;
int opt_index = m_opt_map.at(opt_id).second;
value = get_config_value(config, opt_short_key, opt_index);
}
else{
value = get_config_value(config, opt_key);
change_opt_value(*m_config, opt_key, value);
return;
}
set_value(opt_key, value);
on_change_OG(opt_key, get_value(opt_key));
}
void ConfigOptionsGroup::reload_config(){
for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
auto opt_id = it->first;
std::string opt_key = m_opt_map.at(opt_id).first;
int opt_index = m_opt_map.at(opt_id).second;
auto option = m_options.at(opt_id).opt;
set_value(opt_id, config_value(opt_key, opt_index, option.gui_flags.compare("serialized") == 0 ));
}
}
boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_index, bool deserialize){
if (deserialize) {
// Want to edit a vector value(currently only multi - strings) in a single edit box.
// Aggregate the strings the old way.
// Currently used for the post_process config value only.
if (opt_index != -1)
throw std::out_of_range("Can't deserialize option indexed value");
// return join(';', m_config->get(opt_key)});
return get_config_value(*m_config, opt_key);
}
else {
// return opt_index == -1 ? m_config->get(opt_key) : m_config->get_at(opt_key, opt_index);
return get_config_value(*m_config, opt_key, opt_index);
}
}
boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index /*= -1*/)
{
size_t idx = opt_index == -1 ? 0 : opt_index;
boost::any ret;
wxString text_value = wxString("");
const ConfigOptionDef* opt = config.def()->get(opt_key);
switch (opt->type){
case coFloatOrPercent:{
const auto &value = *config.option<ConfigOptionFloatOrPercent>(opt_key);
if (value.percent)
{
text_value = wxString::Format(_T("%i"), int(value.value));
text_value += "%";
}
else
text_value = double_to_string(value.value);
ret = text_value;
break;
}
case coPercent:{
double val = config.option<ConfigOptionPercent>(opt_key)->value;
text_value = wxString::Format(_T("%i"), int(val));
ret = text_value;// += "%";
}
break;
case coPercents:
case coFloats:
case coFloat:{
double val = opt->type == coFloats ?
config.opt_float(opt_key, idx) :
opt->type == coFloat ? config.opt_float(opt_key) :
config.option<ConfigOptionPercents>(opt_key)->get_at(idx);
ret = double_to_string(val);
}
break;
case coString:
ret = static_cast<wxString>(config.opt_string(opt_key));
break;
case coStrings:
if (opt_key.compare("compatible_printers") == 0){
ret = config.option<ConfigOptionStrings>(opt_key)->values;
break;
}
if (config.option<ConfigOptionStrings>(opt_key)->values.empty())
ret = text_value;
else if (opt->gui_flags.compare("serialized") == 0){
std::vector<std::string> values = config.option<ConfigOptionStrings>(opt_key)->values;
if (!values.empty() && values[0].compare("") != 0)
for (auto el : values)
text_value += el + ";";
ret = text_value;
}
else
ret = static_cast<wxString>(config.opt_string(opt_key, static_cast<unsigned int>(idx)));
break;
case coBool:
ret = config.opt_bool(opt_key);
break;
case coBools:
ret = config.opt_bool(opt_key, idx);
break;
case coInt:
ret = config.opt_int(opt_key);
break;
case coInts:
ret = config.opt_int(opt_key, idx);
break;
case coEnum:{
if (opt_key.compare("external_fill_pattern") == 0 ||
opt_key.compare("fill_pattern") == 0 ){
ret = static_cast<int>(config.option<ConfigOptionEnum<InfillPattern>>(opt_key)->value);
}
else if (opt_key.compare("gcode_flavor") == 0 ){
ret = static_cast<int>(config.option<ConfigOptionEnum<GCodeFlavor>>(opt_key)->value);
}
else if (opt_key.compare("support_material_pattern") == 0){
ret = static_cast<int>(config.option<ConfigOptionEnum<SupportMaterialPattern>>(opt_key)->value);
}
else if (opt_key.compare("seam_position") == 0){
ret = static_cast<int>(config.option<ConfigOptionEnum<SeamPosition>>(opt_key)->value);
}
else if (opt_key.compare("host_type") == 0){
ret = static_cast<int>(config.option<ConfigOptionEnum<PrintHostType>>(opt_key)->value);
}
}
break;
case coPoints:
if (opt_key.compare("bed_shape") == 0)
ret = config.option<ConfigOptionPoints>(opt_key)->values;
else
ret = config.option<ConfigOptionPoints>(opt_key)->get_at(idx);
break;
case coNone:
default:
break;
}
return ret;
}
Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int opt_index){
Field* field = get_field(opt_key);
if (field != nullptr)
return field;
std::string opt_id = "";
for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
if (opt_key == m_opt_map.at(it->first).first && opt_index == m_opt_map.at(it->first).second){
opt_id = it->first;
break;
}
}
return opt_id.empty() ? nullptr : get_field(opt_id);
}
void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
{
SetLabel(value);
if (wrap) Wrap(400);
GetParent()->Layout();
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,271 @@
#ifndef slic3r_OptionsGroup_hpp_
#define slic3r_OptionsGroup_hpp_
#include <wx/wx.h>
#include <wx/stattext.h>
#include <wx/settings.h>
//#include <wx/window.h>
#include <map>
#include <functional>
#include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/libslic3r.h"
#include "Field.hpp"
// Translate the ifdef
#ifdef __WXOSX__
#define wxOSX true
#else
#define wxOSX false
#endif
#define BORDER(a, b) ((wxOSX ? a : b))
namespace Slic3r { namespace GUI {
enum ogDrawFlag{
ogDEFAULT,
ogSIDE_OPTIONS_VERTICAL
};
/// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window).
using widget_t = std::function<wxSizer*(wxWindow*)>;//!std::function<wxWindow*(wxWindow*)>;
//auto default_label_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
//auto modified_label_clr = *new wxColour(254, 189, 101);
/// Wraps a ConfigOptionDef and adds function object for creating a side_widget.
struct Option {
ConfigOptionDef opt { ConfigOptionDef() };
t_config_option_key opt_id;//! {""};
widget_t side_widget {nullptr};
bool readonly {false};
Option(const ConfigOptionDef& _opt, t_config_option_key id) :
opt(_opt), opt_id(id) {}
};
using t_option = std::unique_ptr<Option>; //!
/// Represents option lines
class Line {
public:
wxString label {wxString("")};
wxString label_tooltip {wxString("")};
size_t full_width {0};
wxSizer* sizer {nullptr};
widget_t widget {nullptr};
std::function<wxWindow*(wxWindow*)> near_label_widget{ nullptr };
void append_option(const Option& option) {
m_options.push_back(option);
}
void append_widget(const widget_t widget) {
m_extra_widgets.push_back(widget);
}
Line(wxString label, wxString tooltip) :
label(label), label_tooltip(tooltip) {}
const std::vector<widget_t>& get_extra_widgets() const {return m_extra_widgets;}
const std::vector<Option>& get_options() const { return m_options; }
private:
std::vector<Option> m_options;//! {std::vector<Option>()};
std::vector<widget_t> m_extra_widgets;//! {std::vector<widget_t>()};
};
using column_t = std::function<wxWindow*(wxWindow* parent, const Line&)>;//std::function<wxSizer*(const Line&)>;
using t_optionfield_map = std::map<t_config_option_key, t_field>;
using t_opt_map = std::map< std::string, std::pair<std::string, int> >;
class OptionsGroup {
wxStaticBox* stb;
public:
const bool staticbox {true};
const wxString title {wxString("")};
size_t label_width {200};
wxSizer* sizer {nullptr};
column_t extra_column {nullptr};
t_change m_on_change {nullptr};
std::function<DynamicPrintConfig()> m_get_initial_config{ nullptr };
std::function<DynamicPrintConfig()> m_get_sys_config{ nullptr };
std::function<bool()> have_sys_config{ nullptr };
wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
int sidetext_width{ -1 };
/// Returns a copy of the pointer of the parent wxWindow.
/// Accessor function is because users are not allowed to change the parent
/// but defining it as const means a lot of const_casts to deal with wx functions.
inline wxWindow* parent() const {
#ifdef __WXGTK__
return m_panel;
#else
return m_parent;
#endif /* __WXGTK__ */
}
#ifdef __WXGTK__
wxWindow* get_parent() const {
return m_parent;
}
#endif /* __WXGTK__ */
void append_line(const Line& line, wxStaticText** colored_Label = nullptr);
Line create_single_option_line(const Option& option) const;
void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); }
// return a non-owning pointer reference
inline Field* get_field(const t_config_option_key& id) const{
if (m_fields.find(id) == m_fields.end()) return nullptr;
return m_fields.at(id).get();
}
bool set_value(const t_config_option_key& id, const boost::any& value, bool change_event = false) {
if (m_fields.find(id) == m_fields.end()) return false;
m_fields.at(id)->set_value(value, change_event);
return true;
}
boost::any get_value(const t_config_option_key& id) {
boost::any out;
if (m_fields.find(id) == m_fields.end()) ;
else
out = m_fields.at(id)->get_value();
return out;
}
bool set_side_text(const t_config_option_key& opt_key, const wxString& side_text) {
if (m_fields.find(opt_key) == m_fields.end()) return false;
auto st = m_fields.at(opt_key)->m_side_text;
if (!st) return false;
st->SetLabel(side_text);
return true;
}
void set_name(const wxString& new_name) {
stb->SetLabel(new_name);
}
inline void enable() { for (auto& field : m_fields) field.second->enable(); }
inline void disable() { for (auto& field : m_fields) field.second->disable(); }
void set_flag(ogDrawFlag flag) { m_flag = flag; }
void set_grid_vgap(int gap) { m_grid_sizer->SetVGap(gap); }
void set_show_modified_btns_val(bool show) {
m_show_modified_btns = show;
}
OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false,
ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) :
m_parent(_parent), title(title), m_show_modified_btns(is_tab_opt),
staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){
if (staticbox) {
stb = new wxStaticBox(_parent, wxID_ANY, title);
stb->SetFont(bold_font());
}
sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL));
auto num_columns = 1U;
if (label_width != 0) num_columns++;
if (extra_column != nullptr) num_columns++;
m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0);
static_cast<wxFlexGridSizer*>(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/);
static_cast<wxFlexGridSizer*>(m_grid_sizer)->AddGrowableCol(label_width != 0);
#ifdef __WXGTK__
m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
sizer->Fit(m_panel);
sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5);
#else
sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5);
#endif /* __WXGTK__ */
}
wxGridSizer* get_grid_sizer(){ return m_grid_sizer; }
protected:
std::map<t_config_option_key, Option> m_options;
wxWindow* m_parent {nullptr};
/// Field list, contains unique_ptrs of the derived type.
/// using types that need to know what it is beyond the public interface
/// need to cast based on the related ConfigOptionDef.
t_optionfield_map m_fields;
bool m_disabled {false};
wxGridSizer* m_grid_sizer {nullptr};
// "true" if option is created in preset tabs
bool m_show_modified_btns{ false };
ogDrawFlag m_flag{ ogDEFAULT };
// This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox
// Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel
// inside it before you insert the other controls.
#ifdef __WXGTK__
wxPanel* m_panel {nullptr};
#endif /* __WXGTK__ */
/// Generate a wxSizer or wxWindow from a configuration option
/// Precondition: opt resolves to a known ConfigOption
/// Postcondition: fields contains a wx gui object.
const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr);
const t_field& build_field(const t_config_option_key& id, wxStaticText* label = nullptr);
const t_field& build_field(const Option& opt, wxStaticText* label = nullptr);
void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field);
virtual void on_kill_focus (){};
virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
virtual void back_to_initial_value(const std::string& opt_key){}
virtual void back_to_sys_value(const std::string& opt_key){}
};
class ConfigOptionsGroup: public OptionsGroup {
public:
ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr,
bool is_tab_opt = false, ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) :
OptionsGroup(parent, title, is_tab_opt, flag, extra_clmn), m_config(_config) {}
/// reference to libslic3r config, non-owning pointer (?).
DynamicPrintConfig* m_config {nullptr};
bool m_full_labels {0};
t_opt_map m_opt_map;
Option get_option(const std::string& opt_key, int opt_index = -1);
Line create_single_option_line(const std::string& title, int idx = -1) /*const*/{
Option option = get_option(title, idx);
return OptionsGroup::create_single_option_line(option);
}
void append_single_option_line(const Option& option) {
OptionsGroup::append_single_option_line(option);
}
void append_single_option_line(const std::string title, int idx = -1)
{
Option option = get_option(title, idx);
append_single_option_line(option);
}
void on_change_OG(const t_config_option_key& opt_id, const boost::any& value) override;
void back_to_initial_value(const std::string& opt_key) override;
void back_to_sys_value(const std::string& opt_key) override;
void back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key);
void on_kill_focus() override{ reload_config();}
void reload_config();
boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize);
// return option value from config
boost::any get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1);
Field* get_fieldc(const t_config_option_key& opt_key, int opt_index);
};
// Static text shown among the options.
class ogStaticText :public wxStaticText{
public:
ogStaticText() {}
ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){}
~ogStaticText(){}
void SetText(const wxString& value, bool wrap = true);
};
}}
#endif /* slic3r_OptionsGroup_hpp_ */

View file

@ -0,0 +1,134 @@
#include "Preferences.hpp"
#include "AppConfig.hpp"
#include "OptionsGroup.hpp"
namespace Slic3r {
namespace GUI {
PreferencesDialog::PreferencesDialog(wxWindow* parent, int event_preferences) :
wxDialog(parent, wxID_ANY, _(L("Preferences")), wxDefaultPosition, wxDefaultSize),
m_event_preferences(event_preferences) {
build();
}
void PreferencesDialog::build()
{
auto app_config = get_app_config();
m_optgroup = std::make_shared<ConfigOptionsGroup>(this, _(L("General")));
m_optgroup->label_width = 400;
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
};
// TODO
// $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
// opt_id = > 'version_check',
// type = > 'bool',
// label = > 'Check for updates',
// tooltip = > 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
// default = > $app_config->get("version_check") // 1,
// readonly = > !wxTheApp->have_version_check,
// ));
ConfigOptionDef def;
def.label = L("Remember output directory");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will prompt the last output directory "
"instead of the one containing the input files.");
def.default_value = new ConfigOptionBool{ app_config->has("remember_output_path") ? app_config->get("remember_output_path")[0] == '1' : true }; // 1;
Option option(def, "remember_output_path");
m_optgroup->append_single_option_line(option);
def.label = L("Auto-center parts");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will auto-center objects "
"around the print bed center.");
def.default_value = new ConfigOptionBool{ app_config->get("autocenter")[0] == '1' }; // 1;
option = Option (def,"autocenter");
m_optgroup->append_single_option_line(option);
def.label = L("Background processing");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will pre-process objects as soon "
"as they\'re loaded in order to save time when exporting G-code.");
def.default_value = new ConfigOptionBool{ app_config->get("background_processing")[0] == '1' }; // 1;
option = Option (def,"background_processing");
m_optgroup->append_single_option_line(option);
// Please keep in sync with ConfigWizard
def.label = L("Check for application updates");
def.type = coBool;
def.tooltip = L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.");
def.default_value = new ConfigOptionBool(app_config->get("version_check") == "1");
option = Option (def, "version_check");
m_optgroup->append_single_option_line(option);
// Please keep in sync with ConfigWizard
def.label = L("Update built-in Presets automatically");
def.type = coBool;
def.tooltip = L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.");
def.default_value = new ConfigOptionBool(app_config->get("preset_update") == "1");
option = Option (def, "preset_update");
m_optgroup->append_single_option_line(option);
def.label = L("Suppress \" - default - \" presets");
def.type = coBool;
def.tooltip = L("Suppress \" - default - \" presets in the Print / Filament / Printer "
"selections once there are any other valid presets available.");
def.default_value = new ConfigOptionBool{ app_config->get("no_defaults")[0] == '1' }; // 1;
option = Option (def,"no_defaults");
m_optgroup->append_single_option_line(option);
def.label = L("Show incompatible print and filament presets");
def.type = coBool;
def.tooltip = L("When checked, the print and filament presets are shown in the preset editor "
"even if they are marked as incompatible with the active printer");
def.default_value = new ConfigOptionBool{ app_config->get("show_incompatible_presets")[0] == '1' }; // 1;
option = Option (def,"show_incompatible_presets");
m_optgroup->append_single_option_line(option);
def.label = L("Use legacy OpenGL 1.1 rendering");
def.type = coBool;
def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, "
"you may try to check this checkbox. This will disable the layer height "
"editing and anti aliasing, so it is likely better to upgrade your graphics driver.");
def.default_value = new ConfigOptionBool{ app_config->get("use_legacy_opengl")[0] == '1' }; // 1;
option = Option (def,"use_legacy_opengl");
m_optgroup->append_single_option_line(option);
auto sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(sizer);
sizer->SetSizeHints(this);
}
void PreferencesDialog::accept()
{
if (m_values.find("no_defaults") != m_values.end()||
m_values.find("use_legacy_opengl")!= m_values.end()) {
warning_catcher(this, _(L("You need to restart Slic3r to make the changes effective.")));
}
auto app_config = get_app_config();
for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it) {
app_config->set(it->first, it->second);
}
EndModal(wxID_OK);
Close(); // needed on Linux
// Nothify the UI to update itself from the ini file.
if (m_event_preferences > 0) {
wxCommandEvent event(m_event_preferences);
get_app()->ProcessEvent(event);
}
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,31 @@
#ifndef slic3r_Preferences_hpp_
#define slic3r_Preferences_hpp_
#include "GUI.hpp"
#include <wx/dialog.h>
#include <map>
namespace Slic3r {
namespace GUI {
class ConfigOptionsGroup;
class PreferencesDialog : public wxDialog
{
std::map<std::string, std::string> m_values;
std::shared_ptr<ConfigOptionsGroup> m_optgroup;
int m_event_preferences;
public:
PreferencesDialog(wxWindow* parent, int event_preferences);
~PreferencesDialog(){ }
void build();
void accept();
};
} // GUI
} // Slic3r
#endif /* slic3r_Preferences_hpp_ */

1019
src/slic3r/GUI/Preset.cpp Normal file

File diff suppressed because it is too large Load diff

444
src/slic3r/GUI/Preset.hpp Normal file
View file

@ -0,0 +1,444 @@
#ifndef slic3r_Preset_hpp_
#define slic3r_Preset_hpp_
#include <deque>
#include <boost/filesystem/path.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/Semver.hpp"
class wxBitmap;
class wxChoice;
class wxBitmapComboBox;
class wxItemContainer;
namespace Slic3r {
class AppConfig;
class PresetBundle;
namespace GUI {
class BitmapCache;
}
enum ConfigFileType
{
CONFIG_FILE_TYPE_UNKNOWN,
CONFIG_FILE_TYPE_APP_CONFIG,
CONFIG_FILE_TYPE_CONFIG,
CONFIG_FILE_TYPE_CONFIG_BUNDLE,
};
extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree);
class VendorProfile
{
public:
std::string name;
std::string id;
Semver config_version;
std::string config_update_url;
struct PrinterVariant {
PrinterVariant() {}
PrinterVariant(const std::string &name) : name(name) {}
std::string name;
};
struct PrinterModel {
PrinterModel() {}
std::string id;
std::string name;
PrinterTechnology technology;
std::vector<PrinterVariant> variants;
PrinterVariant* variant(const std::string &name) {
for (auto &v : this->variants)
if (v.name == name)
return &v;
return nullptr;
}
const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
};
std::vector<PrinterModel> models;
VendorProfile() {}
VendorProfile(std::string id) : id(std::move(id)) {}
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true);
size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; }
bool operator< (const VendorProfile &rhs) const { return this->id < rhs.id; }
bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; }
};
class Preset
{
public:
enum Type
{
TYPE_INVALID,
TYPE_PRINT,
TYPE_FILAMENT,
TYPE_SLA_MATERIAL,
TYPE_PRINTER,
};
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
Type type = TYPE_INVALID;
// The preset represents a "default" set of properties,
// pulled from the default values of the PrintConfig (see PrintConfigDef for their definitions).
bool is_default;
// External preset points to a configuration, which has been loaded but not imported
// into the Slic3r default configuration location.
bool is_external = false;
// System preset is read-only.
bool is_system = false;
// Preset is visible, if it is associated with a printer model / variant that is enabled in the AppConfig
// or if it has no printer model / variant association.
// Also the "default" preset is only visible, if it is the only preset in the list.
bool is_visible = true;
// Has this preset been modified?
bool is_dirty = false;
// Is this preset compatible with the currently active printer?
bool is_compatible = true;
// Name of the preset, usually derived form the file name.
std::string name;
// File name of the preset. This could be a Print / Filament / Printer preset,
// or a Configuration file bundling the Print + Filament + Printer presets (in that case is_external and possibly is_system will be true),
// or it could be a G-code (again, is_external will be true).
std::string file;
// If this is a system profile, then there should be a vendor data available to display at the UI.
const VendorProfile *vendor = nullptr;
// Has this profile been loaded?
bool loaded = false;
// Configuration data, loaded from a file, or set from the defaults.
DynamicPrintConfig config;
// Load this profile for the following keys only.
DynamicPrintConfig& load(const std::vector<std::string> &keys, const StaticPrintConfig &defaults);
void save();
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
std::string label() const;
// Set the is_dirty flag if the provided config is different from the active one.
void set_dirty(const DynamicPrintConfig &config) { this->is_dirty = ! this->config.diff(config).empty(); }
void set_dirty(bool dirty = true) { this->is_dirty = dirty; }
void reset_dirty() { this->is_dirty = false; }
bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const;
bool is_compatible_with_printer(const Preset &active_printer) const;
// Returns the name of the preset, from which this preset inherits.
static std::string& inherits(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("inherits", true)->value; }
std::string& inherits() { return Preset::inherits(this->config); }
const std::string& inherits() const { return Preset::inherits(const_cast<Preset*>(this)->config); }
// Returns the "compatible_printers_condition".
static std::string& compatible_printers_condition(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("compatible_printers_condition", true)->value; }
std::string& compatible_printers_condition() { return Preset::compatible_printers_condition(this->config); }
const std::string& compatible_printers_condition() const { return Preset::compatible_printers_condition(const_cast<Preset*>(this)->config); }
static PrinterTechnology& printer_technology(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value; }
PrinterTechnology& printer_technology() { return Preset::printer_technology(this->config); }
const PrinterTechnology& printer_technology() const { return Preset::printer_technology(const_cast<Preset*>(this)->config); }
// Mark this preset as compatible if it is compatible with active_printer.
bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config);
// Set is_visible according to application config
void set_visible_from_appconfig(const AppConfig &app_config);
// Resize the extruder specific fields, initialize them with the content of the 1st extruder.
void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
bool operator<(const Preset &other) const { return this->name < other.name; }
static const std::vector<std::string>& print_options();
static const std::vector<std::string>& filament_options();
// Printer options contain the nozzle options.
static const std::vector<std::string>& printer_options();
// Nozzle options of the printer options.
static const std::vector<std::string>& nozzle_options();
static const std::vector<std::string>& sla_printer_options();
static const std::vector<std::string>& sla_material_options();
static void update_suffix_modified();
protected:
friend class PresetCollection;
friend class PresetBundle;
static void normalize(DynamicPrintConfig &config);
// Resize the extruder specific vectors ()
static void set_num_extruders(DynamicPrintConfig &config, unsigned int n);
static const std::string& suffix_modified();
static std::string remove_suffix_modified(const std::string &name);
};
// Collections of presets of the same type (one of the Print, Filament or Printer type).
class PresetCollection
{
public:
// Initialize the PresetCollection with the "- default -" preset.
PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -");
~PresetCollection();
typedef std::deque<Preset>::iterator Iterator;
typedef std::deque<Preset>::const_iterator ConstIterator;
Iterator begin() { return m_presets.begin() + m_num_default_presets; }
ConstIterator begin() const { return m_presets.begin() + m_num_default_presets; }
Iterator end() { return m_presets.end(); }
ConstIterator end() const { return m_presets.end(); }
void reset(bool delete_files);
Preset::Type type() const { return m_type; }
std::string name() const;
const std::deque<Preset>& operator()() const { return m_presets; }
// Add default preset at the start of the collection, increment the m_default_preset counter.
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
// Load ini files of the particular type from the provided directory path.
void load_presets(const std::string &dir_path, const std::string &subdir);
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications.
Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true);
Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true);
Preset& load_external_preset(
// Path to the profile source file (a G-code, an AMF or 3MF file, a config file)
const std::string &path,
// Name of the profile, derived from the source file name.
const std::string &name,
// Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored.
const std::string &original_name,
// Config to initialize the preset from.
const DynamicPrintConfig &config,
// Select the preset after loading?
bool select = true);
// Save the preset under a new name. If the name is different from the old one,
// a new preset is stored into the list of presets.
// All presets are marked as not modified and the new preset is activated.
void save_current_preset(const std::string &new_name);
// Delete the current preset, activate the first visible preset.
void delete_current_preset();
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
bool load_bitmap_default(const std::string &file_name);
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; }
void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; }
void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; }
void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; }
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
bool is_default_suppressed() const { return m_default_suppressed; }
// Select a preset. If an invalid index is provided, the first visible preset is selected.
Preset& select_preset(size_t idx);
// Return the selected preset, without the user modifications applied.
Preset& get_selected_preset() { return m_presets[m_idx_selected]; }
const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; }
int get_selected_idx() const { return m_idx_selected; }
// Returns the name of the selected preset, or an empty string if no preset is selected.
std::string get_selected_preset_name() const { return (m_idx_selected == -1) ? std::string() : this->get_selected_preset().name; }
// For the current edited preset, return the parent preset if there is one.
// If there is no parent preset, nullptr is returned.
// The parent preset may be a system preset or a user preset, which will be
// reflected by the UI.
const Preset* get_selected_preset_parent() const;
// get parent preset for some child preset
const Preset* get_preset_parent(const Preset& child) const;
// Return the selected preset including the user modifications.
Preset& get_edited_preset() { return m_edited_preset; }
const Preset& get_edited_preset() const { return m_edited_preset; }
// used to update preset_choice from Tab
const std::deque<Preset>& get_presets() { return m_presets; }
int get_idx_selected() { return m_idx_selected; }
static const std::string& get_suffix_modified();
// Return a preset possibly with modifications.
Preset& default_preset() { return m_presets.front(); }
const Preset& default_preset() const { return m_presets.front(); }
// Return a preset by an index. If the preset is active, a temporary copy is returned.
Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; }
const Preset& preset(size_t idx) const { return const_cast<PresetCollection*>(this)->preset(idx); }
void discard_current_changes() { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; }
// Return a preset by its name. If the preset is active, a temporary copy is returned.
// If a preset is not found by its name, null is returned.
Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false);
const Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false) const
{ return const_cast<PresetCollection*>(this)->find_preset(name, first_visible_if_not_found); }
size_t first_visible_idx() const;
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
// If one of the prefered_alternates is compatible, select it.
template<typename PreferedCondition>
size_t first_compatible_idx(PreferedCondition prefered_condition) const
{
size_t i = m_default_suppressed ? m_num_default_presets : 0;
size_t n = this->m_presets.size();
size_t i_compatible = n;
for (; i < n; ++ i)
if (m_presets[i].is_compatible) {
if (prefered_condition(m_presets[i].name))
return i;
if (i_compatible == n)
// Store the first compatible profile into i_compatible.
i_compatible = i;
}
return (i_compatible == n) ? 0 : i_compatible;
}
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
size_t first_compatible_idx() const { return this->first_compatible_idx([](const std::string&){return true;}); }
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
// Return the first visible preset. Certainly at least the '- default -' preset shall be visible.
Preset& first_visible() { return this->preset(this->first_visible_idx()); }
const Preset& first_visible() const { return this->preset(this->first_visible_idx()); }
Preset& first_compatible() { return this->preset(this->first_compatible_idx()); }
template<typename PreferedCondition>
Preset& first_compatible(PreferedCondition prefered_condition) { return this->preset(this->first_compatible_idx(prefered_condition)); }
const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); }
// Return number of presets including the "- default -" preset.
size_t size() const { return m_presets.size(); }
bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; }
// For Print / Filament presets, disable those, which are not compatible with the printer.
template<typename PreferedCondition>
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible, PreferedCondition prefered_condition)
{
if (this->update_compatible_with_printer_internal(active_printer, select_other_if_incompatible) == (size_t)-1)
// Find some other compatible preset, or the "-- default --" preset.
this->select_preset(this->first_compatible_idx(prefered_condition));
}
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible)
{ this->update_compatible_with_printer(active_printer, select_other_if_incompatible, [](const std::string&){return true;}); }
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
bool current_is_dirty() const { return ! this->current_dirty_options().empty(); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> current_dirty_options(const bool deep_compare = false) const
{ return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> current_different_from_parent_options(const bool deep_compare = false) const
{ return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); }
// Update the choice UI from the list of presets.
// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
size_t update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible);
// Update the choice UI from the list of presets.
// Only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
void update_platter_ui(wxBitmapComboBox *ui);
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
// Return true if the dirty flag changed.
bool update_dirty_ui(wxBitmapComboBox *ui);
// Select a profile by its name. Return true if the selection changed.
// Without force, the selection is only updated if the index changes.
// With force, the changes are reverted if the new index is the same as the old index.
bool select_preset_by_name(const std::string &name, bool force);
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std::string path_from_name(const std::string &new_name) const;
protected:
// Select a preset, if it exists. If it does not exist, select an invalid (-1) index.
// This is a temporary state, which shall be fixed immediately by the following step.
bool select_preset_by_name_strict(const std::string &name);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors);
private:
PresetCollection();
PresetCollection(const PresetCollection &other);
PresetCollection& operator=(const PresetCollection &other);
// Find a preset position in the sorted list of presets.
// The "-- default -- " preset is always the first, so it needs
// to be handled differently.
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
std::deque<Preset>::iterator find_preset_internal(const std::string &name)
{
Preset key(m_type, name);
auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
if (it == m_presets.end() || it->name != name) {
// Preset has not been not found in the sorted list of non-default presets. Try the defaults.
for (size_t i = 0; i < m_num_default_presets; ++ i)
if (m_presets[i].name == name) {
it = m_presets.begin() + i;
break;
}
}
return it;
}
std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const
{ return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
size_t update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible);
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
Preset::Type m_type;
// List of presets, starting with the "- default -" preset.
// Use deque to force the container to allocate an object per each entry,
// so that the addresses of the presets don't change during resizing of the container.
std::deque<Preset> m_presets;
// Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user.
Preset m_edited_preset;
// Selected preset.
int m_idx_selected;
// Is the "- default -" preset suppressed?
bool m_default_suppressed = true;
size_t m_num_default_presets = 0;
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter.
// These bitmaps are not owned by PresetCollection, but by a PresetBundle.
const wxBitmap *m_bitmap_compatible = nullptr;
const wxBitmap *m_bitmap_incompatible = nullptr;
const wxBitmap *m_bitmap_lock = nullptr;
const wxBitmap *m_bitmap_lock_open = nullptr;
// Marks placed at the wxBitmapComboBox of a MainFrame.
// These bitmaps are owned by PresetCollection.
wxBitmap *m_bitmap_main_frame;
// Path to the directory to store the config files into.
std::string m_dir_path;
// Caching color bitmaps for the filament combo box.
GUI::BitmapCache *m_bitmap_cache = nullptr;
// to access select_preset_by_name_strict()
friend class PresetBundle;
};
} // namespace Slic3r
#endif /* slic3r_Preset_hpp_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,168 @@
#ifndef slic3r_PresetBundle_hpp_
#define slic3r_PresetBundle_hpp_
#include "AppConfig.hpp"
#include "Preset.hpp"
#include <set>
#include <boost/filesystem/path.hpp>
namespace Slic3r {
namespace GUI {
class BitmapCache;
};
class PlaceholderParser;
// Bundle of Print + Filament + Printer presets.
class PresetBundle
{
public:
PresetBundle();
~PresetBundle();
// Remove all the presets but the "-- default --".
// Optionally remove all the files referenced by the presets from the user profile directory.
void reset(bool delete_files);
void setup_directories();
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void load_presets(const AppConfig &config);
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
// Export selections (current print, current filaments, current printer) into a placeholder parser.
void export_selections(PlaceholderParser &pp);
PresetCollection prints;
PresetCollection filaments;
PresetCollection sla_materials;
PresetCollection printers;
// Filament preset names for a multi-extruder or multi-material print.
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
std::vector<std::string> filament_presets;
// The project configuration values are kept separated from the print/filament/printer preset,
// they are being serialized / deserialized from / to the .amf, .3mf, .config, .gcode,
// and they are being used by slicing core.
DynamicPrintConfig project_config;
// There will be an entry for each system profile loaded,
// and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors.
std::set<VendorProfile> vendors;
struct ObsoletePresets {
std::vector<std::string> prints;
std::vector<std::string> filaments;
std::vector<std::string> sla_materials;
std::vector<std::string> printers;
};
ObsoletePresets obsolete_presets;
bool has_defauls_only() const
{ return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); }
DynamicPrintConfig full_config() const;
// Load user configuration and store it into the user profiles.
// This method is called by the configuration wizard.
void load_config(const std::string &name, DynamicPrintConfig config)
{ this->load_config_file_config(name, false, std::move(config)); }
// Load an external config file containing the print, filament and printer presets.
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
// In the future the configuration will likely be read from an AMF file as well.
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
void load_config_file(const std::string &path);
// Load an external config source containing the print, filament and printer presets.
// The given string must contain the full set of parameters (same as those exported to gcode).
// If the string is parsed successfully, its print / filament / printer profiles will be activated.
void load_config_string(const char* str, const char* source_filename = nullptr);
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
// Load settings into the provided settings instance.
// Activate the presets stored in the config bundle.
// Returns the number of presets loaded successfully.
enum {
// Save the profiles, which have been loaded.
LOAD_CFGBNDLE_SAVE = 1,
// Delete all old config profiles before loading.
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2,
// Load a system config bundle.
LOAD_CFGBNDLE_SYSTEM = 4,
LOAD_CFGBUNDLE_VENDOR_ONLY = 8,
};
// Load the config bundle, store it to the user profile directory by default.
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings);
// Update a filament selection combo box on the platter for an idx_extruder.
void update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui);
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
// Set the filament preset name. As the name could come from the UI selection box,
// an optional "(modified)" suffix will be removed from the filament name.
void set_filament_preset(size_t idx, const std::string &name);
// Read out the number of extruders from an active printer preset,
// update size and content of filament_presets.
void update_multi_material_filament_presets();
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
// as compatible with the currently selected printer.
// Also updates the is_visible flag of each preset.
// If select_other_if_incompatible is true, then the print or filament preset is switched to some compatible
// preset if the current print or filament preset is not compatible.
void update_compatible_with_printer(bool select_other_if_incompatible);
static bool parse_color(const std::string &scolor, unsigned char *rgb_out);
private:
std::string load_system_presets();
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Set the "enabled" flag for printer vendors, printer models and printer variants
// based on the user configuration.
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void load_installed_printers(const AppConfig &config);
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void load_selections(const AppConfig &config);
// Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path.
// and the external config is just referenced, not stored into user profile directory.
// If it is not an external config, then the config will be stored into the user profile directory.
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
bool load_compatible_bitmaps();
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;
// Indicator, that the preset is compatible with the selected printer.
wxBitmap *m_bitmapCompatible;
// Indicator, that the preset is NOT compatible with the selected printer.
wxBitmap *m_bitmapIncompatible;
// Indicator, that the preset is system and not modified.
wxBitmap *m_bitmapLock;
// Indicator, that the preset is system and user modified.
wxBitmap *m_bitmapLockOpen;
// Caching color bitmaps for the filament combo box.
GUI::BitmapCache *m_bitmapCache;
};
} // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */

View file

@ -0,0 +1,278 @@
//#undef NDEBUG
#include <cassert>
#include "PresetBundle.hpp"
#include "PresetHints.hpp"
#include "Flow.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <wx/intl.h>
#include "../../libslic3r/libslic3r.h"
#include "GUI.hpp"
namespace Slic3r {
#define MIN_BUF_LENGTH 4096
std::string PresetHints::cooling_description(const Preset &preset)
{
std::string out;
char buf[MIN_BUF_LENGTH/*4096*/];
if (preset.config.opt_bool("cooling", 0)) {
int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0);
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
int max_fan_speed = preset.config.opt_int("max_fan_speed", 0);
int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5);
int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0);
sprintf(buf, _CHB(L("If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).")),
slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed);
out += buf;
if (fan_below_layer_time > slowdown_below_layer_time) {
sprintf(buf, _CHB(L("\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.")),
fan_below_layer_time, max_fan_speed, min_fan_speed);
out += buf;
}
out += _CHB(L("\nDuring the other layers, fan "));
} else {
out = _CHB(L("Fan "));
}
if (preset.config.opt_bool("fan_always_on", 0)) {
int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0);
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
sprintf(buf, _CHB(L("will always run at %d%% ")), min_fan_speed);
out += buf;
if (disable_fan_first_layers > 1) {
sprintf(buf, _CHB(L("except for the first %d layers")), disable_fan_first_layers);
out += buf;
}
else if (disable_fan_first_layers == 1)
out += _CHB(L("except for the first layer"));
} else
out += _CHB(L("will be turned off."));
return out;
}
static const ConfigOptionFloatOrPercent& first_positive(const ConfigOptionFloatOrPercent *v1, const ConfigOptionFloatOrPercent &v2, const ConfigOptionFloatOrPercent &v3)
{
return (v1 != nullptr && v1->value > 0) ? *v1 : ((v2.value > 0) ? v2 : v3);
}
std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle &preset_bundle)
{
// Find out, to which nozzle index is the current filament profile assigned.
int idx_extruder = 0;
int num_extruders = (int)preset_bundle.filament_presets.size();
for (; idx_extruder < num_extruders; ++ idx_extruder)
if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset().name)
break;
if (idx_extruder == num_extruders)
// The current filament preset is not active for any extruder.
idx_extruder = -1;
const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config;
const DynamicPrintConfig &filament_config = preset_bundle.filaments.get_edited_preset().config;
const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config;
// Current printer values.
float nozzle_diameter = (float)printer_config.opt_float("nozzle_diameter", idx_extruder);
// Print config values
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
double support_material_speed = print_config.opt_float("support_material_speed");
double support_material_interface_speed = print_config.get_abs_value("support_material_interface_speed", support_material_speed);
double bridge_speed = print_config.opt_float("bridge_speed");
double bridge_flow_ratio = print_config.opt_float("bridge_flow_ratio");
double perimeter_speed = print_config.opt_float("perimeter_speed");
double external_perimeter_speed = print_config.get_abs_value("external_perimeter_speed", perimeter_speed);
double gap_fill_speed = print_config.opt_float("gap_fill_speed");
double infill_speed = print_config.opt_float("infill_speed");
double small_perimeter_speed = print_config.get_abs_value("small_perimeter_speed", perimeter_speed);
double solid_infill_speed = print_config.get_abs_value("solid_infill_speed", infill_speed);
double top_solid_infill_speed = print_config.get_abs_value("top_solid_infill_speed", solid_infill_speed);
// Maximum print speed when auto-speed is enabled by setting any of the above speed values to zero.
double max_print_speed = print_config.opt_float("max_print_speed");
// Maximum volumetric speed allowed for the print profile.
double max_volumetric_speed = print_config.opt_float("max_volumetric_speed");
const auto &extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("extrusion_width");
const auto &external_perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width");
const auto &first_layer_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
const auto &infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("infill_extrusion_width");
const auto &perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
const auto &solid_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("solid_infill_extrusion_width");
const auto &support_material_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("support_material_extrusion_width");
const auto &top_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("top_infill_extrusion_width");
const auto &first_layer_speed = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_speed");
// Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print.
// If different from idx_extruder, it will not be taken into account for this hint.
auto feature_extruder_active = [idx_extruder, num_extruders](int i) {
return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1;
};
bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("perimeter_extruder"));
bool infill_extruder_active = feature_extruder_active(print_config.opt_int("infill_extruder"));
bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_extruder"));
bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_material_extruder"));
bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_material_interface_extruder"));
// Current filament values
double filament_diameter = filament_config.opt_float("filament_diameter", 0);
double filament_crossection = M_PI * 0.25 * filament_diameter * filament_diameter;
double extrusion_multiplier = filament_config.opt_float("extrusion_multiplier", 0);
// The following value will be annotated by this hint, so it does not take part in the calculation.
// double filament_max_volumetric_speed = filament_config.opt_float("filament_max_volumetric_speed", 0);
std::string out;
for (size_t idx_type = (first_layer_extrusion_width.value == 0) ? 1 : 0; idx_type < 3; ++ idx_type) {
// First test the maximum volumetric extrusion speed for non-bridging extrusions.
bool first_layer = idx_type == 0;
bool bridging = idx_type == 2;
const ConfigOptionFloatOrPercent *first_layer_extrusion_width_ptr = (first_layer && first_layer_extrusion_width.value > 0) ?
&first_layer_extrusion_width : nullptr;
const float lh = float(first_layer ? first_layer_height : layer_height);
const float bfr = bridging ? bridge_flow_ratio : 0.f;
double max_flow = 0.;
std::string max_flow_extrusion_type;
auto limit_by_first_layer_speed = [&first_layer_speed, first_layer](double speed_normal, double speed_max) {
if (first_layer && first_layer_speed.value > 0)
// Apply the first layer limit.
speed_normal = first_layer_speed.get_abs_value(speed_normal);
return (speed_normal > 0.) ? speed_normal : speed_max;
};
if (perimeter_extruder_active) {
double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter,
first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed :
limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < external_perimeter_rate) {
max_flow = external_perimeter_rate;
max_flow_extrusion_type = _CHB(L("external perimeters"));
}
double perimeter_rate = Flow::new_from_config_width(frPerimeter,
first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed :
limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < perimeter_rate) {
max_flow = perimeter_rate;
max_flow_extrusion_type = _CHB(L("perimeters"));
}
}
if (! bridging && infill_extruder_active) {
double infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed);
if (max_flow < infill_rate) {
max_flow = infill_rate;
max_flow_extrusion_type = _CHB(L("infill"));
}
}
if (solid_infill_extruder_active) {
double solid_infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, 0).mm3_per_mm() *
(bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed));
if (max_flow < solid_infill_rate) {
max_flow = solid_infill_rate;
max_flow_extrusion_type = _CHB(L("solid infill"));
}
if (! bridging) {
double top_solid_infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
if (max_flow < top_solid_infill_rate) {
max_flow = top_solid_infill_rate;
max_flow_extrusion_type = _CHB(L("top solid infill"));
}
}
}
if (support_material_extruder_active) {
double support_material_rate = Flow::new_from_config_width(frSupportMaterial,
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed));
if (max_flow < support_material_rate) {
max_flow = support_material_rate;
max_flow_extrusion_type = _CHB(L("support"));
}
}
if (support_material_interface_extruder_active) {
double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface,
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed));
if (max_flow < support_material_interface_rate) {
max_flow = support_material_interface_rate;
max_flow_extrusion_type = _CHB(L("support interface"));
}
}
//FIXME handle gap_fill_speed
if (! out.empty())
out += "\n";
out += (first_layer ? _CHB(L("First layer volumetric")) : (bridging ? _CHB(L("Bridging volumetric")) : _CHB(L("Volumetric"))));
out += _CHB(L(" flow rate is maximized "));
bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow;
out += (limited_by_max_volumetric_speed ?
_CHB(L("by the print profile maximum")) :
(_CHB(L("when printing ")) + max_flow_extrusion_type))
+ _CHB(L(" with a volumetric rate "));
if (limited_by_max_volumetric_speed)
max_flow = max_volumetric_speed;
char buf[MIN_BUF_LENGTH/*2048*/];
sprintf(buf, _CHB(L("%3.2f mm³/s")), max_flow);
out += buf;
sprintf(buf, _CHB(L(" at filament speed %3.2f mm/s.")), max_flow / filament_crossection);
out += buf;
}
return out;
}
std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &preset_bundle)
{
const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config;
const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config;
float layer_height = float(print_config.opt_float("layer_height"));
int num_perimeters = print_config.opt_int("perimeters");
bool thin_walls = print_config.opt_bool("thin_walls");
float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0));
std::string out;
if (layer_height <= 0.f){
out += _CHB(L("Recommended object thin wall thickness: Not available due to invalid layer height."));
return out;
}
Flow external_perimeter_flow = Flow::new_from_config_width(
frExternalPerimeter,
*print_config.opt<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width"),
nozzle_diameter, layer_height, false);
Flow perimeter_flow = Flow::new_from_config_width(
frPerimeter,
*print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"),
nozzle_diameter, layer_height, false);
if (num_perimeters > 0) {
int num_lines = std::min(num_perimeters * 2, 10);
char buf[MIN_BUF_LENGTH/*256*/];
sprintf(buf, _CHB(L("Recommended object thin wall thickness for layer height %.2f and ")), layer_height);
out += buf;
// Start with the width of two closely spaced
double width = external_perimeter_flow.width + external_perimeter_flow.spacing();
for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) {
if (i > 2)
out += ", ";
sprintf(buf, _CHB(L("%d lines: %.2lf mm")), i, width);
out += buf;
width += perimeter_flow.spacing() * (thin_walls ? 1.f : 2.f);
}
}
return out;
}
}; // namespace Slic3r

View file

@ -0,0 +1,30 @@
#ifndef slic3r_PresetHints_hpp_
#define slic3r_PresetHints_hpp_
#include <string>
#include "PresetBundle.hpp"
namespace Slic3r {
// GUI utility functions to produce hint messages from the current profile.
class PresetHints
{
public:
// Produce a textual description of the cooling logic of a currently active filament.
static std::string cooling_description(const Preset &preset);
// Produce a textual description of the maximum flow achived for the current configuration
// (the current printer, filament and print settigns).
// This description will be useful for getting a gut feeling for the maximum volumetric
// print speed achievable with the extruder.
static std::string maximum_volumetric_flow_description(const PresetBundle &preset_bundle);
// Produce a textual description of a recommended thin wall thickness
// from the provided number of perimeters and the external / internal perimeter width.
static std::string recommended_thin_wall_thickness(const PresetBundle &preset_bundle);
};
} // namespace Slic3r
#endif /* slic3r_PresetHints_hpp_ */

View file

@ -0,0 +1,70 @@
#ifndef IPROGRESSINDICATOR_HPP
#define IPROGRESSINDICATOR_HPP
#include <string>
#include <functional>
namespace Slic3r {
/**
* @brief Generic progress indication interface.
*/
class ProgressIndicator {
public:
using CancelFn = std::function<void(void)>; // Cancel function signature.
private:
float state_ = .0f, max_ = 1.f, step_;
CancelFn cancelfunc_ = [](){};
public:
inline virtual ~ProgressIndicator() {}
/// 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 the 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 instead 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 operation.
virtual void title(const std::string&) = 0;
/// Formatted message for the next status update. Works just like sprintf.
virtual void message_fmt(const std::string& fmt, ...);
/// Set up a cancel callback for the operation if feasible.
virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; }
/**
* Explicitly shut down the progress indicator and call the associated
* callback.
*/
virtual void cancel() { cancelfunc_(); }
/// Convenience 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

View file

@ -0,0 +1,152 @@
#include "ProgressStatusBar.hpp"
#include <wx/timer.h>
#include <wx/gauge.h>
#include <wx/button.h>
#include <wx/statusbr.h>
#include <wx/frame.h>
#include "GUI.hpp"
#include <iostream>
namespace Slic3r {
ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id):
self(new wxStatusBar(parent ? parent : GUI::get_main_frame(),
id == -1? wxID_ANY : id)),
timer_(new wxTimer(self)),
prog_ (new wxGauge(self,
wxGA_HORIZONTAL,
100,
wxDefaultPosition,
wxDefaultSize)),
cancelbutton_(new wxButton(self,
-1,
"Cancel",
wxDefaultPosition,
wxDefaultSize))
{
prog_->Hide();
cancelbutton_->Hide();
self->SetFieldsCount(3);
int w[] = {-1, 150, 155};
self->SetStatusWidths(3, w);
self->Bind(wxEVT_TIMER, [this](const wxTimerEvent&) {
if (prog_->IsShown()) timer_->Stop();
if(is_busy()) prog_->Pulse();
});
self->Bind(wxEVT_SIZE, [this](wxSizeEvent& event){
wxRect rect;
self->GetFieldRect(1, rect);
auto offset = 0;
cancelbutton_->Move(rect.GetX() + offset, rect.GetY() + offset);
cancelbutton_->SetSize(rect.GetWidth() - offset, rect.GetHeight());
self->GetFieldRect(2, rect);
prog_->Move(rect.GetX() + offset, rect.GetY() + offset);
prog_->SetSize(rect.GetWidth() - offset, rect.GetHeight());
event.Skip();
});
cancelbutton_->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) {
if(cancel_cb_) cancel_cb_();
m_perl_cancel_callback.call();
cancelbutton_->Hide();
});
}
ProgressStatusBar::~ProgressStatusBar() {
if(timer_->IsRunning()) timer_->Stop();
}
int ProgressStatusBar::get_progress() const
{
return prog_->GetValue();
}
void ProgressStatusBar::set_progress(int val)
{
if(!prog_->IsShown()) show_progress(true);
if(val == prog_->GetRange()) {
prog_->SetValue(0);
show_progress(false);
} else {
prog_->SetValue(val);
}
}
int ProgressStatusBar::get_range() const
{
return prog_->GetRange();
}
void ProgressStatusBar::set_range(int val)
{
if(val != prog_->GetRange()) {
prog_->SetRange(val);
}
}
void ProgressStatusBar::show_progress(bool show)
{
prog_->Show(show);
prog_->Pulse();
}
void ProgressStatusBar::start_busy(int rate)
{
busy_ = true;
show_progress(true);
if (!timer_->IsRunning()) {
timer_->Start(rate);
}
}
void ProgressStatusBar::stop_busy()
{
timer_->Stop();
show_progress(false);
prog_->SetValue(0);
busy_ = false;
}
void ProgressStatusBar::set_cancel_callback(ProgressStatusBar::CancelFn ccb) {
cancel_cb_ = ccb;
if(ccb) cancelbutton_->Show();
else cancelbutton_->Hide();
}
void ProgressStatusBar::run(int rate)
{
if(!timer_->IsRunning()) {
timer_->Start(rate);
}
}
void ProgressStatusBar::embed(wxFrame *frame)
{
wxFrame* mf = frame? frame : GUI::get_main_frame();
mf->SetStatusBar(self);
}
void ProgressStatusBar::set_status_text(const wxString& txt)
{
self->SetStatusText(wxString::FromUTF8(txt.c_str()));
}
void ProgressStatusBar::show_cancel_button()
{
cancelbutton_->Show();
}
void ProgressStatusBar::hide_cancel_button()
{
cancelbutton_->Hide();
}
}

View file

@ -0,0 +1,68 @@
#ifndef PROGRESSSTATUSBAR_HPP
#define PROGRESSSTATUSBAR_HPP
#include <memory>
#include <functional>
#include "callback.hpp"
class wxTimer;
class wxGauge;
class wxButton;
class wxTimerEvent;
class wxStatusBar;
class wxWindow;
class wxFrame;
class wxString;
namespace Slic3r {
/**
* @brief The ProgressStatusBar class is the widgets occupying the lower area
* of the Slicer main window. It consists of a message area to the left and a
* progress indication area to the right with an optional cancel button.
*/
class ProgressStatusBar {
wxStatusBar *self; // we cheat! It should be the base class but: perl!
wxTimer *timer_;
wxGauge *prog_;
wxButton *cancelbutton_;
public:
/// Cancel callback function type
using CancelFn = std::function<void()>;
ProgressStatusBar(wxWindow *parent = nullptr, int id = -1);
~ProgressStatusBar();
int get_progress() const;
void set_progress(int);
int get_range() const;
void set_range(int = 100);
void show_progress(bool);
void start_busy(int = 100);
void stop_busy();
inline bool is_busy() const { return busy_; }
void set_cancel_callback(CancelFn = CancelFn());
inline void remove_cancel_callback() { set_cancel_callback(); }
void run(int rate);
void embed(wxFrame *frame = nullptr);
void set_status_text(const wxString& txt);
// Temporary methods to satisfy Perl side
void show_cancel_button();
void hide_cancel_button();
PerlCallback m_perl_cancel_callback;
private:
bool busy_ = false;
CancelFn cancel_cb_;
};
namespace GUI {
using Slic3r::ProgressStatusBar;
}
}
#endif // PROGRESSSTATUSBAR_HPP

View file

@ -0,0 +1,279 @@
#include <algorithm>
#include <wx/dcbuffer.h>
#include "RammingChart.hpp"
#include "GUI.hpp"
wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent);
void Chart::draw() {
wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win
dc.SetBrush(GetBackgroundColour());
dc.SetPen(GetBackgroundColour());
dc.DrawRectangle(GetClientRect()); // otherwise the background would end up black on windows
dc.SetPen(*wxBLACK_PEN);
dc.SetBrush(*wxWHITE_BRUSH);
dc.DrawRectangle(m_rect);
if (visible_area.m_width < 0.499) {
dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,m_rect.GetBottom()-m_rect.GetHeight()/2));
return;
}
if (!m_line_to_draw.empty()) {
for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
int color = 510*((m_rect.GetBottom()-(m_line_to_draw)[i])/double(m_rect.GetHeight()));
dc.SetPen( wxPen( wxColor(std::min(255,color),255-std::max(color-255,0),0), 1 ) );
dc.DrawLine(m_rect.GetLeft()+1+i,(m_line_to_draw)[i],m_rect.GetLeft()+1+i,m_rect.GetBottom());
}
dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
if (splines)
dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
else {
dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i]);
dc.DrawLine(m_rect.GetLeft()+i+1,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
}
}
}
// draw draggable buttons
dc.SetBrush(*wxBLUE_BRUSH);
dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
for (auto& button : m_buttons)
//dc.DrawRectangle(math_to_screen(button.get_pos())-wxPoint(side/2.,side/2.), wxSize(side,side));
dc.DrawCircle(math_to_screen(button.get_pos()),side/2.);
//dc.DrawRectangle(math_to_screen(button.get_pos()-wxPoint2DDouble(0.125,0))-wxPoint(0,5),wxSize(50,10));
// draw x-axis:
float last_mark = -10000;
for (float math_x=int(visible_area.m_x*10)/10 ; math_x < (visible_area.m_x+visible_area.m_width) ; math_x+=0.1) {
int x = math_to_screen(wxPoint2DDouble(math_x,visible_area.m_y)).x;
int y = m_rect.GetBottom();
if (x-last_mark < 50) continue;
dc.DrawLine(x,y+3,x,y-3);
dc.DrawText(wxString().Format(wxT("%.1f"), math_x),wxPoint(x-10,y+7));
last_mark = x;
}
// draw y-axis:
last_mark=10000;
for (int math_y=visible_area.m_y ; math_y < (visible_area.m_y+visible_area.m_height) ; math_y+=1) {
int y = math_to_screen(wxPoint2DDouble(visible_area.m_x,math_y)).y;
int x = m_rect.GetLeft();
if (last_mark-y < 50) continue;
dc.DrawLine(x-3,y,x+3,y);
dc.DrawText(wxString()<<math_y,wxPoint(x-25,y-2/*7*/));
last_mark = y;
}
// axis labels:
wxString label = _(L("Time")) + " ("+_(L("s"))+")";
int text_width = 0;
int text_height = 0;
dc.GetTextExtent(label,&text_width,&text_height);
dc.DrawText(label,wxPoint(0.5*(m_rect.GetRight()+m_rect.GetLeft())-text_width/2.f, m_rect.GetBottom()+25));
label = _(L("Volumetric speed")) + " (" + _(L("mm")) + wxString("³/", wxConvUTF8) + _(L("s")) + ")";
dc.GetTextExtent(label,&text_width,&text_height);
dc.DrawRotatedText(label,wxPoint(0,0.5*(m_rect.GetBottom()+m_rect.GetTop())+text_width/2.f),90);
}
void Chart::mouse_right_button_clicked(wxMouseEvent& event) {
if (!manual_points_manipulation)
return;
wxPoint point = event.GetPosition();
int button_index = which_button_is_clicked(point);
if (button_index != -1 && m_buttons.size()>2) {
m_buttons.erase(m_buttons.begin()+button_index);
recalculate_line();
}
}
void Chart::mouse_clicked(wxMouseEvent& event) {
wxPoint point = event.GetPosition();
int button_index = which_button_is_clicked(point);
if ( button_index != -1) {
m_dragged = &m_buttons[button_index];
m_previous_mouse = point;
}
}
void Chart::mouse_moved(wxMouseEvent& event) {
if (!event.Dragging() || !m_dragged) return;
wxPoint pos = event.GetPosition();
wxRect rect = m_rect;
rect.Deflate(side/2.);
if (!(rect.Contains(pos))) { // the mouse left chart area
mouse_left_window(event);
return;
}
int delta_x = pos.x - m_previous_mouse.x;
int delta_y = pos.y - m_previous_mouse.y;
m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height);
m_previous_mouse = pos;
recalculate_line();
}
void Chart::mouse_double_clicked(wxMouseEvent& event) {
if (!manual_points_manipulation)
return;
wxPoint point = event.GetPosition();
if (!m_rect.Contains(point)) // the click is outside the chart
return;
m_buttons.push_back(screen_to_math(point));
std::sort(m_buttons.begin(),m_buttons.end());
recalculate_line();
return;
}
void Chart::recalculate_line() {
std::vector<wxPoint> points;
for (auto& but : m_buttons) {
points.push_back(wxPoint(math_to_screen(but.get_pos())));
if (points.size()>1 && points.back().x==points[points.size()-2].x) points.pop_back();
if (points.size()>1 && points.back().x > m_rect.GetRight()) {
points.pop_back();
break;
}
}
std::sort(points.begin(),points.end(),[](wxPoint& a,wxPoint& b) { return a.x < b.x; });
m_line_to_draw.clear();
m_total_volume = 0.f;
// Cubic spline interpolation: see https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation#Methods
const bool boundary_first_derivative = true; // true - first derivative is 0 at the leftmost and rightmost point
// false - second ---- || -------
const int N = points.size()-1; // last point can be accessed as N, we have N+1 total points
std::vector<float> diag(N+1);
std::vector<float> mu(N+1);
std::vector<float> lambda(N+1);
std::vector<float> h(N+1);
std::vector<float> rhs(N+1);
// let's fill in inner equations
for (int i=1;i<=N;++i) h[i] = points[i].x-points[i-1].x;
std::fill(diag.begin(),diag.end(),2.f);
for (int i=1;i<=N-1;++i) {
mu[i] = h[i]/(h[i]+h[i+1]);
lambda[i] = 1.f - mu[i];
rhs[i] = 6 * ( float(points[i+1].y-points[i].y )/(h[i+1]*(points[i+1].x-points[i-1].x)) -
float(points[i].y -points[i-1].y)/(h[i] *(points[i+1].x-points[i-1].x)) );
}
// now fill in the first and last equations, according to boundary conditions:
if (boundary_first_derivative) {
const float endpoints_derivative = 0;
lambda[0] = 1;
mu[N] = 1;
rhs[0] = (6.f/h[1]) * (float(points[0].y-points[1].y)/(points[0].x-points[1].x) - endpoints_derivative);
rhs[N] = (6.f/h[N]) * (endpoints_derivative - float(points[N-1].y-points[N].y)/(points[N-1].x-points[N].x));
}
else {
lambda[0] = 0;
mu[N] = 0;
rhs[0] = 0;
rhs[N] = 0;
}
// the trilinear system is ready to be solved:
for (int i=1;i<=N;++i) {
float multiple = mu[i]/diag[i-1]; // let's subtract proper multiple of above equation
diag[i]-= multiple * lambda[i-1];
rhs[i] -= multiple * rhs[i-1];
}
// now the back substitution (vector mu contains invalid values from now on):
rhs[N] = rhs[N]/diag[N];
for (int i=N-1;i>=0;--i)
rhs[i] = (rhs[i]-lambda[i]*rhs[i+1])/diag[i];
unsigned int i=1;
float y=0.f;
for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) {
if (splines) {
if (i<points.size()-1 && points[i].x < x ) {
++i;
}
if (points[0].x > x)
y = points[0].y;
else
if (points[N].x < x)
y = points[N].y;
else
y = (rhs[i-1]*pow(points[i].x-x,3)+rhs[i]*pow(x-points[i-1].x,3)) / (6*h[i]) +
(points[i-1].y-rhs[i-1]*h[i]*h[i]/6.f) * (points[i].x-x)/h[i] +
(points[i].y -rhs[i] *h[i]*h[i]/6.f) * (x-points[i-1].x)/h[i];
m_line_to_draw.push_back(y);
}
else {
float x_math = screen_to_math(wxPoint(x,0)).m_x;
if (i+2<=points.size() && m_buttons[i+1].get_pos().m_x-0.125 < x_math)
++i;
m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y);
}
m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1);
m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1);
m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight());
}
wxPostEvent(this->GetParent(), wxCommandEvent(EVT_WIPE_TOWER_CHART_CHANGED));
Refresh();
}
std::vector<float> Chart::get_ramming_speed(float sampling) const {
std::vector<float> speeds_out;
const int number_of_samples = std::round( visible_area.m_width / sampling);
if (number_of_samples>0) {
const int dx = (m_line_to_draw.size()-1) / number_of_samples;
for (int j=0;j<number_of_samples;++j) {
float left = screen_to_math(wxPoint(0,m_line_to_draw[j*dx])).m_y;
float right = screen_to_math(wxPoint(0,m_line_to_draw[(j+1)*dx])).m_y;
speeds_out.push_back((left+right)/2.f);
}
}
return speeds_out;
}
std::vector<std::pair<float,float>> Chart::get_buttons() const {
std::vector<std::pair<float, float>> buttons_out;
for (const auto& button : m_buttons)
buttons_out.push_back(std::make_pair(float(button.get_pos().m_x),float(button.get_pos().m_y)));
return buttons_out;
}
BEGIN_EVENT_TABLE(Chart, wxWindow)
EVT_MOTION(Chart::mouse_moved)
EVT_LEFT_DOWN(Chart::mouse_clicked)
EVT_LEFT_UP(Chart::mouse_released)
EVT_LEFT_DCLICK(Chart::mouse_double_clicked)
EVT_RIGHT_DOWN(Chart::mouse_right_button_clicked)
EVT_LEAVE_WINDOW(Chart::mouse_left_window)
EVT_PAINT(Chart::paint_event)
END_EVENT_TABLE()

View file

@ -0,0 +1,115 @@
#ifndef RAMMING_CHART_H_
#define RAMMING_CHART_H_
#include <vector>
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
wxDECLARE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent);
class Chart : public wxWindow {
public:
Chart(wxWindow* parent, wxRect rect,const std::vector<std::pair<float,float>>& initial_buttons,int ramming_speed_size, float sampling) :
wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize())
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
m_rect = wxRect(wxPoint(50,0),rect.GetSize()-wxSize(50,50));
visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.);
m_buttons.clear();
if (initial_buttons.size()>0)
for (const auto& pair : initial_buttons)
m_buttons.push_back(wxPoint2DDouble(pair.first,pair.second));
recalculate_line();
}
void set_xy_range(float x,float y) {
x = int(x/0.5) * 0.5;
if (x>=0) visible_area.SetRight(x);
if (y>=0) visible_area.SetBottom(y);
recalculate_line();
}
float get_volume() const { return m_total_volume; }
float get_time() const { return visible_area.m_width; }
std::vector<float> get_ramming_speed(float sampling) const; //returns sampled ramming speed
std::vector<std::pair<float,float>> get_buttons() const; // returns buttons position
void draw();
void mouse_clicked(wxMouseEvent& event);
void mouse_right_button_clicked(wxMouseEvent& event);
void mouse_moved(wxMouseEvent& event);
void mouse_double_clicked(wxMouseEvent& event);
void mouse_left_window(wxMouseEvent&) { m_dragged = nullptr; }
void mouse_released(wxMouseEvent&) { m_dragged = nullptr; }
void paint_event(wxPaintEvent&) { draw(); }
DECLARE_EVENT_TABLE()
private:
static const bool fixed_x = true;
static const bool splines = true;
static const bool manual_points_manipulation = false;
static const int side = 10; // side of draggable button
class ButtonToDrag {
public:
bool operator<(const ButtonToDrag& a) const { return m_pos.m_x < a.m_pos.m_x; }
ButtonToDrag(wxPoint2DDouble pos) : m_pos{pos} {};
wxPoint2DDouble get_pos() const { return m_pos; }
void move(double x,double y) { m_pos.m_x+=x; m_pos.m_y+=y; }
private:
wxPoint2DDouble m_pos; // position in math coordinates
};
wxPoint math_to_screen(const wxPoint2DDouble& math) const {
wxPoint screen;
screen.x = (math.m_x-visible_area.m_x) * (m_rect.GetWidth() / visible_area.m_width );
screen.y = (math.m_y-visible_area.m_y) * (m_rect.GetHeight() / visible_area.m_height );
screen.y *= -1;
screen += m_rect.GetLeftBottom();
return screen;
}
wxPoint2DDouble screen_to_math(const wxPoint& screen) const {
wxPoint2DDouble math = screen;
math -= m_rect.GetLeftBottom();
math.m_y *= -1;
math.m_x *= visible_area.m_width / m_rect.GetWidth(); // scales to [0;1]x[0,1]
math.m_y *= visible_area.m_height / m_rect.GetHeight();
return (math+visible_area.GetLeftTop());
}
int which_button_is_clicked(const wxPoint& point) const {
if (!m_rect.Contains(point))
return -1;
for (unsigned int i=0;i<m_buttons.size();++i) {
wxRect rect(math_to_screen(m_buttons[i].get_pos())-wxPoint(side/2.,side/2.),wxSize(side,side)); // bounding rectangle of this button
if ( rect.Contains(point) )
return i;
}
return (-1);
}
void recalculate_line();
void recalculate_volume();
wxRect m_rect; // rectangle on screen the chart is mapped into (screen coordinates)
wxPoint m_previous_mouse;
std::vector<ButtonToDrag> m_buttons;
std::vector<int> m_line_to_draw;
wxRect2DDouble visible_area;
ButtonToDrag* m_dragged = nullptr;
float m_total_volume = 0.f;
};
#endif // RAMMING_CHART_H_

3033
src/slic3r/GUI/Tab.cpp Normal file

File diff suppressed because it is too large Load diff

385
src/slic3r/GUI/Tab.hpp Normal file
View file

@ -0,0 +1,385 @@
#ifndef slic3r_Tab_hpp_
#define slic3r_Tab_hpp_
// The "Expert" tab at the right of the main tabbed window.
//
// This file implements following packages:
// Slic3r::GUI::Tab;
// Slic3r::GUI::Tab::Print;
// Slic3r::GUI::Tab::Filament;
// Slic3r::GUI::Tab::Printer;
// Slic3r::GUI::Tab::Page
// - Option page: For example, the Slic3r::GUI::Tab::Print has option pages "Layers and perimeters", "Infill", "Skirt and brim" ...
// Slic3r::GUI::SavePresetWindow
// - Dialog to select a new preset name to store the configuration.
// Slic3r::GUI::Tab::Preset;
// - Single preset item: name, file is default or external.
#include <wx/panel.h>
#include <wx/notebook.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <wx/bmpcbox.h>
#include <wx/bmpbuttn.h>
#include <wx/treectrl.h>
#include <wx/imaglist.h>
#include <wx/statbox.h>
#include <wx/dataview.h>
#include <map>
#include <vector>
#include <memory>
#include "BedShapeDialog.hpp"
//!enum { ID_TAB_TREE = wxID_HIGHEST + 1 };
namespace Slic3r {
namespace GUI {
typedef std::pair<wxBitmap*, std::string> t_icon_description;
typedef std::vector<std::pair<wxBitmap*, std::string>> t_icon_descriptions;
// Single Tab page containing a{ vsizer } of{ optgroups }
// package Slic3r::GUI::Tab::Page;
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
class Page : public wxScrolledWindow
{
wxWindow* m_parent;
wxString m_title;
size_t m_iconID;
wxBoxSizer* m_vsizer;
public:
Page(wxWindow* parent, const wxString title, const int iconID) :
m_parent(parent),
m_title(title),
m_iconID(iconID)
{
Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_vsizer = new wxBoxSizer(wxVERTICAL);
m_item_color = &get_label_clr_default();
SetSizer(m_vsizer);
}
~Page(){}
bool m_is_modified_values{ false };
bool m_is_nonsys_values{ true };
public:
std::vector <ConfigOptionsGroupShp> m_optgroups;
DynamicPrintConfig* m_config;
wxBoxSizer* vsizer() const { return m_vsizer; }
wxWindow* parent() const { return m_parent; }
wxString title() const { return m_title; }
size_t iconID() const { return m_iconID; }
void set_config(DynamicPrintConfig* config_in) { m_config = config_in; }
void reload_config();
Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const;
bool set_value(const t_config_option_key& opt_key, const boost::any& value);
ConfigOptionsGroupShp new_optgroup(const wxString& title, int noncommon_label_width = -1);
bool set_item_colour(const wxColour *clr) {
if (m_item_color != clr) {
m_item_color = clr;
return true;
}
return false;
}
const wxColour get_item_colour() {
return *m_item_color;
}
protected:
// Color of TreeCtrlItem. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_item_color;
};
// Slic3r::GUI::Tab;
using PageShp = std::shared_ptr<Page>;
class Tab: public wxPanel
{
wxNotebook* m_parent;
#ifdef __WXOSX__
wxPanel* m_tmp_panel;
int m_size_move = -1;
#endif // __WXOSX__
protected:
std::string m_name;
const wxString m_title;
wxBitmapComboBox* m_presets_choice;
wxBitmapButton* m_btn_save_preset;
wxBitmapButton* m_btn_delete_preset;
wxBitmapButton* m_btn_hide_incompatible_presets;
wxBoxSizer* m_hsizer;
wxBoxSizer* m_left_sizer;
wxTreeCtrl* m_treectrl;
wxImageList* m_icons;
wxCheckBox* m_compatible_printers_checkbox;
wxButton* m_compatible_printers_btn;
wxButton* m_undo_btn;
wxButton* m_undo_to_sys_btn;
wxButton* m_question_btn;
wxComboCtrl* m_cc_presets_choice;
wxDataViewTreeCtrl* m_presetctrl;
wxImageList* m_preset_icons;
// Cached bitmaps.
// A "flag" icon to be displayned next to the preset name in the Tab's combo box.
wxBitmap m_bmp_show_incompatible_presets;
wxBitmap m_bmp_hide_incompatible_presets;
// Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field.
wxBitmap m_bmp_value_lock;
wxBitmap m_bmp_value_unlock;
wxBitmap m_bmp_white_bullet;
// The following bitmap points to either m_bmp_value_unlock or m_bmp_white_bullet, depending on whether the current preset has a parent preset.
wxBitmap *m_bmp_non_system;
// Bitmaps to be shown on the "Undo user changes" button next to each input field.
wxBitmap m_bmp_value_revert;
// wxBitmap m_bmp_value_unmodified;
wxBitmap m_bmp_question;
// Colors for ui "decoration"
wxColour m_sys_label_clr;
wxColour m_modified_label_clr;
wxColour m_default_text_clr;
// Tooltip text for reset buttons (for whole options group)
wxString m_ttg_value_lock;
wxString m_ttg_value_unlock;
wxString m_ttg_white_bullet_ns;
// The following text points to either m_ttg_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset.
wxString *m_ttg_non_system;
// Tooltip text to be shown on the "Undo user changes" button next to each input field.
wxString m_ttg_white_bullet;
wxString m_ttg_value_revert;
// Tooltip text for reset buttons (for each option in group)
wxString m_tt_value_lock;
wxString m_tt_value_unlock;
// The following text points to either m_tt_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset.
wxString *m_tt_non_system;
// Tooltip text to be shown on the "Undo user changes" button next to each input field.
wxString m_tt_white_bullet;
wxString m_tt_value_revert;
int m_icon_count;
std::map<std::string, size_t> m_icon_index; // Map from an icon file name to its index
std::vector<PageShp> m_pages;
bool m_disable_tree_sel_changed_event;
bool m_show_incompatible_presets;
std::vector<std::string> m_reload_dependent_tabs = {};
enum OptStatus { osSystemValue = 1, osInitValue = 2 };
std::map<std::string, int> m_options_list;
int m_opt_status_value = 0;
t_icon_descriptions m_icon_descriptions = {};
// The two following two event IDs are generated at Plater.pm by calling Wx::NewEventType.
wxEventType m_event_value_change = 0;
wxEventType m_event_presets_changed = 0;
bool m_is_modified_values{ false };
bool m_is_nonsys_values{ true };
bool m_postpone_update_ui {false};
size_t m_selected_preset_item{ 0 };
public:
PresetBundle* m_preset_bundle;
bool m_show_btn_incompatible_presets = false;
PresetCollection* m_presets;
DynamicPrintConfig* m_config;
ogStaticText* m_parent_preset_description_line;
wxStaticText* m_colored_Label = nullptr;
public:
Tab() {}
Tab(wxNotebook* parent, const wxString& title, const char* name) :
m_parent(parent), m_title(title), m_name(name) {
Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name);
get_tabs_list().push_back(this);
}
~Tab(){
delete_tab_from_list(this);
}
wxWindow* parent() const { return m_parent; }
wxString title() const { return m_title; }
std::string name() const { return m_name; }
// Set the events to the callbacks posted to the main frame window (currently implemented in Perl).
void set_event_value_change(wxEventType evt) { m_event_value_change = evt; }
void set_event_presets_changed(wxEventType evt) { m_event_presets_changed = evt; }
void create_preset_tab(PresetBundle *preset_bundle);
void load_current_preset();
void rebuild_page_tree(bool tree_sel_change_event = false);
void select_preset(std::string preset_name = "");
bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = "");
wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn);
void update_presetsctrl(wxDataViewTreeCtrl* ui, bool show_incompatible);
void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false);
void reload_compatible_printers_widget();
void OnTreeSelChange(wxTreeEvent& event);
void OnKeyDown(wxKeyEvent& event);
void save_preset(std::string name = "");
void delete_preset();
void toggle_show_hide_incompatible();
void update_show_hide_incompatible_button();
void update_ui_from_settings();
void update_labels_colour();
void update_changed_ui();
void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page);
void update_changed_tree_ui();
void update_undo_buttons();
void on_roll_back_value(const bool to_sys = false);
PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false);
virtual void OnActivate();
virtual void on_preset_loaded(){}
virtual void build() = 0;
virtual void update() = 0;
virtual void init_options_list();
void load_initial_data();
void update_dirty();
void update_tab_ui();
void load_config(const DynamicPrintConfig& config);
virtual void reload_config();
Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const;
bool set_value(const t_config_option_key& opt_key, const boost::any& value);
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText);
bool current_preset_is_dirty();
DynamicPrintConfig* get_config() { return m_config; }
PresetCollection* get_presets() { return m_presets; }
std::vector<std::string> get_dependent_tabs() { return m_reload_dependent_tabs; }
size_t get_selected_preset_item() { return m_selected_preset_item; }
void on_value_change(const std::string& opt_key, const boost::any& value);
void update_wiping_button_visibility();
protected:
void on_presets_changed();
void update_preset_description_line();
void update_frequently_changed_parameters();
void update_tab_presets(wxComboCtrl* ui, bool show_incompatible);
void fill_icon_descriptions();
void set_tooltips_text();
};
//Slic3r::GUI::Tab::Print;
class TabPrint : public Tab
{
public:
TabPrint() {}
TabPrint(wxNotebook* parent) :
Tab(parent, _(L("Print Settings")), "print") {}
~TabPrint(){}
ogStaticText* m_recommended_thin_wall_thickness_description_line;
bool m_support_material_overhangs_queried = false;
void build() override;
void reload_config() override;
void update() override;
void OnActivate() override;
};
//Slic3r::GUI::Tab::Filament;
class TabFilament : public Tab
{
ogStaticText* m_volumetric_speed_description_line;
ogStaticText* m_cooling_description_line;
public:
TabFilament() {}
TabFilament(wxNotebook* parent) :
Tab(parent, _(L("Filament Settings")), "filament") {}
~TabFilament(){}
void build() override;
void reload_config() override;
void update() override;
void OnActivate() override;
};
//Slic3r::GUI::Tab::Printer;
class TabPrinter : public Tab
{
bool m_has_single_extruder_MM_page = false;
bool m_use_silent_mode = false;
void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key);
bool m_rebuild_kinematics_page = false;
std::vector<PageShp> m_pages_fff;
std::vector<PageShp> m_pages_sla;
public:
wxButton* m_serial_test_btn;
wxButton* m_print_host_test_btn;
wxButton* m_printhost_browse_btn;
size_t m_extruders_count;
size_t m_extruders_count_old = 0;
size_t m_initial_extruders_count;
size_t m_sys_extruders_count;
PrinterTechnology m_printer_technology = ptFFF;
TabPrinter() {}
TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Printer Settings")), "printer") {}
~TabPrinter(){}
void build() override;
void build_fff();
void build_sla();
void update() override;
void update_fff();
void update_sla();
void update_pages(); // update m_pages according to printer technology
void update_serial_ports();
void extruders_count_changed(size_t extruders_count);
PageShp build_kinematics_page();
void build_extruder_pages();
void on_preset_loaded() override;
void init_options_list() override;
};
class TabSLAMaterial : public Tab
{
public:
TabSLAMaterial() {}
TabSLAMaterial(wxNotebook* parent) :
Tab(parent, _(L("SLA Material Settings")), "sla_material") {}
~TabSLAMaterial(){}
void build() override;
void update() override;
void init_options_list() override;
};
class SavePresetWindow :public wxDialog
{
public:
SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _(L("Save preset"))){}
~SavePresetWindow(){}
std::string m_chosen_name;
wxComboBox* m_combo;
void build(const wxString& title, const std::string& default_name, std::vector<std::string> &values);
void accept();
std::string get_name() { return m_chosen_name; }
};
} // GUI
} // Slic3r
#endif /* slic3r_Tab_hpp_ */

View file

@ -0,0 +1,20 @@
#include "TabIface.hpp"
#include "Tab.hpp"
namespace Slic3r {
void TabIface::load_current_preset() { m_tab->load_current_preset(); }
void TabIface::update_tab_ui() { m_tab->update_tab_ui(); }
void TabIface::update_ui_from_settings() { m_tab->update_ui_from_settings();}
void TabIface::select_preset(char* name) { m_tab->select_preset(name);}
void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*config);}
void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast<std::string>(value)); }
bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();}
void TabIface::OnActivate() { return m_tab->OnActivate();}
size_t TabIface::get_selected_preset_item() { return m_tab->get_selected_preset_item(); }
std::string TabIface::title() { return m_tab->title().ToUTF8().data(); }
DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); }
PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; }
std::vector<std::string> TabIface::get_dependent_tabs() { return m_tab->get_dependent_tabs(); }
}; // namespace Slic3r

View file

@ -0,0 +1,41 @@
#ifndef slic3r_TabIface_hpp_
#define slic3r_TabIface_hpp_
#include <vector>
#include <string>
namespace Slic3r {
class DynamicPrintConfig;
class PresetCollection;
namespace GUI {
class Tab;
}
class TabIface {
public:
TabIface() : m_tab(nullptr) {}
TabIface(GUI::Tab *tab) : m_tab(tab) {}
// TabIface(const TabIface &rhs) : m_tab(rhs.m_tab) {}
void load_current_preset();
void update_tab_ui();
void update_ui_from_settings();
void select_preset(char* name);
std::string title();
void load_config(DynamicPrintConfig* config);
void load_key_value(char* opt_key, char* value);
bool current_preset_is_dirty();
void OnActivate();
DynamicPrintConfig* get_config();
PresetCollection* get_presets();
std::vector<std::string> get_dependent_tabs();
size_t get_selected_preset_item();
protected:
GUI::Tab *m_tab;
}; // namespace GUI
}; // namespace Slic3r
#endif /* slic3r_TabIface_hpp_ */

View file

@ -0,0 +1,196 @@
#include "UpdateDialogs.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/event.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/hyperlink.h>
#include <wx/statbmp.h>
#include <wx/checkbox.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "ConfigWizard.hpp"
namespace Slic3r {
namespace GUI {
static const std::string CONFIG_UPDATE_WIKI_URL("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update");
// MsgUpdateSlic3r
MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online) :
MsgDialog(nullptr, _(L("Update available")), _(L("New version of Slic3r PE is available"))),
ver_current(ver_current),
ver_online(ver_online)
{
const auto url = wxString::Format("https://github.com/prusa3d/Slic3r/releases/tag/version_%s", ver_online.to_string());
auto *link = new wxHyperlinkCtrl(this, wxID_ANY, url, url);
auto *text = new wxStaticText(this, wxID_ANY, _(L("To download, follow the link below.")));
const auto link_width = link->GetSize().GetWidth();
text->Wrap(CONTENT_WIDTH > link_width ? CONTENT_WIDTH : link_width);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string()));
versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string()));
content_sizer->Add(versions);
content_sizer->AddSpacer(VERT_SPACING);
content_sizer->Add(link);
content_sizer->AddSpacer(2*VERT_SPACING);
cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more")));
content_sizer->Add(cbox);
content_sizer->AddSpacer(VERT_SPACING);
Fit();
}
MsgUpdateSlic3r::~MsgUpdateSlic3r() {}
bool MsgUpdateSlic3r::disable_version_check() const
{
return cbox->GetValue();
}
// MsgUpdateConfig
MsgUpdateConfig::MsgUpdateConfig(const std::unordered_map<std::string, std::string> &updates) :
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"Would you like to install it?\n\n"
"Note that a full configuration snapshot will be created first. It can then be restored at any time "
"should there be a problem with the new version.\n\n"
"Updated configuration bundles:"
)));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
for (const auto &update : updates) {
auto *text_vendor = new wxStaticText(this, wxID_ANY, update.first);
text_vendor->SetFont(boldfont);
versions->Add(text_vendor);
versions->Add(new wxStaticText(this, wxID_ANY, update.second));
}
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto *btn_cancel = new wxButton(this, wxID_CANCEL);
btn_sizer->Add(btn_cancel);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto *btn_ok = new wxButton(this, wxID_OK);
btn_sizer->Add(btn_ok);
btn_ok->SetFocus();
Fit();
}
MsgUpdateConfig::~MsgUpdateConfig() {}
// MsgDataIncompatible
MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats) :
MsgDialog(nullptr, _(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), wxID_NONE)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"This version of Slic3r PE is not compatible with currently installed configuration bundles.\n"
"This probably happened as a result of running an older Slic3r PE after using a newer one.\n\n"
"You may either exit Slic3r and try again with a newer version, or you may re-run the initial configuration. "
"Doing so will create a backup snapshot of the existing configuration before installing files compatible with this Slic3r.\n"
)));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
auto *text2 = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("This Slic3r PE version: %s")), SLIC3R_VERSION));
text2->Wrap(CONTENT_WIDTH);
content_sizer->Add(text2);
content_sizer->AddSpacer(VERT_SPACING);
auto *text3 = new wxStaticText(this, wxID_ANY, _(L("Incompatible bundles:")));
text3->Wrap(CONTENT_WIDTH);
content_sizer->Add(text3);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
for (const auto &incompat : incompats) {
auto *text_vendor = new wxStaticText(this, wxID_ANY, incompat.first);
text_vendor->SetFont(boldfont);
versions->Add(text_vendor);
versions->Add(new wxStaticText(this, wxID_ANY, incompat.second));
}
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto *btn_exit = new wxButton(this, wxID_EXIT, _(L("Exit Slic3r")));
btn_sizer->Add(btn_exit);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto *btn_reconf = new wxButton(this, wxID_REPLACE, _(L("Re-configure")));
btn_sizer->Add(btn_reconf);
btn_exit->SetFocus();
auto exiter = [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); };
btn_exit->Bind(wxEVT_BUTTON, exiter);
btn_reconf->Bind(wxEVT_BUTTON, exiter);
Fit();
}
MsgDataIncompatible::~MsgDataIncompatible() {}
// MsgDataLegacy
MsgDataLegacy::MsgDataLegacy() :
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update")))
{
auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(
_(L(
"Slic3r PE now uses an updated configuration structure.\n\n"
"So called 'System presets' have been introduced, which hold the built-in default settings for various "
"printers. These System presets cannot be modified, instead, users now may create their "
"own presets inheriting settings from one of the System presets.\n"
"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n"
"Please proceed with the %s that follows to set up the new presets "
"and to choose whether to enable automatic preset updates."
)),
ConfigWizard::name()
));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *text2 = new wxStaticText(this, wxID_ANY, _(L("For more information please visit our wiki page:")));
static const wxString url("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update");
// The wiki page name is intentionally not localized:
auto *link = new wxHyperlinkCtrl(this, wxID_ANY, "Slic3r PE 1.40 configuration update", CONFIG_UPDATE_WIKI_URL);
content_sizer->Add(text2);
content_sizer->Add(link);
content_sizer->AddSpacer(VERT_SPACING);
Fit();
}
MsgDataLegacy::~MsgDataLegacy() {}
}
}

View file

@ -0,0 +1,81 @@
#ifndef slic3r_UpdateDialogs_hpp_
#define slic3r_UpdateDialogs_hpp_
#include <string>
#include <unordered_map>
#include "slic3r/Utils/Semver.hpp"
#include "MsgDialog.hpp"
class wxBoxSizer;
class wxCheckBox;
namespace Slic3r {
namespace GUI {
// A confirmation dialog listing configuration updates
class MsgUpdateSlic3r : public MsgDialog
{
public:
MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online);
MsgUpdateSlic3r(MsgUpdateSlic3r &&) = delete;
MsgUpdateSlic3r(const MsgUpdateSlic3r &) = delete;
MsgUpdateSlic3r &operator=(MsgUpdateSlic3r &&) = delete;
MsgUpdateSlic3r &operator=(const MsgUpdateSlic3r &) = delete;
virtual ~MsgUpdateSlic3r();
// Tells whether the user checked the "don't bother me again" checkbox
bool disable_version_check() const;
private:
const Semver &ver_current;
const Semver &ver_online;
wxCheckBox *cbox;
};
// Confirmation dialog informing about configuration update. Lists updated bundles & their versions.
class MsgUpdateConfig : public MsgDialog
{
public:
// updates is a map of "vendor name" -> "version (comment)"
MsgUpdateConfig(const std::unordered_map<std::string, std::string> &updates);
MsgUpdateConfig(MsgUpdateConfig &&) = delete;
MsgUpdateConfig(const MsgUpdateConfig &) = delete;
MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete;
MsgUpdateConfig &operator=(const MsgUpdateConfig &) = delete;
~MsgUpdateConfig();
};
// Informs about currently installed bundles not being compatible with the running Slic3r. Asks about action.
class MsgDataIncompatible : public MsgDialog
{
public:
// incompats is a map of "vendor name" -> "version restrictions"
MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats);
MsgDataIncompatible(MsgDataIncompatible &&) = delete;
MsgDataIncompatible(const MsgDataIncompatible &) = delete;
MsgDataIncompatible &operator=(MsgDataIncompatible &&) = delete;
MsgDataIncompatible &operator=(const MsgDataIncompatible &) = delete;
~MsgDataIncompatible();
};
// Informs about a legacy data directory - an update from Slic3r PE < 1.40
class MsgDataLegacy : public MsgDialog
{
public:
MsgDataLegacy();
MsgDataLegacy(MsgDataLegacy &&) = delete;
MsgDataLegacy(const MsgDataLegacy &) = delete;
MsgDataLegacy &operator=(MsgDataLegacy &&) = delete;
MsgDataLegacy &operator=(const MsgDataLegacy &) = delete;
~MsgDataLegacy();
};
}
}
#endif

16
src/slic3r/GUI/Widget.hpp Normal file
View file

@ -0,0 +1,16 @@
#ifndef WIDGET_HPP
#define WIDGET_HPP
#include <wx/wxprec.h>
#ifndef WX_PRECOM
#include <wx/wx.h>
#endif
class Widget {
protected:
wxSizer* _sizer;
public:
Widget(): _sizer(nullptr) { }
bool valid() const { return _sizer != nullptr; }
wxSizer* sizer() const { return _sizer; }
};
#endif

View file

@ -0,0 +1,338 @@
#include <algorithm>
#include <sstream>
#include "WipeTowerDialog.hpp"
#include "GUI.hpp"
#include <wx/sizer.h>
RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters)
: wxDialog(parent, wxID_ANY, _(L("Ramming customization")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
{
m_panel_ramming = new RammingPanel(this,parameters);
// Not found another way of getting the background colours of RammingDialog, RammingPanel and Chart correct than setting
// them all explicitely. Reading the parent colour yielded colour that didn't really match it, no wxSYS_COLOUR_... matched
// colour used for the dialog. Same issue (and "solution") here : https://forums.wxwidgets.org/viewtopic.php?f=1&t=39608
// Whoever can fix this, feel free to do so.
this-> SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
m_panel_ramming->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
m_panel_ramming->Show(true);
this->Show();
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_panel_ramming, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, 10);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); });
this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) {
m_output_data = m_panel_ramming->get_parameters();
EndModal(wxID_OK);
},wxID_OK);
this->Show();
wxMessageDialog(this,_(L("Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to "
"properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself "
"be reinserted later. This phase is important and different materials can require different extrusion speeds to get "
"the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level "
"setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc.")),_(L("Warning")),wxOK|wxICON_EXCLAMATION).ShowModal();
}
RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/)
{
auto sizer_chart = new wxBoxSizer(wxVERTICAL);
auto sizer_param = new wxBoxSizer(wxVERTICAL);
std::stringstream stream{ parameters };
stream >> m_ramming_line_width_multiplicator >> m_ramming_step_multiplicator;
int ramming_speed_size = 0;
float dummy = 0.f;
while (stream >> dummy)
++ramming_speed_size;
stream.clear();
stream.get();
std::vector<std::pair<float, float>> buttons;
float x = 0.f;
float y = 0.f;
while (stream >> x >> y)
buttons.push_back(std::make_pair(x, y));
m_chart = new Chart(this, wxRect(10, 10, 480, 360), buttons, ramming_speed_size, 0.25f);
m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in RammingDialog constructor
sizer_chart->Add(m_chart, 0, wxALL, 5);
m_widget_time = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0.,5.0,3.,0.5);
m_widget_volume = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0,10000,0);
m_widget_ramming_line_width_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100);
m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100);
auto gsizer_param = new wxFlexGridSizer(2, 5, 15);
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time")) + " (" + _(L("s")) + "):")), 0, wxALIGN_CENTER_VERTICAL);
gsizer_param->Add(m_widget_time);
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total rammed volume")) + " (" + _(L("mm")) + wxString("³):", wxConvUTF8))), 0, wxALIGN_CENTER_VERTICAL);
gsizer_param->Add(m_widget_volume);
gsizer_param->AddSpacer(20);
gsizer_param->AddSpacer(20);
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line width")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL);
gsizer_param->Add(m_widget_ramming_line_width_multiplicator);
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL);
gsizer_param->Add(m_widget_ramming_step_multiplicator);
sizer_param->Add(gsizer_param, 0, wxTOP, 100);
m_widget_time->SetValue(m_chart->get_time());
m_widget_time->SetDigits(2);
m_widget_volume->SetValue(m_chart->get_volume());
m_widget_volume->Disable();
m_widget_ramming_line_width_multiplicator->SetValue(m_ramming_line_width_multiplicator);
m_widget_ramming_step_multiplicator->SetValue(m_ramming_step_multiplicator);
m_widget_ramming_step_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); });
m_widget_ramming_line_width_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); });
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(sizer_chart, 0, wxALL, 5);
sizer->Add(sizer_param, 0, wxALL, 10);
sizer->SetSizeHints(this);
SetSizer(sizer);
m_widget_time->Bind(wxEVT_TEXT,[this](wxCommandEvent&) {m_chart->set_xy_range(m_widget_time->GetValue(),-1);});
m_widget_time->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value
m_widget_volume->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value
Bind(EVT_WIPE_TOWER_CHART_CHANGED,[this](wxCommandEvent&) {m_widget_volume->SetValue(m_chart->get_volume()); m_widget_time->SetValue(m_chart->get_time());} );
Refresh(this);
}
void RammingPanel::line_parameters_changed() {
m_ramming_line_width_multiplicator = m_widget_ramming_line_width_multiplicator->GetValue();
m_ramming_step_multiplicator = m_widget_ramming_step_multiplicator->GetValue();
}
std::string RammingPanel::get_parameters()
{
std::vector<float> speeds = m_chart->get_ramming_speed(0.25f);
std::vector<std::pair<float,float>> buttons = m_chart->get_buttons();
std::stringstream stream;
stream << m_ramming_line_width_multiplicator << " " << m_ramming_step_multiplicator;
for (const float& speed_value : speeds)
stream << " " << speed_value;
stream << "|";
for (const auto& button : buttons)
stream << " " << button.first << " " << button.second;
return stream.str();
}
#define ITEM_WIDTH 60
// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode:
WipingDialog::WipingDialog(wxWindow* parent,const std::vector<float>& matrix, const std::vector<float>& extruders)
: wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
{
auto widget_button = new wxButton(this,wxID_ANY,"-",wxPoint(0,0),wxDefaultSize);
m_panel_wiping = new WipingPanel(this,matrix,extruders, widget_button);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
// set min sizer width according to extruders count
const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH);
main_sizer->SetMinSize(wxSize(sizer_width, -1));
main_sizer->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5);
main_sizer->Add(widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5);
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); });
this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { // if OK button is clicked..
m_output_matrix = m_panel_wiping->read_matrix_values(); // ..query wiping panel and save returned values
m_output_extruders = m_panel_wiping->read_extruders_values(); // so they can be recovered later by calling get_...()
EndModal(wxID_OK);
},wxID_OK);
this->Show();
}
// This function allows to "play" with sizers parameters (like align or border)
void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift/*=0*/)
{
sizer->Add(new wxStaticText(page, wxID_ANY, info,wxDefaultPosition,wxSize(0,50)), 0, wxEXPAND | wxLEFT, 15);
auto table_sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift);
table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50);
table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP, 10);
}
// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers)
WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, wxButton* widget_button)
: wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/)
{
m_widget_button = widget_button; // pointer to the button in parent dialog
m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){ toggle_advanced(true); });
m_number_of_extruders = (int)(sqrt(matrix.size())+0.001);
// Create two switched panels with their own sizers
m_sizer_simple = new wxBoxSizer(wxVERTICAL);
m_sizer_advanced = new wxBoxSizer(wxVERTICAL);
m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_page_advanced = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_page_simple->SetSizer(m_sizer_simple);
m_page_advanced->SetSizer(m_sizer_advanced);
auto gridsizer_simple = new wxGridSizer(3, 5, 10);
m_gridsizer_advanced = new wxGridSizer(m_number_of_extruders+1, 5, 1);
// First create controls for advanced mode and assign them to m_page_advanced:
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
edit_boxes.push_back(std::vector<wxTextCtrl*>(0));
for (unsigned int j = 0; j < m_number_of_extruders; ++j) {
edit_boxes.back().push_back(new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH, -1)));
if (i == j)
edit_boxes[i][j]->Disable();
else
edit_boxes[i][j]->SetValue(wxString("") << int(matrix[m_number_of_extruders*j + i]));
}
}
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("")));
for (unsigned int i = 0; i < m_number_of_extruders; ++i)
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
for (unsigned int j = 0; j < m_number_of_extruders; ++j)
m_gridsizer_advanced->Add(edit_boxes[j][i], 0);
}
// collect and format sizer
format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced,
_(L("Here you can adjust required purging volume (mm³) for any given pair of tools.")),
_(L("Extruder changed to")));
// Hide preview page before new page creating
// It allows to do that from a beginning of the main panel
m_page_advanced->Hide();
// Now the same for simple mode:
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString("")), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("unloaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
for (unsigned int i=0;i<m_number_of_extruders;++i) {
m_old.push_back(new wxSpinCtrl(m_page_simple,wxID_ANY,wxEmptyString,wxDefaultPosition, wxSize(80, -1),wxSP_ARROW_KEYS|wxALIGN_RIGHT,0,300,extruders[2*i]));
m_new.push_back(new wxSpinCtrl(m_page_simple,wxID_ANY,wxEmptyString,wxDefaultPosition, wxSize(80, -1),wxSP_ARROW_KEYS|wxALIGN_RIGHT,0,300,extruders[2*i+1]));
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Tool #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(m_old.back(),0);
gridsizer_simple->Add(m_new.back(),0);
}
// collect and format sizer
format_sizer(m_sizer_simple, m_page_simple, gridsizer_simple,
_(L("Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded.")),
_(L("Volume to purge (mm³) when the filament is being")), 50);
m_sizer = new wxBoxSizer(wxVERTICAL);
m_sizer->Add(m_page_simple, 0, wxEXPAND | wxALL, 25);
m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 25);
m_sizer->SetSizeHints(this);
SetSizer(m_sizer);
toggle_advanced(); // to show/hide what is appropriate
m_page_advanced->Bind(wxEVT_PAINT,[this](wxPaintEvent&) {
wxPaintDC dc(m_page_advanced);
int y_pos = 0.5 * (edit_boxes[0][0]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetSize().y);
wxString label = _(L("From"));
int text_width = 0;
int text_height = 0;
dc.GetTextExtent(label,&text_width,&text_height);
int xpos = m_gridsizer_advanced->GetPosition().x;
dc.DrawRotatedText(label,xpos-text_height,y_pos + text_width/2.f,90);
});
}
// Reads values from the (advanced) wiping matrix:
std::vector<float> WipingPanel::read_matrix_values() {
if (!m_advanced)
fill_in_matrix();
std::vector<float> output;
for (unsigned int i=0;i<m_number_of_extruders;++i) {
for (unsigned int j=0;j<m_number_of_extruders;++j) {
double val = 0.;
edit_boxes[j][i]->GetValue().ToDouble(&val);
output.push_back((float)val);
}
}
return output;
}
// Reads values from simple mode to save them for next time:
std::vector<float> WipingPanel::read_extruders_values() {
std::vector<float> output;
for (unsigned int i=0;i<m_number_of_extruders;++i) {
output.push_back(m_old[i]->GetValue());
output.push_back(m_new[i]->GetValue());
}
return output;
}
// This updates the "advanced" matrix based on values from "simple" mode
void WipingPanel::fill_in_matrix() {
for (unsigned i=0;i<m_number_of_extruders;++i) {
for (unsigned j=0;j<m_number_of_extruders;++j) {
if (i==j) continue;
edit_boxes[j][i]->SetValue(wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue()));
}
}
}
// Function to check if simple and advanced settings are matching
bool WipingPanel::advanced_matches_simple() {
for (unsigned i=0;i<m_number_of_extruders;++i) {
for (unsigned j=0;j<m_number_of_extruders;++j) {
if (i==j) continue;
if (edit_boxes[j][i]->GetValue() != (wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue())))
return false;
}
}
return true;
}
// Switches the dialog from simple to advanced mode and vice versa
void WipingPanel::toggle_advanced(bool user_action) {
if (m_advanced && !advanced_matches_simple() && user_action) {
if (wxMessageDialog(this,wxString(_(L("Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?"))),
wxString(_(L("Warning"))),wxYES_NO|wxICON_EXCLAMATION).ShowModal() != wxID_YES)
return;
}
if (user_action)
m_advanced = !m_advanced; // user demands a change -> toggle
else
m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate
(m_advanced ? m_page_advanced : m_page_simple)->Show();
(!m_advanced ? m_page_advanced : m_page_simple)->Hide();
m_widget_button->SetLabel(m_advanced ? _(L("Show simplified settings")) : _(L("Show advanced settings")));
if (m_advanced)
if (user_action) fill_in_matrix(); // otherwise keep values loaded from config
m_sizer->Layout();
Refresh();
}

View file

@ -0,0 +1,90 @@
#ifndef _WIPE_TOWER_DIALOG_H_
#define _WIPE_TOWER_DIALOG_H_
#include <wx/spinctrl.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include <wx/msgdlg.h>
#include "RammingChart.hpp"
class RammingPanel : public wxPanel {
public:
RammingPanel(wxWindow* parent);
RammingPanel(wxWindow* parent,const std::string& data);
std::string get_parameters();
private:
Chart* m_chart = nullptr;
wxSpinCtrl* m_widget_volume = nullptr;
wxSpinCtrl* m_widget_ramming_line_width_multiplicator = nullptr;
wxSpinCtrl* m_widget_ramming_step_multiplicator = nullptr;
wxSpinCtrlDouble* m_widget_time = nullptr;
int m_ramming_step_multiplicator;
int m_ramming_line_width_multiplicator;
void line_parameters_changed();
};
class RammingDialog : public wxDialog {
public:
RammingDialog(wxWindow* parent,const std::string& parameters);
std::string get_parameters() { return m_output_data; }
private:
RammingPanel* m_panel_ramming = nullptr;
std::string m_output_data;
};
class WipingPanel : public wxPanel {
public:
WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, wxButton* widget_button);
std::vector<float> read_matrix_values();
std::vector<float> read_extruders_values();
void toggle_advanced(bool user_action = false);
void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift=0);
private:
void fill_in_matrix();
bool advanced_matches_simple();
std::vector<wxSpinCtrl*> m_old;
std::vector<wxSpinCtrl*> m_new;
std::vector<std::vector<wxTextCtrl*>> edit_boxes;
unsigned int m_number_of_extruders = 0;
bool m_advanced = false;
wxPanel* m_page_simple = nullptr;
wxPanel* m_page_advanced = nullptr;
wxBoxSizer* m_sizer = nullptr;
wxBoxSizer* m_sizer_simple = nullptr;
wxBoxSizer* m_sizer_advanced = nullptr;
wxGridSizer* m_gridsizer_advanced = nullptr;
wxButton* m_widget_button = nullptr;
};
class WipingDialog : public wxDialog {
public:
WipingDialog(wxWindow* parent,const std::vector<float>& matrix, const std::vector<float>& extruders);
std::vector<float> get_matrix() const { return m_output_matrix; }
std::vector<float> get_extruders() const { return m_output_extruders; }
private:
WipingPanel* m_panel_wiping = nullptr;
std::vector<float> m_output_matrix;
std::vector<float> m_output_extruders;
};
#endif // _WIPE_TOWER_DIALOG_H_

View file

@ -0,0 +1,30 @@
// I AM A PHONY PLACEHOLDER FOR THE PERL CALLBACK.
// GET RID OF ME!
#ifndef slic3r_GUI_PerlCallback_phony_hpp_
#define slic3r_GUI_PerlCallback_phony_hpp_
#include <vector>
namespace Slic3r {
class PerlCallback {
public:
PerlCallback(void *) {}
PerlCallback() {}
void register_callback(void *) {}
void deregister_callback() {}
void call() const {}
void call(int) const {}
void call(int, int) const {}
void call(const std::vector<int>&) const {}
void call(double) const {}
void call(double, double) const {}
void call(double, double, double) const {}
void call(double, double, double, double) const {}
void call(bool b) const {}
};
} // namespace Slic3r
#endif /* slic3r_GUI_PerlCallback_phony_hpp_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,773 @@
#ifndef slic3r_GUI_wxExtensions_hpp_
#define slic3r_GUI_wxExtensions_hpp_
#include <wx/checklst.h>
#include <wx/combo.h>
#include <wx/dataview.h>
#include <wx/dc.h>
#include <wx/collpane.h>
#include <wx/wupdlock.h>
#include <wx/button.h>
#include <wx/slider.h>
#include <vector>
#include <set>
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{
static const unsigned int DefaultWidth;
static const unsigned int DefaultHeight;
static const unsigned int DefaultItemHeight;
wxString m_text;
// Events sent on mouseclick are quite complex. Function OnListBoxSelection is supposed to pass the event to the checkbox, which works fine on
// Win. On OSX and Linux the events are generated differently - clicking on the checkbox square generates the event twice (and the square
// therefore seems not to respond).
// This enum is meant to save current state of affairs, i.e., if the event forwarding is ok to do or not. It is only used on Linux
// and OSX by some #ifdefs. It also stores information whether OnListBoxSelection is supposed to change the checkbox status,
// or if it changed status on its own already (which happens when the square is clicked). More comments in OnCheckListBox(...)
// There indeed is a better solution, maybe making a custom event used for the event passing to distinguish the original and passed message
// and blocking one of them on OSX and Linux. Feel free to refactor, but carefully test on all platforms.
enum class OnCheckListBoxFunction{
FreeToProceed,
RefuseToProceed,
WasRefusedLastTime
} m_check_box_events_status = OnCheckListBoxFunction::FreeToProceed;
public:
virtual bool Create(wxWindow* parent);
virtual wxWindow* GetControl();
virtual void SetStringValue(const wxString& value);
virtual wxString GetStringValue() const;
virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
virtual void OnKeyEvent(wxKeyEvent& evt);
void OnCheckListBox(wxCommandEvent& evt);
void OnListBoxSelection(wxCommandEvent& evt);
};
// *** wxDataViewTreeCtrlComboBox ***
class wxDataViewTreeCtrlComboPopup: public wxDataViewTreeCtrl, public wxComboPopup
{
static const unsigned int DefaultWidth;
static const unsigned int DefaultHeight;
static const unsigned int DefaultItemHeight;
wxString m_text;
int m_cnt_open_items{0};
public:
virtual bool Create(wxWindow* parent);
virtual wxWindow* GetControl() { return this; }
virtual void SetStringValue(const wxString& value) { m_text = value; }
virtual wxString GetStringValue() const { return m_text; }
// virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
virtual void OnKeyEvent(wxKeyEvent& evt);
void OnDataViewTreeCtrlSelection(wxCommandEvent& evt);
void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; }
};
// *** PrusaCollapsiblePane ***
// ----------------------------------------------------------------------------
class PrusaCollapsiblePane : public wxCollapsiblePane
{
public:
PrusaCollapsiblePane() {}
PrusaCollapsiblePane(wxWindow *parent,
wxWindowID winid,
const wxString& label,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxCP_DEFAULT_STYLE,
const wxValidator& val = wxDefaultValidator,
const wxString& name = wxCollapsiblePaneNameStr)
{
Create(parent, winid, label, pos, size, style, val, name);
}
~PrusaCollapsiblePane() {}
void OnStateChange(const wxSize& sz); //override/hide of OnStateChange from wxCollapsiblePane
virtual bool Show(bool show = true) override {
wxCollapsiblePane::Show(show);
OnStateChange(GetBestSize());
return true;
}
};
// *** PrusaCollapsiblePaneMSW *** used only #ifdef __WXMSW__
// ----------------------------------------------------------------------------
#ifdef __WXMSW__
class PrusaCollapsiblePaneMSW : public PrusaCollapsiblePane//wxCollapsiblePane
{
wxButton* m_pDisclosureTriangleButton = nullptr;
wxBitmap m_bmp_close;
wxBitmap m_bmp_open;
public:
PrusaCollapsiblePaneMSW() {}
PrusaCollapsiblePaneMSW( wxWindow *parent,
wxWindowID winid,
const wxString& label,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxCP_DEFAULT_STYLE,
const wxValidator& val = wxDefaultValidator,
const wxString& name = wxCollapsiblePaneNameStr)
{
Create(parent, winid, label, pos, size, style, val, name);
}
~PrusaCollapsiblePaneMSW() {}
bool Create(wxWindow *parent,
wxWindowID id,
const wxString& label,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& val,
const wxString& name);
void UpdateBtnBmp();
void SetLabel(const wxString &label) override;
bool Layout() override;
void Collapse(bool collapse) override;
};
#endif //__WXMSW__
// *****************************************************************************
// ----------------------------------------------------------------------------
// PrusaDataViewBitmapText: helper class used by PrusaBitmapTextRenderer
// ----------------------------------------------------------------------------
class PrusaDataViewBitmapText : public wxObject
{
public:
PrusaDataViewBitmapText(const wxString &text = wxEmptyString,
const wxBitmap& bmp = wxNullBitmap) :
m_text(text), m_bmp(bmp)
{ }
PrusaDataViewBitmapText(const PrusaDataViewBitmapText &other)
: wxObject(),
m_text(other.m_text),
m_bmp(other.m_bmp)
{ }
void SetText(const wxString &text) { m_text = text; }
wxString GetText() const { return m_text; }
void SetBitmap(const wxIcon &icon) { m_bmp = icon; }
const wxBitmap &GetBitmap() const { return m_bmp; }
bool IsSameAs(const PrusaDataViewBitmapText& other) const {
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
}
bool operator==(const PrusaDataViewBitmapText& other) const {
return IsSameAs(other);
}
bool operator!=(const PrusaDataViewBitmapText& other) const {
return !IsSameAs(other);
}
private:
wxString m_text;
wxBitmap m_bmp;
};
DECLARE_VARIANT_OBJECT(PrusaDataViewBitmapText)
// ----------------------------------------------------------------------------
// PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel
// ----------------------------------------------------------------------------
class PrusaObjectDataViewModelNode;
WX_DEFINE_ARRAY_PTR(PrusaObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray);
class PrusaObjectDataViewModelNode
{
PrusaObjectDataViewModelNode* m_parent;
MyObjectTreeModelNodePtrArray m_children;
wxIcon m_empty_icon;
wxBitmap m_empty_bmp;
std::vector< std::string > m_opt_categories;
public:
PrusaObjectDataViewModelNode(const wxString &name, const int instances_count=1) {
m_parent = NULL;
m_name = name;
m_copy = wxString::Format("%d", instances_count);
m_type = "object";
m_volume_id = -1;
#ifdef __WXGTK__
// it's necessary on GTK because of control have to know if this item will be container
// in another case you couldn't to add subitem for this item
// it will be produce "segmentation fault"
m_container = true;
#endif //__WXGTK__
set_object_action_icon();
}
PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent,
const wxString& sub_obj_name,
const wxBitmap& bmp,
const wxString& extruder,
const int volume_id=-1) {
m_parent = parent;
m_name = sub_obj_name;
m_copy = wxEmptyString;
m_bmp = bmp;
m_type = "volume";
m_volume_id = volume_id;
m_extruder = extruder;
#ifdef __WXGTK__
// it's necessary on GTK because of control have to know if this item will be container
// in another case you couldn't to add subitem for this item
// it will be produce "segmentation fault"
m_container = true;
#endif //__WXGTK__
set_part_action_icon();
}
PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent) :
m_parent(parent),
m_name("Settings to modified"),
m_copy(wxEmptyString),
m_type("settings"),
m_extruder(wxEmptyString) {}
~PrusaObjectDataViewModelNode()
{
// free all our children nodes
size_t count = m_children.GetCount();
for (size_t i = 0; i < count; i++)
{
PrusaObjectDataViewModelNode *child = m_children[i];
delete child;
}
}
wxString m_name;
wxIcon& m_icon = m_empty_icon;
wxBitmap& m_bmp = m_empty_bmp;
wxString m_copy;
std::string m_type;
int m_volume_id = -2;
bool m_container = false;
wxString m_extruder = "default";
wxBitmap m_action_icon;
bool IsContainer() const
{
return m_container;
}
PrusaObjectDataViewModelNode* GetParent()
{
return m_parent;
}
MyObjectTreeModelNodePtrArray& GetChildren()
{
return m_children;
}
PrusaObjectDataViewModelNode* GetNthChild(unsigned int n)
{
return m_children.Item(n);
}
void Insert(PrusaObjectDataViewModelNode* child, unsigned int n)
{
if (!m_container)
m_container = true;
m_children.Insert(child, n);
}
void Append(PrusaObjectDataViewModelNode* child)
{
if (!m_container)
m_container = true;
m_children.Add(child);
}
void RemoveAllChildren()
{
if (GetChildCount() == 0)
return;
for (size_t id = GetChildCount() - 1; id >= 0; --id)
{
if (m_children.Item(id)->GetChildCount() > 0)
m_children[id]->RemoveAllChildren();
auto node = m_children[id];
m_children.RemoveAt(id);
delete node;
}
}
size_t GetChildCount() const
{
return m_children.GetCount();
}
bool SetValue(const wxVariant &variant, unsigned int col)
{
switch (col)
{
case 0:{
PrusaDataViewBitmapText data;
data << variant;
m_bmp = data.GetBitmap();
m_name = data.GetText();
return true;}
case 1:
m_copy = variant.GetString();
return true;
case 2:
m_extruder = variant.GetString();
return true;
case 3:
m_action_icon << variant;
return true;
default:
printf("MyObjectTreeModel::SetValue: wrong column");
}
return false;
}
void SetIcon(const wxIcon &icon)
{
m_icon = icon;
}
void SetBitmap(const wxBitmap &icon)
{
m_bmp = icon;
}
void SetType(const std::string& type){
m_type = type;
}
const std::string& GetType(){
return m_type;
}
void SetVolumeId(const int& volume_id){
m_volume_id = volume_id;
}
const int& GetVolumeId(){
return m_volume_id;
}
// use this function only for childrens
void AssignAllVal(PrusaObjectDataViewModelNode& from_node)
{
// ! Don't overwrite other values because of equality of this values for all children --
m_name = from_node.m_name;
m_icon = from_node.m_icon;
m_volume_id = from_node.m_volume_id;
m_extruder = from_node.m_extruder;
}
bool SwapChildrens(int frst_id, int scnd_id) {
if (GetChildCount() < 2 ||
frst_id < 0 || frst_id >= GetChildCount() ||
scnd_id < 0 || scnd_id >= GetChildCount())
return false;
PrusaObjectDataViewModelNode new_scnd = *GetNthChild(frst_id);
PrusaObjectDataViewModelNode new_frst = *GetNthChild(scnd_id);
new_scnd.m_volume_id = m_children.Item(scnd_id)->m_volume_id;
new_frst.m_volume_id = m_children.Item(frst_id)->m_volume_id;
m_children.Item(frst_id)->AssignAllVal(new_frst);
m_children.Item(scnd_id)->AssignAllVal(new_scnd);
return true;
}
// Set action icons for node
void set_object_action_icon();
void set_part_action_icon();
bool update_settings_digest(const std::vector<std::string>& categories);
};
// ----------------------------------------------------------------------------
// PrusaObjectDataViewModel
// ----------------------------------------------------------------------------
class PrusaObjectDataViewModel :public wxDataViewModel
{
std::vector<PrusaObjectDataViewModelNode*> m_objects;
public:
PrusaObjectDataViewModel();
~PrusaObjectDataViewModel();
wxDataViewItem Add(const wxString &name);
wxDataViewItem Add(const wxString &name, const int instances_count);
wxDataViewItem AddChild(const wxDataViewItem &parent_item,
const wxString &name,
const wxBitmap& icon,
const int extruder = 0,
const bool create_frst_child = true);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
wxDataViewItem Delete(const wxDataViewItem &item);
void DeleteAll();
void DeleteChildren(wxDataViewItem& parent);
wxDataViewItem GetItemById(int obj_idx);
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
int GetIdByItem(wxDataViewItem& item);
int GetVolumeIdByItem(const wxDataViewItem& item);
bool IsEmpty() { return m_objects.empty(); }
// helper method for wxLog
wxString GetName(const wxDataViewItem &item) const;
wxString GetCopy(const wxDataViewItem &item) const;
wxIcon& GetIcon(const wxDataViewItem &item) const;
wxBitmap& GetBitmap(const wxDataViewItem &item) const;
// helper methods to change the model
virtual unsigned int GetColumnCount() const override { return 3;}
virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); }
virtual void GetValue(wxVariant &variant,
const wxDataViewItem &item, unsigned int col) const override;
virtual bool SetValue(const wxVariant &variant,
const wxDataViewItem &item, unsigned int col) override;
bool SetValue(const wxVariant &variant, const int item_idx, unsigned int col);
wxDataViewItem MoveChildUp(const wxDataViewItem &item);
wxDataViewItem MoveChildDown(const wxDataViewItem &item);
// For parent move child from cur_volume_id place to new_volume_id
// Remaining items will moved up/down accordingly
wxDataViewItem ReorganizeChildren(int cur_volume_id,
int new_volume_id,
const wxDataViewItem &parent);
virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override;
virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override;
virtual bool IsContainer(const wxDataViewItem &item) const override;
virtual unsigned int GetChildren(const wxDataViewItem &parent,
wxDataViewItemArray &array) const override;
// Is the container just a header or an item with all columns
// In our case it is an item with all columns
virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
wxDataViewItem HasSettings(const wxDataViewItem &item) const;
bool IsSettingsItem(const wxDataViewItem &item) const;
void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector<std::string>& categories);
};
// ----------------------------------------------------------------------------
// PrusaBitmapTextRenderer
// ----------------------------------------------------------------------------
class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer
{
public:
PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT,
int align = wxDVR_DEFAULT_ALIGNMENT):
wxDataViewCustomRenderer(wxT("wxObject"), mode, align) {}
bool SetValue(const wxVariant &value);
bool GetValue(wxVariant &value) const;
virtual bool Render(wxRect cell, wxDC *dc, int state);
virtual wxSize GetSize() const;
virtual bool HasEditorCtrl() const { return false; }
private:
// wxDataViewIconText m_value;
PrusaDataViewBitmapText m_value;
};
// ----------------------------------------------------------------------------
// MyCustomRenderer
// ----------------------------------------------------------------------------
class MyCustomRenderer : public wxDataViewCustomRenderer
{
public:
// This renderer can be either activatable or editable, for demonstration
// purposes. In real programs, you should select whether the user should be
// able to activate or edit the cell and it doesn't make sense to switch
// between the two -- but this is just an example, so it doesn't stop us.
explicit MyCustomRenderer(wxDataViewCellMode mode)
: wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER)
{ }
virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/
{
dc->SetBrush(*wxLIGHT_GREY_BRUSH);
dc->SetPen(*wxTRANSPARENT_PEN);
rect.Deflate(2);
dc->DrawRoundedRectangle(rect, 5);
RenderText(m_value,
0, // no offset
wxRect(dc->GetTextExtent(m_value)).CentreIn(rect),
dc,
state);
return true;
}
virtual bool ActivateCell(const wxRect& WXUNUSED(cell),
wxDataViewModel *WXUNUSED(model),
const wxDataViewItem &WXUNUSED(item),
unsigned int WXUNUSED(col),
const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/
{
wxString position;
if (mouseEvent)
position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y);
else
position = "from keyboard";
// wxLogMessage("MyCustomRenderer ActivateCell() %s", position);
return false;
}
virtual wxSize GetSize() const override/*wxOVERRIDE*/
{
return wxSize(60, 20);
}
virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/
{
m_value = value.GetString();
return true;
}
virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; }
virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; }
virtual wxWindow*
CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override/*wxOVERRIDE*/
{
wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value,
labelRect.GetPosition(),
labelRect.GetSize(),
wxTE_PROCESS_ENTER);
text->SetInsertionPointEnd();
return text;
}
virtual bool
GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/
{
wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl);
if (!text)
return false;
value = text->GetValue();
return true;
}
private:
wxString m_value;
};
// ----------------------------------------------------------------------------
// PrusaDoubleSlider
// ----------------------------------------------------------------------------
enum SelectedSlider {
ssUndef,
ssLower,
ssHigher
};
enum TicksAction{
taOnIcon,
taAdd,
taDel
};
class PrusaDoubleSlider : public wxControl
{
public:
PrusaDoubleSlider(
wxWindow *parent,
wxWindowID id,
int lowerValue,
int higherValue,
int minValue,
int maxValue,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxSL_VERTICAL,
const wxValidator& val = wxDefaultValidator,
const wxString& name = wxEmptyString);
~PrusaDoubleSlider(){}
int GetLowerValue() const {
return m_lower_value;
}
int GetHigherValue() const {
return m_higher_value;
}
int GetActiveValue() const;
double GetLowerValueD() const { return get_double_value(ssLower); }
double GetHigherValueD() const { return get_double_value(ssHigher); }
wxSize DoGetBestSize() const override;
void SetLowerValue(const int lower_val);
void SetHigherValue(const int higher_val);
void SetMaxValue(const int max_value);
void SetKoefForLabels(const double koef) {
m_label_koef = koef;
}
void SetSliderValues(const std::vector<std::pair<int, double>>& values) {
m_values = values;
}
void ChangeOneLayerLock();
void OnPaint(wxPaintEvent& ){ render();}
void OnLeftDown(wxMouseEvent& event);
void OnMotion(wxMouseEvent& event);
void OnLeftUp(wxMouseEvent& event);
void OnEnterWin(wxMouseEvent& event){ enter_window(event, true); }
void OnLeaveWin(wxMouseEvent& event){ enter_window(event, false); }
void OnWheel(wxMouseEvent& event);
void OnKeyDown(wxKeyEvent &event);
void OnKeyUp(wxKeyEvent &event);
void OnRightDown(wxMouseEvent& event);
void OnRightUp(wxMouseEvent& event);
protected:
void render();
void draw_focus_rect();
void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end);
void draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
void draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection);
void draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos);
void draw_ticks(wxDC& dc);
void draw_one_layer_icon(wxDC& dc);
void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection);
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection);
void detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false);
void correct_lower_value();
void correct_higher_value();
void move_current_thumb(const bool condition);
void action_tick(const TicksAction action);
void enter_window(wxMouseEvent& event, const bool enter);
bool is_point_in_rect(const wxPoint& pt, const wxRect& rect);
bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
double get_scroll_step();
wxString get_label(const SelectedSlider& selection) const;
void get_lower_and_higher_position(int& lower_pos, int& higher_pos);
int get_value_from_position(const wxCoord x, const wxCoord y);
wxCoord get_position_from_value(const int value);
wxSize get_size();
void get_size(int *w, int *h);
double get_double_value(const SelectedSlider& selection) const;
private:
int m_min_value;
int m_max_value;
int m_lower_value;
int m_higher_value;
wxBitmap m_bmp_thumb_higher;
wxBitmap m_bmp_thumb_lower;
wxBitmap m_bmp_add_tick_on;
wxBitmap m_bmp_add_tick_off;
wxBitmap m_bmp_del_tick_on;
wxBitmap m_bmp_del_tick_off;
wxBitmap m_bmp_one_layer_lock_on;
wxBitmap m_bmp_one_layer_lock_off;
wxBitmap m_bmp_one_layer_unlock_on;
wxBitmap m_bmp_one_layer_unlock_off;
SelectedSlider m_selection;
bool m_is_left_down = false;
bool m_is_right_down = false;
bool m_is_one_layer = false;
bool m_is_focused = false;
bool m_is_action_icon_focesed = false;
bool m_is_one_layer_icon_focesed = false;
wxRect m_rect_lower_thumb;
wxRect m_rect_higher_thumb;
wxRect m_rect_tick_action;
wxRect m_rect_one_layer_icon;
wxSize m_thumb_size;
int m_tick_icon_dim;
int m_lock_icon_dim;
long m_style;
float m_label_koef = 1.0;
// control's view variables
wxCoord SLIDER_MARGIN; // margin around slider
wxPen DARK_ORANGE_PEN;
wxPen ORANGE_PEN;
wxPen LIGHT_ORANGE_PEN;
wxPen DARK_GREY_PEN;
wxPen GREY_PEN;
wxPen LIGHT_GREY_PEN;
std::vector<wxPen*> line_pens;
std::vector<wxPen*> segm_pens;
std::set<int> m_ticks;
std::vector<std::pair<int,double>> m_values;
};
// ----------------------------------------------------------------------------
// PrusaLockButton
// ----------------------------------------------------------------------------
class PrusaLockButton : public wxButton
{
public:
PrusaLockButton(
wxWindow *parent,
wxWindowID id,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize);
~PrusaLockButton(){}
void OnButton(wxCommandEvent& event);
void OnEnterBtn(wxMouseEvent& event){ enter_button(true); event.Skip(); }
void OnLeaveBtn(wxMouseEvent& event){ enter_button(false); event.Skip(); }
bool IsLocked() const { return m_is_pushed; }
protected:
void enter_button(const bool enter);
private:
bool m_is_pushed = false;
wxBitmap m_bmp_lock_on;
wxBitmap m_bmp_lock_off;
wxBitmap m_bmp_unlock_on;
wxBitmap m_bmp_unlock_off;
int m_lock_icon_dim;
};
// ******************************* EXPERIMENTS **********************************************
// ******************************************************************************************
#endif // slic3r_GUI_wxExtensions_hpp_

25
src/slic3r/GUI/wxinit.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef slic3r_wxinit_hpp_
#define slic3r_wxinit_hpp_
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/html/htmlwin.h>
// Perl redefines a _ macro, so we undef this one
#undef _
// We do want to use translation however, so define it as __ so we can do a find/replace
// later when we no longer need to undef _
#define __(s) wxGetTranslation((s))
// legacy macros
// https://wiki.wxwidgets.org/EventTypes_and_Event-Table_Macros
#ifndef wxEVT_BUTTON
#define wxEVT_BUTTON wxEVT_COMMAND_BUTTON_CLICKED
#endif
#ifndef wxEVT_HTML_LINK_CLICKED
#define wxEVT_HTML_LINK_CLICKED wxEVT_COMMAND_HTML_LINK_CLICKED
#endif
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
#ifndef slic3r_ASCIIFolding_hpp_
#define slic3r_ASCIIFolding_hpp_
#include <string>
namespace Slic3r {
// If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares.
extern std::string fold_utf8_to_ascii(const char *src);
extern std::string fold_utf8_to_ascii(const std::string &src);
}; // namespace Slic3r
#endif /* slic3r_ASCIIFolding_hpp_ */

View file

@ -0,0 +1,781 @@
#include "Bonjour.hpp"
#include <cstdint>
#include <algorithm>
#include <array>
#include <vector>
#include <string>
#include <random>
#include <thread>
#include <boost/optional.hpp>
#include <boost/system/error_code.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <boost/format.hpp>
using boost::optional;
using boost::system::error_code;
namespace endian = boost::endian;
namespace asio = boost::asio;
using boost::asio::ip::udp;
namespace Slic3r {
// Minimal implementation of a MDNS/DNS-SD client
// This implementation is extremely simple, only the bits that are useful
// for basic MDNS discovery of OctoPi devices are present.
// However, the bits that are present are implemented with security in mind.
// Only fully correct DNS replies are allowed through.
// While decoding the decoder will bail the moment it encounters anything fishy.
// At least that's the idea. To help prove this is actually the case,
// the implementations has been tested with AFL.
struct DnsName: public std::string
{
enum
{
MAX_RECURSION = 10, // Keep this low
};
static optional<DnsName> decode(const std::vector<char> &buffer, size_t &offset, unsigned depth = 0)
{
// Check offset sanity:
if (offset + 1 >= buffer.size()) {
return boost::none;
}
// Check for recursion depth to prevent parsing names that are nested too deeply or end up cyclic:
if (depth >= MAX_RECURSION) {
return boost::none;
}
DnsName res;
const size_t bsize = buffer.size();
while (true) {
const char* ptr = buffer.data() + offset;
unsigned len = static_cast<unsigned char>(*ptr);
if (len & 0xc0) {
// This is a recursive label
unsigned len_2 = static_cast<unsigned char>(ptr[1]);
size_t pointer = (len & 0x3f) << 8 | len_2;
const auto nested = decode(buffer, pointer, depth + 1);
if (!nested) {
return boost::none;
} else {
if (res.size() > 0) {
res.push_back('.');
}
res.append(*nested);
offset += 2;
return std::move(res);
}
} else if (len == 0) {
// This is a name terminator
offset++;
break;
} else {
// This is a regular label
len &= 0x3f;
if (len + offset + 1 >= bsize) {
return boost::none;
}
res.reserve(len);
if (res.size() > 0) {
res.push_back('.');
}
ptr++;
for (const auto end = ptr + len; ptr < end; ptr++) {
char c = *ptr;
if (c >= 0x20 && c <= 0x7f) {
res.push_back(c);
} else {
return boost::none;
}
}
offset += len + 1;
}
}
if (res.size() > 0) {
return std::move(res);
} else {
return boost::none;
}
}
};
struct DnsHeader
{
uint16_t id;
uint16_t flags;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
enum
{
SIZE = 12,
};
static DnsHeader decode(const std::vector<char> &buffer) {
DnsHeader res;
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data());
res.id = endian::big_to_native(data_16[0]);
res.flags = endian::big_to_native(data_16[1]);
res.qdcount = endian::big_to_native(data_16[2]);
res.ancount = endian::big_to_native(data_16[3]);
res.nscount = endian::big_to_native(data_16[4]);
res.arcount = endian::big_to_native(data_16[5]);
return res;
}
uint32_t rrcount() const {
return ancount + nscount + arcount;
}
};
struct DnsQuestion
{
enum
{
MIN_SIZE = 5,
};
DnsName name;
uint16_t type;
uint16_t qclass;
DnsQuestion() :
type(0),
qclass(0)
{}
static optional<DnsQuestion> decode(const std::vector<char> &buffer, size_t &offset)
{
auto qname = DnsName::decode(buffer, offset);
if (!qname) {
return boost::none;
}
DnsQuestion res;
res.name = std::move(*qname);
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data() + offset);
res.type = endian::big_to_native(data_16[0]);
res.qclass = endian::big_to_native(data_16[1]);
offset += 4;
return std::move(res);
}
};
struct DnsResource
{
DnsName name;
uint16_t type;
uint16_t rclass;
uint32_t ttl;
std::vector<char> data;
DnsResource() :
type(0),
rclass(0),
ttl(0)
{}
static optional<DnsResource> decode(const std::vector<char> &buffer, size_t &offset, size_t &dataoffset)
{
const size_t bsize = buffer.size();
if (offset + 1 >= bsize) {
return boost::none;
}
auto rname = DnsName::decode(buffer, offset);
if (!rname) {
return boost::none;
}
if (offset + 10 >= bsize) {
return boost::none;
}
DnsResource res;
res.name = std::move(*rname);
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data() + offset);
res.type = endian::big_to_native(data_16[0]);
res.rclass = endian::big_to_native(data_16[1]);
res.ttl = endian::big_to_native(*reinterpret_cast<const uint32_t*>(data_16 + 2));
uint16_t rdlength = endian::big_to_native(data_16[4]);
offset += 10;
if (offset + rdlength > bsize) {
return boost::none;
}
dataoffset = offset;
res.data = std::move(std::vector<char>(buffer.begin() + offset, buffer.begin() + offset + rdlength));
offset += rdlength;
return std::move(res);
}
};
struct DnsRR_A
{
enum { TAG = 0x1 };
asio::ip::address_v4 ip;
static void decode(optional<DnsRR_A> &result, const DnsResource &rr)
{
if (rr.data.size() == 4) {
DnsRR_A res;
const uint32_t ip = endian::big_to_native(*reinterpret_cast<const uint32_t*>(rr.data.data()));
res.ip = asio::ip::address_v4(ip);
result = std::move(res);
}
}
};
struct DnsRR_AAAA
{
enum { TAG = 0x1c };
asio::ip::address_v6 ip;
static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr)
{
if (rr.data.size() == 16) {
DnsRR_AAAA res;
std::array<unsigned char, 16> ip;
std::copy_n(rr.data.begin(), 16, ip.begin());
res.ip = asio::ip::address_v6(ip);
result = std::move(res);
}
}
};
struct DnsRR_SRV
{
enum
{
TAG = 0x21,
MIN_SIZE = 8,
};
uint16_t priority;
uint16_t weight;
uint16_t port;
DnsName hostname;
static optional<DnsRR_SRV> decode(const std::vector<char> &buffer, const DnsResource &rr, size_t dataoffset)
{
if (rr.data.size() < MIN_SIZE) {
return boost::none;
}
DnsRR_SRV res;
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(rr.data.data());
res.priority = endian::big_to_native(data_16[0]);
res.weight = endian::big_to_native(data_16[1]);
res.port = endian::big_to_native(data_16[2]);
size_t offset = dataoffset + 6;
auto hostname = DnsName::decode(buffer, offset);
if (hostname) {
res.hostname = std::move(*hostname);
return std::move(res);
} else {
return boost::none;
}
}
};
struct DnsRR_TXT
{
enum
{
TAG = 0x10,
};
std::vector<std::string> values;
static optional<DnsRR_TXT> decode(const DnsResource &rr)
{
const size_t size = rr.data.size();
if (size < 2) {
return boost::none;
}
DnsRR_TXT res;
for (auto it = rr.data.begin(); it != rr.data.end(); ) {
unsigned val_size = static_cast<unsigned char>(*it);
if (val_size == 0 || it + val_size >= rr.data.end()) {
return boost::none;
}
++it;
std::string value(val_size, ' ');
std::copy(it, it + val_size, value.begin());
res.values.push_back(std::move(value));
it += val_size;
}
return std::move(res);
}
};
struct DnsSDPair
{
optional<DnsRR_SRV> srv;
optional<DnsRR_TXT> txt;
};
struct DnsSDMap : public std::map<std::string, DnsSDPair>
{
void insert_srv(std::string &&name, DnsRR_SRV &&srv)
{
auto hit = this->find(name);
if (hit != this->end()) {
hit->second.srv = std::move(srv);
} else {
DnsSDPair pair;
pair.srv = std::move(srv);
this->insert(std::make_pair(std::move(name), std::move(pair)));
}
}
void insert_txt(std::string &&name, DnsRR_TXT &&txt)
{
auto hit = this->find(name);
if (hit != this->end()) {
hit->second.txt = std::move(txt);
} else {
DnsSDPair pair;
pair.txt = std::move(txt);
this->insert(std::make_pair(std::move(name), std::move(pair)));
}
}
};
struct DnsMessage
{
enum
{
MAX_SIZE = 4096,
MAX_ANS = 30,
};
DnsHeader header;
optional<DnsQuestion> question;
optional<DnsRR_A> rr_a;
optional<DnsRR_AAAA> rr_aaaa;
std::vector<DnsRR_SRV> rr_srv;
DnsSDMap sdmap;
static optional<DnsMessage> decode(const std::vector<char> &buffer, optional<uint16_t> id_wanted = boost::none)
{
const auto size = buffer.size();
if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) {
return boost::none;
}
DnsMessage res;
res.header = DnsHeader::decode(buffer);
if (id_wanted && *id_wanted != res.header.id) {
return boost::none;
}
if (res.header.qdcount > 1 || res.header.ancount > MAX_ANS) {
return boost::none;
}
size_t offset = DnsHeader::SIZE;
if (res.header.qdcount == 1) {
res.question = DnsQuestion::decode(buffer, offset);
}
for (unsigned i = 0; i < res.header.rrcount(); i++) {
size_t dataoffset = 0;
auto rr = DnsResource::decode(buffer, offset, dataoffset);
if (!rr) {
return boost::none;
} else {
res.parse_rr(buffer, std::move(*rr), dataoffset);
}
}
return std::move(res);
}
private:
void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset)
{
switch (rr.type) {
case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break;
case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break;
case DnsRR_SRV::TAG: {
auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset);
if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); }
break;
}
case DnsRR_TXT::TAG: {
auto txt = DnsRR_TXT::decode(rr);
if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); }
break;
}
}
}
};
std::ostream& operator<<(std::ostream &os, const DnsMessage &msg)
{
os << "DnsMessage(ID: " << msg.header.id << ", "
<< "Q: " << (msg.question ? msg.question->name.c_str() : "none") << ", "
<< "A: " << (msg.rr_a ? msg.rr_a->ip.to_string() : "none") << ", "
<< "AAAA: " << (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none") << ", "
<< "services: [";
enum { SRV_PRINT_MAX = 3 };
unsigned i = 0;
for (const auto &sdpair : msg.sdmap) {
os << sdpair.first << ", ";
if (++i >= SRV_PRINT_MAX) {
os << "...";
break;
}
}
os << "])";
return os;
}
struct BonjourRequest
{
static const asio::ip::address_v4 MCAST_IP4;
static const uint16_t MCAST_PORT;
uint16_t id;
std::vector<char> data;
static optional<BonjourRequest> make(const std::string &service, const std::string &protocol);
private:
BonjourRequest(uint16_t id, std::vector<char> &&data) :
id(id),
data(std::move(data))
{}
};
const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb};
const uint16_t BonjourRequest::MCAST_PORT = 5353;
optional<BonjourRequest> BonjourRequest::make(const std::string &service, const std::string &protocol)
{
if (service.size() > 15 || protocol.size() > 15) {
return boost::none;
}
std::random_device dev;
std::uniform_int_distribution<uint16_t> dist;
uint16_t id = dist(dev);
uint16_t id_big = endian::native_to_big(id);
const char *id_char = reinterpret_cast<char*>(&id_big);
std::vector<char> data;
data.reserve(service.size() + 18);
// Add the transaction ID
data.push_back(id_char[0]);
data.push_back(id_char[1]);
// Add metadata
static const unsigned char rq_meta[] = {
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
// Add PTR query name
data.push_back(service.size() + 1);
data.push_back('_');
data.insert(data.end(), service.begin(), service.end());
data.push_back(protocol.size() + 1);
data.push_back('_');
data.insert(data.end(), protocol.begin(), protocol.end());
// Add the rest of PTR record
static const unsigned char ptr_tail[] = {
0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0xff,
};
std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
return BonjourRequest(id, std::move(data));
}
// API - private part
struct Bonjour::priv
{
const std::string service;
const std::string protocol;
const std::string service_dn;
unsigned timeout;
unsigned retries;
uint16_t rq_id;
std::vector<char> buffer;
std::thread io_thread;
Bonjour::ReplyFn replyfn;
Bonjour::CompleteFn completefn;
priv(std::string service, std::string protocol);
std::string strip_service_dn(const std::string &service_name) const;
void udp_receive(udp::endpoint from, size_t bytes);
void lookup_perform();
};
Bonjour::priv::priv(std::string service, std::string protocol) :
service(std::move(service)),
protocol(std::move(protocol)),
service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()),
timeout(10),
retries(1),
rq_id(0)
{
buffer.resize(DnsMessage::MAX_SIZE);
}
std::string Bonjour::priv::strip_service_dn(const std::string &service_name) const
{
if (service_name.size() <= service_dn.size()) {
return service_name;
}
auto needle = service_name.rfind(service_dn);
if (needle == service_name.size() - service_dn.size()) {
return service_name.substr(0, needle - 1);
} else {
return service_name;
}
}
void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
{
if (bytes == 0 || !replyfn) {
return;
}
buffer.resize(bytes);
const auto dns_msg = DnsMessage::decode(buffer, rq_id);
if (dns_msg) {
asio::ip::address ip = from.address();
if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; }
else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; }
for (const auto &sdpair : dns_msg->sdmap) {
if (! sdpair.second.srv) {
continue;
}
const auto &srv = *sdpair.second.srv;
auto service_name = strip_service_dn(sdpair.first);
std::string path;
std::string version;
if (sdpair.second.txt) {
static const std::string tag_path = "path=";
static const std::string tag_version = "version=";
for (const auto &value : sdpair.second.txt->values) {
if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) {
path = std::move(value.substr(tag_path.size()));
} else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
version = std::move(value.substr(tag_version.size()));
}
}
}
BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(path), std::move(version));
replyfn(std::move(reply));
}
}
}
void Bonjour::priv::lookup_perform()
{
const auto brq = BonjourRequest::make(service, protocol);
if (!brq) {
return;
}
auto self = this;
rq_id = brq->id;
try {
boost::asio::io_service io_service;
udp::socket socket(io_service);
socket.open(udp::v4());
socket.set_option(udp::socket::reuse_address(true));
udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
socket.send_to(asio::buffer(brq->data), mcast);
bool expired = false;
bool retry = false;
asio::deadline_timer timer(io_service);
retries--;
std::function<void(const error_code &)> timer_handler = [&](const error_code &error) {
if (retries == 0 || error) {
expired = true;
if (self->completefn) {
self->completefn();
}
} else {
retry = true;
retries--;
timer.expires_from_now(boost::posix_time::seconds(timeout));
timer.async_wait(timer_handler);
}
};
timer.expires_from_now(boost::posix_time::seconds(timeout));
timer.async_wait(timer_handler);
udp::endpoint recv_from;
const auto recv_handler = [&](const error_code &error, size_t bytes) {
if (!error) { self->udp_receive(recv_from, bytes); }
};
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
while (io_service.run_one()) {
if (expired) {
socket.cancel();
} else if (retry) {
retry = false;
socket.send_to(asio::buffer(brq->data), mcast);
} else {
buffer.resize(DnsMessage::MAX_SIZE);
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
}
}
} catch (std::exception& e) {
}
}
// API - public part
BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version) :
ip(std::move(ip)),
port(port),
service_name(std::move(service_name)),
hostname(std::move(hostname)),
path(path.empty() ? std::move(std::string("/")) : std::move(path)),
version(version.empty() ? std::move(std::string("Unknown")) : std::move(version))
{
std::string proto;
std::string port_suffix;
if (port == 443) { proto = "https://"; }
if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); }
if (this->path[0] != '/') { this->path.insert(0, 1, '/'); }
full_address = proto + ip.to_string() + port_suffix;
if (this->path != "/") { full_address += path; }
}
bool BonjourReply::operator==(const BonjourReply &other) const
{
return this->full_address == other.full_address
&& this->service_name == other.service_name;
}
bool BonjourReply::operator<(const BonjourReply &other) const
{
if (this->ip != other.ip) {
// So that the common case doesn't involve string comparison
return this->ip < other.ip;
} else {
auto cmp = this->full_address.compare(other.full_address);
return cmp != 0 ? cmp < 0 : this->service_name < other.service_name;
}
}
std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
{
os << "BonjourReply(" << reply.ip.to_string() << ", " << reply.service_name << ", "
<< reply.hostname << ", " << reply.path << ", " << reply.version << ")";
return os;
}
Bonjour::Bonjour(std::string service, std::string protocol) :
p(new priv(std::move(service), std::move(protocol)))
{}
Bonjour::Bonjour(Bonjour &&other) : p(std::move(other.p)) {}
Bonjour::~Bonjour()
{
if (p && p->io_thread.joinable()) {
p->io_thread.detach();
}
}
Bonjour& Bonjour::set_timeout(unsigned timeout)
{
if (p) { p->timeout = timeout; }
return *this;
}
Bonjour& Bonjour::set_retries(unsigned retries)
{
if (p && retries > 0) { p->retries = retries; }
return *this;
}
Bonjour& Bonjour::on_reply(ReplyFn fn)
{
if (p) { p->replyfn = std::move(fn); }
return *this;
}
Bonjour& Bonjour::on_complete(CompleteFn fn)
{
if (p) { p->completefn = std::move(fn); }
return *this;
}
Bonjour::Ptr Bonjour::lookup()
{
auto self = std::make_shared<Bonjour>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self]() {
self->p->lookup_perform();
});
self->p->io_thread = std::move(io_thread);
}
return self;
}
}

View file

@ -0,0 +1,64 @@
#ifndef slic3r_Bonjour_hpp_
#define slic3r_Bonjour_hpp_
#include <cstdint>
#include <memory>
#include <string>
#include <functional>
#include <boost/asio/ip/address.hpp>
namespace Slic3r {
struct BonjourReply
{
boost::asio::ip::address ip;
uint16_t port;
std::string service_name;
std::string hostname;
std::string full_address;
std::string path;
std::string version;
BonjourReply() = delete;
BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version);
bool operator==(const BonjourReply &other) const;
bool operator<(const BonjourReply &other) const;
};
std::ostream& operator<<(std::ostream &, const BonjourReply &);
/// Bonjour lookup performer
class Bonjour : public std::enable_shared_from_this<Bonjour> {
private:
struct priv;
public:
typedef std::shared_ptr<Bonjour> Ptr;
typedef std::function<void(BonjourReply &&)> ReplyFn;
typedef std::function<void()> CompleteFn;
Bonjour(std::string service, std::string protocol = "tcp");
Bonjour(Bonjour &&other);
~Bonjour();
Bonjour& set_timeout(unsigned timeout);
Bonjour& set_retries(unsigned retries);
// ^ Note: By default there is 1 retry (meaning 1 broadcast is sent).
// Timeout is per one retry, ie. total time spent listening = retries * timeout.
// If retries > 1, then care needs to be taken as more than one reply from the same service may be received.
Bonjour& on_reply(ReplyFn fn);
Bonjour& on_complete(CompleteFn fn);
Ptr lookup();
private:
std::unique_ptr<priv> p;
};
}
#endif

279
src/slic3r/Utils/Duet.cpp Normal file
View file

@ -0,0 +1,279 @@
#include "Duet.hpp"
#include "PrintHostSendDialog.hpp"
#include <algorithm>
#include <ctime>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <wx/frame.h>
#include <wx/event.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "Http.hpp"
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
Duet::Duet(DynamicPrintConfig *config) :
host(config->opt_string("print_host")),
password(config->opt_string("printhost_apikey"))
{}
Duet::~Duet() {}
bool Duet::test(wxString &msg) const
{
bool connected = connect(msg);
if (connected) {
disconnect();
}
return connected;
}
wxString Duet::get_test_ok_msg () const
{
return wxString::Format("%s", _(L("Connection to Duet works correctly.")));
}
wxString Duet::get_test_failed_msg (wxString &msg) const
{
return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg);
}
bool Duet::send_gcode(const std::string &filename) const
{
enum { PROGRESS_RANGE = 1000 };
const auto errortitle = _(L("Error while uploading to the Duet"));
fs::path filepath(filename);
PrintHostSendDialog send_dialog(filepath.filename(), true);
if (send_dialog.ShowModal() != wxID_OK) { return false; }
const bool print = send_dialog.print();
const auto upload_filepath = send_dialog.filename();
const auto upload_filename = upload_filepath.filename();
const auto upload_parent_path = upload_filepath.parent_path();
wxProgressDialog progress_dialog(
_(L("Duet upload")),
_(L("Sending G-code file to Duet...")),
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
progress_dialog.Pulse();
wxString connect_msg;
if (!connect(connect_msg)) {
auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
GUI::show_error(&progress_dialog, std::move(errormsg));
return false;
}
bool res = true;
auto upload_cmd = get_upload_url(upload_filepath.string());
BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
% filepath.string()
% upload_filename.string()
% upload_parent_path.string()
% print
% upload_cmd;
auto http = Http::post(std::move(upload_cmd));
http.set_post_body(filename)
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
progress_dialog.Update(PROGRESS_RANGE);
int err_code = get_err_code_from_body(body);
if (err_code != 0) {
auto msg = format_error(body, L("Unknown error occured"), 0);
GUI::show_error(&progress_dialog, std::move(msg));
res = false;
} else if (print) {
wxString errormsg;
res = start_print(errormsg, upload_filepath.string());
if (!res) {
GUI::show_error(&progress_dialog, std::move(errormsg));
}
}
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
GUI::show_error(&progress_dialog, std::move(errormsg));
res = false;
})
.on_progress([&](Http::Progress progress, bool &cancel) {
if (cancel) {
// Upload was canceled
res = false;
} else if (progress.ultotal > 0) {
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
} else {
cancel = !progress_dialog.Pulse();
}
})
.perform_sync();
disconnect();
return res;
}
bool Duet::has_auto_discovery() const
{
return false;
}
bool Duet::can_test() const
{
return true;
}
bool Duet::connect(wxString &msg) const
{
bool res = false;
auto url = get_connect_url();
auto http = Http::get(std::move(url));
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
msg = format_error(body, error, status);
})
.on_complete([&](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
int err_code = get_err_code_from_body(body);
switch (err_code) {
case 0:
res = true;
break;
case 1:
msg = format_error(body, L("Wrong password"), 0);
break;
case 2:
msg = format_error(body, L("Could not get resources to create a new connection"), 0);
break;
default:
msg = format_error(body, L("Unknown error occured"), 0);
break;
}
})
.perform_sync();
return res;
}
void Duet::disconnect() const
{
auto url = (boost::format("%1%rr_disconnect")
% get_base_url()).str();
auto http = Http::get(std::move(url));
http.on_error([&](std::string body, std::string error, unsigned status) {
// we don't care about it, if disconnect is not working Duet will disconnect automatically after some time
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
})
.perform_sync();
}
std::string Duet::get_upload_url(const std::string &filename) const
{
return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
% get_base_url()
% Http::url_encode(filename)
% timestamp_str()).str();
}
std::string Duet::get_connect_url() const
{
return (boost::format("%1%rr_connect?password=%2%&%3%")
% get_base_url()
% (password.empty() ? "reprap" : password)
% timestamp_str()).str();
}
std::string Duet::get_base_url() const
{
if (host.find("http://") == 0 || host.find("https://") == 0) {
if (host.back() == '/') {
return host;
} else {
return (boost::format("%1%/") % host).str();
}
} else {
return (boost::format("http://%1%/") % host).str();
}
}
std::string Duet::timestamp_str() const
{
enum { BUFFER_SIZE = 32 };
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
char buffer[BUFFER_SIZE];
std::strftime(buffer, BUFFER_SIZE, "time=%Y-%m-%dT%H:%M:%S", &tm);
return std::string(buffer);
}
wxString Duet::format_error(const std::string &body, const std::string &error, unsigned status)
{
if (status != 0) {
auto wxbody = wxString::FromUTF8(body.data());
return wxString::Format("HTTP %u: %s", status, wxbody);
} else {
return wxString::FromUTF8(error.data());
}
}
bool Duet::start_print(wxString &msg, const std::string &filename) const
{
bool res = false;
auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"")
% get_base_url()
% Http::url_encode(filename)).str();
auto http = Http::get(std::move(url));
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body;
msg = format_error(body, error, status);
})
.on_complete([&](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
res = true;
})
.perform_sync();
return res;
}
int Duet::get_err_code_from_body(const std::string &body) const
{
pt::ptree root;
std::istringstream iss (body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
}

47
src/slic3r/Utils/Duet.hpp Normal file
View file

@ -0,0 +1,47 @@
#ifndef slic3r_Duet_hpp_
#define slic3r_Duet_hpp_
#include <string>
#include <wx/string.h>
#include "PrintHost.hpp"
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class Duet : public PrintHost
{
public:
Duet(DynamicPrintConfig *config);
virtual ~Duet();
bool test(wxString &curl_msg) const;
wxString get_test_ok_msg () const;
wxString get_test_failed_msg (wxString &msg) const;
// Send gcode file to duet, filename is expected to be in UTF-8
bool send_gcode(const std::string &filename) const;
bool has_auto_discovery() const;
bool can_test() const;
private:
std::string host;
std::string password;
std::string get_upload_url(const std::string &filename) const;
std::string get_connect_url() const;
std::string get_base_url() const;
std::string timestamp_str() const;
bool connect(wxString &msg) const;
void disconnect() const;
bool start_print(wxString &msg, const std::string &filename) const;
int get_err_code_from_body(const std::string &body) const;
static wxString format_error(const std::string &body, const std::string &error, unsigned status);
};
}
#endif

View file

@ -0,0 +1,402 @@
#ifdef HAS_WIN10SDK
#ifndef NOMINMAX
# define NOMINMAX
#endif
#include "FixModelByWin10.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <condition_variable>
#include <exception>
#include <string>
#include <thread>
#include <boost/filesystem.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <roapi.h>
// for ComPtr
#include <wrl/client.h>
// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/
#include <winrt/robuffer.h>
#include <winrt/windows.storage.provider.h>
#include <winrt/windows.graphics.printing3d.h>
#include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "../GUI/GUI.hpp"
#include "../GUI/PresetBundle.hpp"
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
extern "C"{
// from rapi.h
typedef HRESULT (__stdcall* FunctionRoInitialize)(int);
typedef HRESULT (__stdcall* FunctionRoUninitialize)();
typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance);
typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory);
// from winstring.h
typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string);
typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string);
}
namespace Slic3r {
HMODULE s_hRuntimeObjectLibrary = nullptr;
FunctionRoInitialize s_RoInitialize = nullptr;
FunctionRoUninitialize s_RoUninitialize = nullptr;
FunctionRoActivateInstance s_RoActivateInstance = nullptr;
FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr;
FunctionWindowsCreateString s_WindowsCreateString = nullptr;
FunctionWindowsDelteString s_WindowsDeleteString = nullptr;
bool winrt_load_runtime_object_library()
{
if (s_hRuntimeObjectLibrary == nullptr)
s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll");
if (s_hRuntimeObjectLibrary != nullptr) {
s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize");
s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize");
s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance");
s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory");
s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString");
s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString");
}
return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString;
}
static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst)
{
HSTRING hClassName;
HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
if (S_OK != hr)
return hr;
hr = (*s_RoActivateInstance)(hClassName, pinst);
(*s_WindowsDeleteString)(hClassName);
return hr;
}
template<typename TYPE>
static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst)
{
IInspectable *pinspectable = nullptr;
HRESULT hr = winrt_activate_instance(class_name, &pinspectable);
if (S_OK != hr)
return hr;
hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst);
pinspectable->Release();
return hr;
}
static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst)
{
HSTRING hClassName;
HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
if (S_OK != hr)
return hr;
hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst);
(*s_WindowsDeleteString)(hClassName);
return hr;
}
template<typename TYPE>
static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst)
{
return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast<void**>(pinst));
}
// To be called often to test whether to cancel the operation.
typedef std::function<void ()> ThrowOnCancelFn;
template<typename T>
static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr<T> &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100)
{
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
asyncAction.As(&asyncInfo);
AsyncStatus status;
// Ugly blocking loop until the RepairAsync call finishes.
//FIXME replace with a callback.
// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage
for (;;) {
asyncInfo->get_Status(&status);
if (status != AsyncStatus::Started)
return status;
throw_on_cancel();
::Sleep(blocking_tick_ms);
}
}
static HRESULT winrt_open_file_stream(
const std::wstring &path,
ABI::Windows::Storage::FileAccessMode mode,
ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream,
ThrowOnCancelFn throw_on_cancel)
{
// Get the file factory.
Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFileStatics> fileFactory;
HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf());
if (FAILED(hr)) return hr;
// Open the file asynchronously.
HSTRING hstr_path;
hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path);
if (FAILED(hr)) return hr;
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile*>> fileOpenAsync;
hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf());
if (FAILED(hr)) return hr;
(*s_WindowsDeleteString)(hstr_path);
// Wait until the file gets open, get the actual file.
AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel);
Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storageFile;
if (status == AsyncStatus::Completed) {
hr = fileOpenAsync->GetResults(storageFile.GetAddressOf());
} else {
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
hr = fileOpenAsync.As(&asyncInfo);
if (FAILED(hr)) return hr;
HRESULT err;
hr = asyncInfo->get_ErrorCode(&err);
return FAILED(hr) ? hr : err;
}
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> fileStreamAsync;
hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf());
if (FAILED(hr)) return hr;
status = winrt_async_await(fileStreamAsync, throw_on_cancel);
if (status == AsyncStatus::Completed) {
hr = fileStreamAsync->GetResults(fileStream);
} else {
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
hr = fileStreamAsync.As(&asyncInfo);
if (FAILED(hr)) return hr;
HRESULT err;
hr = asyncInfo->get_ErrorCode(&err);
if (!FAILED(hr))
hr = err;
}
return hr;
}
bool is_windows10()
{
HKEY hKey;
LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey);
if (lRes == ERROR_SUCCESS) {
WCHAR szBuffer[512];
DWORD dwBufferSize = sizeof(szBuffer);
lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize);
if (lRes == ERROR_SUCCESS)
return wcsncmp(szBuffer, L"Windows 10", 10) == 0;
RegCloseKey(hKey);
}
return false;
}
// Progress function, to be called regularly to update the progress.
typedef std::function<void (const char * /* message */, unsigned /* progress */)> ProgressFn;
void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel)
{
if (! is_windows10())
throw std::runtime_error("fix_model_by_win10_sdk called on non Windows 10 system");
if (! winrt_load_runtime_object_library())
throw std::runtime_error("Failed to initialize the WinRT library.");
HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED);
{
on_progress(L("Exporting the source model"), 20);
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> fileStream;
hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel);
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3D3MFPackage> printing3d3mfpackage;
hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf());
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Graphics::Printing3D::Printing3DModel*>> modelAsync;
hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf());
AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel);
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3DModel> model;
if (status == AsyncStatus::Completed)
hr = modelAsync->GetResults(model.GetAddressOf());
else
throw std::runtime_error(L("Failed loading the input model."));
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::Graphics::Printing3D::Printing3DMesh*>> meshes;
hr = model->get_Meshes(meshes.GetAddressOf());
unsigned num_meshes = 0;
hr = meshes->get_Size(&num_meshes);
on_progress(L("Repairing the model by the Netfabb service"), 40);
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> repairAsync;
hr = model->RepairAsync(repairAsync.GetAddressOf());
status = winrt_async_await(repairAsync, throw_on_cancel);
if (status != AsyncStatus::Completed)
throw std::runtime_error(L("Mesh repair failed."));
repairAsync->GetResults();
on_progress(L("Loading the repaired model"), 60);
// Verify the number of meshes returned after the repair action.
meshes.Reset();
hr = model->get_Meshes(meshes.GetAddressOf());
hr = meshes->get_Size(&num_meshes);
// Save model to this class' Printing3D3MFPackage.
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> saveToPackageAsync;
hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf());
status = winrt_async_await(saveToPackageAsync, throw_on_cancel);
if (status != AsyncStatus::Completed)
throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
hr = saveToPackageAsync->GetResults();
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> generatorStreamAsync;
hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf());
status = winrt_async_await(generatorStreamAsync, throw_on_cancel);
if (status != AsyncStatus::Completed)
throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> generatorStream;
hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf());
// Go to the beginning of the stream.
generatorStream->Seek(0);
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IInputStream> inputStream;
hr = generatorStream.As(&inputStream);
// Get the buffer factory.
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf());
// Open the destination file.
FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb");
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
byte *buffer_ptr;
bufferFactory->Create(65536 * 2048, buffer.GetAddressOf());
{
Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
buffer.As(&bufferByteAccess);
hr = bufferByteAccess->Buffer(&buffer_ptr);
}
uint32_t length;
hr = buffer->get_Length(&length);
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead;
for (;;) {
hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf());
status = winrt_async_await(asyncRead, throw_on_cancel);
if (status != AsyncStatus::Completed)
throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
hr = buffer->get_Length(&length);
if (length == 0)
break;
fwrite(buffer_ptr, length, 1, fout);
}
fclose(fout);
// Here all the COM objects will be released through the ComPtr destructors.
}
(*s_RoUninitialize)();
}
class RepairCanceledException : public std::exception {
public:
const char* what() const throw() { return "Model repair has been canceled"; }
};
void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result)
{
std::mutex mutex;
std::condition_variable condition;
std::unique_lock<std::mutex> lock(mutex);
struct Progress {
std::string message;
int percent = 0;
bool updated = false;
} progress;
std::atomic<bool> canceled = false;
std::atomic<bool> finished = false;
// Open a progress dialog.
wxProgressDialog progress_dialog(
_(L("Model fixing")),
_(L("Exporting model...")),
100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
// Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
// (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
bool success = false;
auto on_progress = [&mutex, &condition, &progress](const char *msg, unsigned prcnt) {
std::lock_guard<std::mutex> lk(mutex);
progress.message = msg;
progress.percent = prcnt;
progress.updated = true;
condition.notify_all();
};
auto worker_thread = boost::thread([&model_object, &print, &result, on_progress, &success, &canceled, &finished]() {
try {
on_progress(L("Exporting the source model"), 0);
boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
path_src += ".3mf";
Model model;
model.add_object(model_object);
if (! Slic3r::store_3mf(path_src.string().c_str(), &model, const_cast<Print*>(&print), false)) {
boost::filesystem::remove(path_src);
throw std::runtime_error(L("Export of a temporary 3mf file failed"));
}
model.clear_objects();
model.clear_materials();
boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
path_dst += ".3mf";
fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress,
[&canceled]() { if (canceled) throw RepairCanceledException(); });
boost::filesystem::remove(path_src);
PresetBundle bundle;
on_progress(L("Loading the repaired model"), 80);
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &bundle, &result);
boost::filesystem::remove(path_dst);
if (! loaded)
throw std::runtime_error(L("Import of the repaired 3mf file failed"));
success = true;
finished = true;
on_progress(L("Model repair finished"), 100);
} catch (RepairCanceledException &ex) {
canceled = true;
finished = true;
on_progress(L("Model repair canceled"), 100);
} catch (std::exception &ex) {
success = false;
finished = true;
on_progress(ex.what(), 100);
}
});
while (! finished) {
condition.wait_for(lock, std::chrono::milliseconds(500), [&progress]{ return progress.updated; });
if (! progress_dialog.Update(progress.percent, _(progress.message)))
canceled = true;
progress.updated = false;
}
if (canceled) {
// Nothing to show.
} else if (success) {
wxMessageDialog dlg(nullptr, _(L("Model repaired successfully")), _(L("Model Repair by the Netfabb service")), wxICON_INFORMATION | wxOK_DEFAULT);
dlg.ShowModal();
} else {
wxMessageDialog dlg(nullptr, _(L("Model repair failed: \n")) + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT);
dlg.ShowModal();
}
worker_thread.join();
}
} // namespace Slic3r
#endif /* HAS_WIN10SDK */

View file

@ -0,0 +1,26 @@
#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_
#define slic3r_GUI_Utils_FixModelByWin10_hpp_
#include <string>
namespace Slic3r {
class Model;
class ModelObject;
class Print;
#ifdef HAS_WIN10SDK
extern bool is_windows10();
extern void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result);
#else /* HAS_WIN10SDK */
inline bool is_windows10() { return false; }
inline void fix_model_by_win10_sdk_gui(const ModelObject &, const Print &, Model &) {}
#endif /* HAS_WIN10SDK */
} // namespace Slic3r
#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */

View file

@ -0,0 +1,106 @@
#include "HexFile.hpp"
#include <sstream>
#include <boost/filesystem/fstream.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
namespace Utils {
static HexFile::DeviceKind parse_device_kind(const std::string &str)
{
if (str == "mk2") { return HexFile::DEV_MK2; }
else if (str == "mk3") { return HexFile::DEV_MK3; }
else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
else { return HexFile::DEV_GENERIC; }
}
static size_t hex_num_sections(fs::ifstream &file)
{
file.seekg(0);
if (! file.good()) {
return 0;
}
static const char *hex_terminator = ":00000001FF\r";
size_t res = 0;
std::string line;
while (getline(file, line, '\n').good()) {
// Account for LF vs CRLF
if (!line.empty() && line.back() != '\r') {
line.push_back('\r');
}
if (line == hex_terminator) {
res++;
}
}
return res;
}
HexFile::HexFile(fs::path path) :
path(std::move(path))
{
fs::ifstream file(this->path);
if (! file.good()) {
return;
}
std::string line;
std::stringstream header_ini;
while (std::getline(file, line, '\n').good()) {
if (line.empty()) {
continue;
}
// Account for LF vs CRLF
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (line.front() == ';') {
line.front() = ' ';
header_ini << line << std::endl;
} else if (line.front() == ':') {
break;
}
}
pt::ptree ptree;
try {
pt::read_ini(header_ini, ptree);
} catch (std::exception &e) {
return;
}
bool has_device_meta = false;
const auto device = ptree.find("device");
if (device != ptree.not_found()) {
this->device = parse_device_kind(device->second.data());
has_device_meta = true;
}
const auto model_id = ptree.find("model_id");
if (model_id != ptree.not_found()) {
this->model_id = model_id->second.data();
}
if (! has_device_meta) {
// No device metadata, look at the number of 'sections'
if (hex_num_sections(file) == 2) {
// Looks like a pre-metadata l10n firmware for the MK3, assume that's the case
this->device = DEV_MK3;
}
}
}
}
}

View file

@ -0,0 +1,33 @@
#ifndef slic3r_Hex_hpp_
#define slic3r_Hex_hpp_
#include <string>
#include <boost/filesystem/path.hpp>
namespace Slic3r {
namespace Utils {
struct HexFile
{
enum DeviceKind {
DEV_GENERIC,
DEV_MK2,
DEV_MK3,
DEV_MM_CONTROL,
};
boost::filesystem::path path;
DeviceKind device = DEV_GENERIC;
std::string model_id;
HexFile() {}
HexFile(boost::filesystem::path path);
};
}
}
#endif

451
src/slic3r/Utils/Http.cpp Normal file
View file

@ -0,0 +1,451 @@
#include "Http.hpp"
#include <cstdlib>
#include <functional>
#include <thread>
#include <deque>
#include <sstream>
#include <boost/filesystem/fstream.hpp>
#include <boost/format.hpp>
#include <curl/curl.h>
#include "../../libslic3r/libslic3r.h"
namespace fs = boost::filesystem;
namespace Slic3r {
// Private
class CurlGlobalInit
{
static const CurlGlobalInit instance;
CurlGlobalInit() { ::curl_global_init(CURL_GLOBAL_DEFAULT); }
~CurlGlobalInit() { ::curl_global_cleanup(); }
};
struct Http::priv
{
enum {
DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
};
::CURL *curl;
::curl_httppost *form;
::curl_httppost *form_end;
::curl_slist *headerlist;
// Used for reading the body
std::string buffer;
// Used for storing file streams added as multipart form parts
// Using a deque here because unlike vector it doesn't ivalidate pointers on insertion
std::deque<fs::ifstream> form_files;
std::string postfields;
size_t limit;
bool cancel;
std::thread io_thread;
Http::CompleteFn completefn;
Http::ErrorFn errorfn;
Http::ProgressFn progressfn;
priv(const std::string &url);
~priv();
static bool ca_file_supported(::CURL *curl);
static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow);
static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
void form_add_file(const char *name, const fs::path &path, const char* filename);
void set_post_body(const fs::path &path);
std::string curl_error(CURLcode curlcode);
std::string body_size_error();
void http_perform();
};
Http::priv::priv(const std::string &url) :
curl(::curl_easy_init()),
form(nullptr),
form_end(nullptr),
headerlist(nullptr),
limit(0),
cancel(false)
{
if (curl == nullptr) {
throw std::runtime_error(std::string("Could not construct Curl object"));
}
::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION);
}
Http::priv::~priv()
{
::curl_easy_cleanup(curl);
::curl_formfree(form);
::curl_slist_free_all(headerlist);
}
bool Http::priv::ca_file_supported(::CURL *curl)
{
#ifdef _WIN32
bool res = false;
#else
bool res = true;
#endif
if (curl == nullptr) { return res; }
#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 48
::curl_tlssessioninfo *tls;
if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) {
if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) {
// With Windows and OS X native SSL support, cert files cannot be set
res = false;
}
}
#endif
return res;
}
size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
{
auto self = static_cast<priv*>(userp);
const char *cdata = static_cast<char*>(data);
const size_t realsize = size * nmemb;
const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
if (self->buffer.size() + realsize > limit) {
// This makes curl_easy_perform return CURLE_WRITE_ERROR
return 0;
}
self->buffer.append(cdata, realsize);
return realsize;
}
int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
auto self = static_cast<priv*>(userp);
bool cb_cancel = false;
if (self->progressfn) {
Progress progress(dltotal, dlnow, ultotal, ulnow);
self->progressfn(progress, cb_cancel);
}
return self->cancel || cb_cancel;
}
int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow)
{
return xfercb(userp, dltotal, dlnow, ultotal, ulnow);
}
size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp)
{
auto stream = reinterpret_cast<fs::ifstream*>(userp);
try {
stream->read(buffer, size * nitems);
} catch (...) {
return CURL_READFUNC_ABORT;
}
return stream->gcount();
}
void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename)
{
// We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
// and so we use CURLFORM_STREAM with boost ifstream to read the file.
if (filename == nullptr) {
filename = path.string().c_str();
}
form_files.emplace_back(path, std::ios::in | std::ios::binary);
auto &stream = form_files.back();
stream.seekg(0, std::ios::end);
size_t size = stream.tellg();
stream.seekg(0);
if (filename != nullptr) {
::curl_formadd(&form, &form_end,
CURLFORM_COPYNAME, name,
CURLFORM_FILENAME, filename,
CURLFORM_CONTENTTYPE, "application/octet-stream",
CURLFORM_STREAM, static_cast<void*>(&stream),
CURLFORM_CONTENTSLENGTH, static_cast<long>(size),
CURLFORM_END
);
}
}
void Http::priv::set_post_body(const fs::path &path)
{
std::ifstream file(path.string());
std::string file_content { std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
postfields = file_content;
}
std::string Http::priv::curl_error(CURLcode curlcode)
{
return (boost::format("%1% (%2%)")
% ::curl_easy_strerror(curlcode)
% curlcode
).str();
}
std::string Http::priv::body_size_error()
{
return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
}
void Http::priv::http_perform()
{
::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this));
::curl_easy_setopt(curl, CURLOPT_READFUNCTION, form_file_read_cb);
::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
::curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfercb);
::curl_easy_setopt(curl, CURLOPT_XFERINFODATA, static_cast<void*>(this));
(void)xfercb_legacy; // prevent unused function warning
#else
::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, xfercb);
::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast<void*>(this));
#endif
#ifndef NDEBUG
::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
#endif
if (headerlist != nullptr) {
::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
}
if (form != nullptr) {
::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
}
if (!postfields.empty()) {
::curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields.c_str());
::curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, postfields.size());
}
CURLcode res = ::curl_easy_perform(curl);
if (res != CURLE_OK) {
if (res == CURLE_ABORTED_BY_CALLBACK) {
if (cancel) {
// The abort comes from the request being cancelled programatically
Progress dummyprogress(0, 0, 0, 0);
bool cancel = true;
if (progressfn) { progressfn(dummyprogress, cancel); }
} else {
// The abort comes from the CURLOPT_READFUNCTION callback, which means reading file failed
if (errorfn) { errorfn(std::move(buffer), "Error reading file for file upload", 0); }
}
}
else if (res == CURLE_WRITE_ERROR) {
if (errorfn) { errorfn(std::move(buffer), body_size_error(), 0); }
} else {
if (errorfn) { errorfn(std::move(buffer), curl_error(res), 0); }
};
} else {
long http_status = 0;
::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
if (http_status >= 400) {
if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); }
} else {
if (completefn) { completefn(std::move(buffer), http_status); }
}
}
}
Http::Http(const std::string &url) : p(new priv(url)) {}
// Public
Http::Http(Http &&other) : p(std::move(other.p)) {}
Http::~Http()
{
if (p && p->io_thread.joinable()) {
p->io_thread.detach();
}
}
Http& Http::size_limit(size_t sizeLimit)
{
if (p) { p->limit = sizeLimit; }
return *this;
}
Http& Http::header(std::string name, const std::string &value)
{
if (!p) { return * this; }
if (name.size() > 0) {
name.append(": ").append(value);
} else {
name.push_back(':');
}
p->headerlist = curl_slist_append(p->headerlist, name.c_str());
return *this;
}
Http& Http::remove_header(std::string name)
{
if (p) {
name.push_back(':');
p->headerlist = curl_slist_append(p->headerlist, name.c_str());
}
return *this;
}
Http& Http::ca_file(const std::string &name)
{
if (p && priv::ca_file_supported(p->curl)) {
::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
}
return *this;
}
Http& Http::form_add(const std::string &name, const std::string &contents)
{
if (p) {
::curl_formadd(&p->form, &p->form_end,
CURLFORM_COPYNAME, name.c_str(),
CURLFORM_COPYCONTENTS, contents.c_str(),
CURLFORM_END
);
}
return *this;
}
Http& Http::form_add_file(const std::string &name, const fs::path &path)
{
if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); }
return *this;
}
Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename)
{
if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); }
return *this;
}
Http& Http::set_post_body(const fs::path &path)
{
if (p) { p->set_post_body(path);}
return *this;
}
Http& Http::on_complete(CompleteFn fn)
{
if (p) { p->completefn = std::move(fn); }
return *this;
}
Http& Http::on_error(ErrorFn fn)
{
if (p) { p->errorfn = std::move(fn); }
return *this;
}
Http& Http::on_progress(ProgressFn fn)
{
if (p) { p->progressfn = std::move(fn); }
return *this;
}
Http::Ptr Http::perform()
{
auto self = std::make_shared<Http>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self](){
self->p->http_perform();
});
self->p->io_thread = std::move(io_thread);
}
return self;
}
void Http::perform_sync()
{
if (p) { p->http_perform(); }
}
void Http::cancel()
{
if (p) { p->cancel = true; }
}
Http Http::get(std::string url)
{
return std::move(Http{std::move(url)});
}
Http Http::post(std::string url)
{
Http http{std::move(url)};
curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
return http;
}
bool Http::ca_file_supported()
{
::CURL *curl = ::curl_easy_init();
bool res = priv::ca_file_supported(curl);
if (curl != nullptr) { ::curl_easy_cleanup(curl); }
return res;
}
std::string Http::url_encode(const std::string &str)
{
::CURL *curl = ::curl_easy_init();
if (curl == nullptr) {
return str;
}
char *ce = ::curl_easy_escape(curl, str.c_str(), str.length());
std::string encoded = std::string(ce);
::curl_free(ce);
::curl_easy_cleanup(curl);
return encoded;
}
std::ostream& operator<<(std::ostream &os, const Http::Progress &progress)
{
os << "Http::Progress("
<< "dltotal = " << progress.dltotal
<< ", dlnow = " << progress.dlnow
<< ", ultotal = " << progress.ultotal
<< ", ulnow = " << progress.ulnow
<< ")";
return os;
}
}

115
src/slic3r/Utils/Http.hpp Normal file
View file

@ -0,0 +1,115 @@
#ifndef slic3r_Http_hpp_
#define slic3r_Http_hpp_
#include <memory>
#include <string>
#include <functional>
#include <boost/filesystem/path.hpp>
namespace Slic3r {
/// Represetns a Http request
class Http : public std::enable_shared_from_this<Http> {
private:
struct priv;
public:
struct Progress
{
size_t dltotal; // Total bytes to download
size_t dlnow; // Bytes downloaded so far
size_t ultotal; // Total bytes to upload
size_t ulnow; // Bytes uploaded so far
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) :
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow)
{}
};
typedef std::shared_ptr<Http> Ptr;
typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
// A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...).
// If the HTTP request could not be made or failed before completion, the `error` arg contains a description
// of the error and `http_status` is zero.
// If the HTTP request was completed but the response HTTP code is >= 400, `error` is empty and `http_status` contains the response code.
// In either case there may or may not be a body.
typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn;
// See the Progress struct above.
// Writing true to the `cancel` reference cancels the request in progress.
typedef std::function<void(Progress, bool& /* cancel */)> ProgressFn;
Http(Http &&other);
// Note: strings are expected to be UTF-8-encoded
// These are the primary constructors that create a HTTP object
// for a GET and a POST request respectively.
static Http get(std::string url);
static Http post(std::string url);
~Http();
Http(const Http &) = delete;
Http& operator=(const Http &) = delete;
Http& operator=(Http &&) = delete;
// Sets a maximum size of the data that can be received.
// A value of zero sets the default limit, which is is 5MB.
Http& size_limit(size_t sizeLimit);
// Sets a HTTP header field.
Http& header(std::string name, const std::string &value);
// Removes a header field.
Http& remove_header(std::string name);
// Sets a CA certificate file for usage with HTTPS. This is only supported on some backends,
// specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store.
// See also ca_file_supported().
Http& ca_file(const std::string &filename);
// Add a HTTP multipart form field
Http& form_add(const std::string &name, const std::string &contents);
// Add a HTTP multipart form file data contents, `name` is the name of the part
Http& form_add_file(const std::string &name, const boost::filesystem::path &path);
// Same as above except also override the file's filename with a custom one
Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
// Set the file contents as a POST request body.
// The data is used verbatim, it is not additionally encoded in any way.
// This can be used for hosts which do not support multipart requests.
Http& set_post_body(const boost::filesystem::path &path);
// Callback called on HTTP request complete
Http& on_complete(CompleteFn fn);
// Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup,
// TCP connection, HTTP transfer, and finally also when the response indicates an error (status >= 400).
// Therefore, a response body may or may not be present.
Http& on_error(ErrorFn fn);
// Callback called on data download/upload prorgess (called fairly frequently).
// See the `Progress` structure for description of the data passed.
// Writing a true-ish value into the cancel reference parameter cancels the request.
Http& on_progress(ProgressFn fn);
// Starts performing the request in a background thread
Ptr perform();
// Starts performing the request on the current thread
void perform_sync();
// Cancels a request in progress
void cancel();
// Tells whether current backend supports seting up a CA file using ca_file()
static bool ca_file_supported();
// converts the given string to an url_encoded_string
static std::string url_encode(const std::string &str);
private:
Http(const std::string &url);
std::unique_ptr<priv> p;
};
std::ostream& operator<<(std::ostream &, const Http::Progress &);
}
#endif

View file

@ -0,0 +1,173 @@
#include "OctoPrint.hpp"
#include "PrintHostSendDialog.hpp"
#include <algorithm>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include "libslic3r/PrintConfig.hpp"
#include "Http.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
host(config->opt_string("print_host")),
apikey(config->opt_string("printhost_apikey")),
cafile(config->opt_string("printhost_cafile"))
{}
OctoPrint::~OctoPrint() {}
bool OctoPrint::test(wxString &msg) const
{
// Since the request is performed synchronously here,
// it is ok to refer to `msg` from within the closure
bool res = true;
auto url = make_url("api/version");
BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url;
auto http = Http::get(std::move(url));
set_auth(http);
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1%, HTTP %2%, body: `%3%`") % error % status % body;
res = false;
msg = format_error(body, error, status);
})
.on_complete([&](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: Got version: %1%") % body;
})
.perform_sync();
return res;
}
wxString OctoPrint::get_test_ok_msg () const
{
return wxString::Format("%s", _(L("Connection to OctoPrint works correctly.")));
}
wxString OctoPrint::get_test_failed_msg (wxString &msg) const
{
return wxString::Format("%s: %s\n\n%s",
_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required.")));
}
bool OctoPrint::send_gcode(const std::string &filename) const
{
enum { PROGRESS_RANGE = 1000 };
const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
fs::path filepath(filename);
PrintHostSendDialog send_dialog(filepath.filename(), true);
if (send_dialog.ShowModal() != wxID_OK) { return false; }
const bool print = send_dialog.print();
const auto upload_filepath = send_dialog.filename();
const auto upload_filename = upload_filepath.filename();
const auto upload_parent_path = upload_filepath.parent_path();
wxProgressDialog progress_dialog(
_(L("OctoPrint upload")),
_(L("Sending G-code file to the OctoPrint server...")),
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
progress_dialog.Pulse();
wxString test_msg;
if (!test(test_msg)) {
auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
GUI::show_error(&progress_dialog, std::move(errormsg));
return false;
}
bool res = true;
auto url = make_url("api/files/local");
BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%")
% filepath.string()
% url
% upload_filename.string()
% upload_parent_path.string()
% print;
auto http = Http::post(std::move(url));
set_auth(http);
http.form_add("print", print ? "true" : "false")
.form_add("path", upload_parent_path.string())
.form_add_file("file", filename, upload_filename.string())
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body;
progress_dialog.Update(PROGRESS_RANGE);
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
GUI::show_error(&progress_dialog, std::move(errormsg));
res = false;
})
.on_progress([&](Http::Progress progress, bool &cancel) {
if (cancel) {
// Upload was canceled
res = false;
} else if (progress.ultotal > 0) {
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
} else {
cancel = !progress_dialog.Pulse();
}
})
.perform_sync();
return res;
}
bool OctoPrint::has_auto_discovery() const
{
return true;
}
bool OctoPrint::can_test() const
{
return true;
}
void OctoPrint::set_auth(Http &http) const
{
http.header("X-Api-Key", apikey);
if (! cafile.empty()) {
http.ca_file(cafile);
}
}
std::string OctoPrint::make_url(const std::string &path) const
{
if (host.find("http://") == 0 || host.find("https://") == 0) {
if (host.back() == '/') {
return (boost::format("%1%%2%") % host % path).str();
} else {
return (boost::format("%1%/%2%") % host % path).str();
}
} else {
return (boost::format("http://%1%/%2%") % host % path).str();
}
}
wxString OctoPrint::format_error(const std::string &body, const std::string &error, unsigned status)
{
if (status != 0) {
auto wxbody = wxString::FromUTF8(body.data());
return wxString::Format("HTTP %u: %s", status, wxbody);
} else {
return wxString::FromUTF8(error.data());
}
}
}

View file

@ -0,0 +1,42 @@
#ifndef slic3r_OctoPrint_hpp_
#define slic3r_OctoPrint_hpp_
#include <string>
#include <wx/string.h>
#include "PrintHost.hpp"
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class OctoPrint : public PrintHost
{
public:
OctoPrint(DynamicPrintConfig *config);
virtual ~OctoPrint();
bool test(wxString &curl_msg) const;
wxString get_test_ok_msg () const;
wxString get_test_failed_msg (wxString &msg) const;
// Send gcode file to octoprint, filename is expected to be in UTF-8
bool send_gcode(const std::string &filename) const;
bool has_auto_discovery() const;
bool can_test() const;
private:
std::string host;
std::string apikey;
std::string cafile;
void set_auth(Http &http) const;
std::string make_url(const std::string &path) const;
static wxString format_error(const std::string &body, const std::string &error, unsigned status);
};
}
#endif

View file

@ -0,0 +1,633 @@
#include "PresetUpdater.hpp"
#include <algorithm>
#include <thread>
#include <unordered_map>
#include <ostream>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <wx/app.h>
#include <wx/event.h>
#include <wx/msgdlg.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "slic3r/GUI/UpdateDialogs.hpp"
#include "slic3r/GUI/ConfigWizard.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Config/Version.hpp"
#include "slic3r/Config/Snapshot.hpp"
namespace fs = boost::filesystem;
using Slic3r::GUI::Config::Index;
using Slic3r::GUI::Config::Version;
using Slic3r::GUI::Config::Snapshot;
using Slic3r::GUI::Config::SnapshotDB;
namespace Slic3r {
enum {
SLIC3R_VERSION_BODY_MAX = 256,
};
static const char *INDEX_FILENAME = "index.idx";
static const char *TMP_EXTENSION = ".download";
struct Update
{
fs::path source;
fs::path target;
Version version;
Update(fs::path &&source, fs::path &&target, const Version &version) :
source(std::move(source)),
target(std::move(target)),
version(version)
{}
std::string name() const { return source.stem().string(); }
friend std::ostream& operator<<(std::ostream& os , const Update &self) {
os << "Update(" << self.source.string() << " -> " << self.target.string() << ')';
return os;
}
};
struct Incompat
{
fs::path bundle;
Version version;
Incompat(fs::path &&bundle, const Version &version) :
bundle(std::move(bundle)),
version(version)
{}
std::string name() const { return bundle.stem().string(); }
friend std::ostream& operator<<(std::ostream& os , const Incompat &self) {
os << "Incompat(" << self.bundle.string() << ')';
return os;
}
};
struct Updates
{
std::vector<Incompat> incompats;
std::vector<Update> updates;
};
struct PresetUpdater::priv
{
int version_online_event;
std::vector<Index> index_db;
bool enabled_version_check;
bool enabled_config_update;
std::string version_check_url;
bool had_config_update;
fs::path cache_path;
fs::path rsrc_path;
fs::path vendor_path;
bool cancel;
std::thread thread;
priv(int version_online_event);
void set_download_prefs(AppConfig *app_config);
bool get_file(const std::string &url, const fs::path &target_path) const;
void prune_tmps() const;
void sync_version() const;
void sync_config(const std::set<VendorProfile> vendors) const;
void check_install_indices() const;
Updates get_config_updates() const;
void perform_updates(Updates &&updates, bool snapshot = true) const;
static void copy_file(const fs::path &from, const fs::path &to);
};
PresetUpdater::priv::priv(int version_online_event) :
version_online_event(version_online_event),
had_config_update(false),
cache_path(fs::path(Slic3r::data_dir()) / "cache"),
rsrc_path(fs::path(resources_dir()) / "profiles"),
vendor_path(fs::path(Slic3r::data_dir()) / "vendor"),
cancel(false)
{
set_download_prefs(GUI::get_app_config());
check_install_indices();
index_db = std::move(Index::load_db());
}
// Pull relevant preferences from AppConfig
void PresetUpdater::priv::set_download_prefs(AppConfig *app_config)
{
enabled_version_check = app_config->get("version_check") == "1";
version_check_url = app_config->version_check_url();
enabled_config_update = app_config->get("preset_update") == "1" && !app_config->legacy_datadir();
}
// Downloads a file (http get operation). Cancels if the Updater is being destroyed.
bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &target_path) const
{
bool res = false;
fs::path tmp_path = target_path;
tmp_path += (boost::format(".%1%%2%") % get_current_pid() % TMP_EXTENSION).str();
BOOST_LOG_TRIVIAL(info) << boost::format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`")
% url
% target_path.string()
% tmp_path.string();
Http::get(url)
.on_progress([this](Http::Progress, bool &cancel) {
if (cancel) { cancel = true; }
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%")
% url
% http_status
% error;
})
.on_complete([&](std::string body, unsigned /* http_status */) {
fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
file.write(body.c_str(), body.size());
file.close();
fs::rename(tmp_path, target_path);
res = true;
})
.perform_sync();
return res;
}
// Remove leftover paritally downloaded files, if any.
void PresetUpdater::priv::prune_tmps() const
{
for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == TMP_EXTENSION) {
BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << it->path().string();
fs::remove(it->path());
}
}
}
// Get Slic3rPE version available online, save in AppConfig.
void PresetUpdater::priv::sync_version() const
{
if (! enabled_version_check) { return; }
BOOST_LOG_TRIVIAL(info) << boost::format("Downloading Slic3rPE online version from: `%1%`") % version_check_url;
Http::get(version_check_url)
.size_limit(SLIC3R_VERSION_BODY_MAX)
.on_progress([this](Http::Progress, bool &cancel) {
cancel = this->cancel;
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%")
% version_check_url
% http_status
% error;
})
.on_complete([&](std::string body, unsigned /* http_status */) {
boost::trim(body);
BOOST_LOG_TRIVIAL(info) << boost::format("Got Slic3rPE online version: `%1%`. Sending to GUI thread...") % body;
wxCommandEvent* evt = new wxCommandEvent(version_online_event);
evt->SetString(body);
GUI::get_app()->QueueEvent(evt);
})
.perform_sync();
}
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
// Both are saved in cache.
void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors) const
{
BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache";
if (!enabled_config_update) { return; }
// Donwload vendor preset bundles
for (const auto &index : index_db) {
if (cancel) { return; }
const auto vendor_it = vendors.find(VendorProfile(index.vendor()));
if (vendor_it == vendors.end()) {
BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor();
continue;
}
const VendorProfile &vendor = *vendor_it;
if (vendor.config_update_url.empty()) {
BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name;
continue;
}
// Download a fresh index
BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name;
const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME;
const auto idx_path = cache_path / (vendor.id + ".idx");
if (! get_file(idx_url, idx_path)) { continue; }
if (cancel) { return; }
// Load the fresh index up
Index new_index;
new_index.load(idx_path);
// See if a there's a new version to download
const auto recommended_it = new_index.recommended();
if (recommended_it == new_index.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % vendor.name;
continue;
}
const auto recommended = recommended_it->config_version;
BOOST_LOG_TRIVIAL(debug) << boost::format("Got index for vendor: %1%: current version: %2%, recommended version: %3%")
% vendor.name
% vendor.config_version.to_string()
% recommended.to_string();
if (vendor.config_version >= recommended) { continue; }
// Download a fresh bundle
BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name;
const auto bundle_url = (boost::format("%1%/%2%.ini") % vendor.config_update_url % recommended.to_string()).str();
const auto bundle_path = cache_path / (vendor.id + ".ini");
if (! get_file(bundle_url, bundle_path)) { continue; }
if (cancel) { return; }
}
}
// Install indicies from resources. Only installs those that are either missing or older than in resources.
void PresetUpdater::priv::check_install_indices() const
{
BOOST_LOG_TRIVIAL(info) << "Checking if indices need to be installed from resources...";
for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) {
const auto &path = it->path();
if (path.extension() == ".idx") {
const auto path_in_cache = cache_path / path.filename();
if (! fs::exists(path_in_cache)) {
BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename();
copy_file(path, path_in_cache);
} else {
Index idx_rsrc, idx_cache;
idx_rsrc.load(path);
idx_cache.load(path_in_cache);
if (idx_cache.version() < idx_rsrc.version()) {
BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename();
copy_file(path, path_in_cache);
}
}
}
}
}
// Generates a list of bundle updates that are to be performed
Updates PresetUpdater::priv::get_config_updates() const
{
Updates updates;
BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates...";
for (const auto idx : index_db) {
auto bundle_path = vendor_path / (idx.vendor() + ".ini");
if (! fs::exists(bundle_path)) {
BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor();
continue;
}
// Perform a basic load and check the version
const auto vp = VendorProfile::from_ini(bundle_path, false);
const auto ver_current = idx.find(vp.config_version);
if (ver_current == idx.end()) {
auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str();
BOOST_LOG_TRIVIAL(error) << message;
throw std::runtime_error(message);
}
// Getting a recommended version from the latest index, wich may have been downloaded
// from the internet, or installed / updated from the installation resources.
const auto recommended = idx.recommended();
if (recommended == idx.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor();
}
BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%, version cached: %3%")
% vp.name
% ver_current->config_version.to_string()
% recommended->config_version.to_string();
if (! ver_current->is_current_slic3r_supported()) {
BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string();
updates.incompats.emplace_back(std::move(bundle_path), *ver_current);
} else if (recommended->config_version > ver_current->config_version) {
// Config bundle update situation
// Check if the update is already present in a snapshot
const auto recommended_snap = SnapshotDB::singleton().snapshot_with_vendor_preset(vp.name, recommended->config_version);
if (recommended_snap != SnapshotDB::singleton().end()) {
BOOST_LOG_TRIVIAL(info) << boost::format("Bundle update %1% %2% already found in snapshot %3%, skipping...")
% vp.name
% recommended->config_version.to_string()
% recommended_snap->id;
continue;
}
auto path_src = cache_path / (idx.vendor() + ".ini");
auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini");
if (! fs::exists(path_src)) {
if (! fs::exists(path_in_rsrc)) {
BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update, but bundle found in neither cache nor resources")
% idx.vendor();
continue;
} else {
path_src = std::move(path_in_rsrc);
path_in_rsrc.clear();
}
}
auto new_vp = VendorProfile::from_ini(path_src, false);
bool found = false;
if (new_vp.config_version == recommended->config_version) {
updates.updates.emplace_back(std::move(path_src), std::move(bundle_path), *recommended);
found = true;
} else if (! path_in_rsrc.empty() && fs::exists(path_in_rsrc)) {
new_vp = VendorProfile::from_ini(path_in_rsrc, false);
if (new_vp.config_version == recommended->config_version) {
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(bundle_path), *recommended);
found = true;
}
}
if (! found)
BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources")
% idx.vendor()
% recommended->config_version.to_string();
}
}
return updates;
}
void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
{
if (updates.incompats.size() > 0) {
if (snapshot) {
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_DOWNGRADE);
}
BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% incompatible bundles") % updates.incompats.size();
for (const auto &incompat : updates.incompats) {
BOOST_LOG_TRIVIAL(info) << '\t' << incompat;
fs::remove(incompat.bundle);
}
}
else if (updates.updates.size() > 0) {
if (snapshot) {
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE);
}
BOOST_LOG_TRIVIAL(info) << boost::format("Performing %1% updates") % updates.updates.size();
for (const auto &update : updates.updates) {
BOOST_LOG_TRIVIAL(info) << '\t' << update;
copy_file(update.source, update.target);
PresetBundle bundle;
bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% conflicting presets")
% (bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
auto preset_remover = [](const Preset &preset) {
BOOST_LOG_TRIVIAL(info) << '\t' << preset.file;
fs::remove(preset.file);
};
for (const auto &preset : bundle.prints) { preset_remover(preset); }
for (const auto &preset : bundle.filaments) { preset_remover(preset); }
for (const auto &preset : bundle.printers) { preset_remover(preset); }
// Also apply the `obsolete_presets` property, removing obsolete ini files
BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% obsolete presets")
% (bundle.obsolete_presets.prints.size() + bundle.obsolete_presets.filaments.size() + bundle.obsolete_presets.printers.size());
auto obsolete_remover = [](const char *subdir, const std::string &preset) {
auto path = fs::path(Slic3r::data_dir()) / subdir / preset;
path += ".ini";
BOOST_LOG_TRIVIAL(info) << '\t' << path.string();
fs::remove(path);
};
for (const auto &name : bundle.obsolete_presets.prints) { obsolete_remover("print", name); }
for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("filament", name); }
for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("sla_material", name); }
for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); }
}
}
}
void PresetUpdater::priv::copy_file(const fs::path &source, const fs::path &target)
{
static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644
// Make sure the file has correct permission both before and after we copy over it
if (fs::exists(target)) {
fs::permissions(target, perms);
}
fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
fs::permissions(target, perms);
}
PresetUpdater::PresetUpdater(int version_online_event) :
p(new priv(version_online_event))
{}
// Public
PresetUpdater::~PresetUpdater()
{
if (p && p->thread.joinable()) {
// This will stop transfers being done by the thread, if any.
// Cancelling takes some time, but should complete soon enough.
p->cancel = true;
p->thread.join();
}
}
void PresetUpdater::sync(PresetBundle *preset_bundle)
{
p->set_download_prefs(GUI::get_app_config());
if (!p->enabled_version_check && !p->enabled_config_update) { return; }
// Copy the whole vendors data for use in the background thread
// Unfortunatelly as of C++11, it needs to be copied again
// into the closure (but perhaps the compiler can elide this).
std::set<VendorProfile> vendors = preset_bundle->vendors;
p->thread = std::move(std::thread([this, vendors]() {
this->p->prune_tmps();
this->p->sync_version();
this->p->sync_config(std::move(vendors));
}));
}
void PresetUpdater::slic3r_update_notify()
{
if (! p->enabled_version_check) { return; }
if (p->had_config_update) {
BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed";
return;
}
auto* app_config = GUI::get_app_config();
const auto ver_slic3r = Semver::parse(SLIC3R_VERSION);
const auto ver_online_str = app_config->get("version_online");
const auto ver_online = Semver::parse(ver_online_str);
const auto ver_online_seen = Semver::parse(app_config->get("version_online_seen"));
if (! ver_slic3r) {
throw std::runtime_error("Could not parse Slic3r version string: " SLIC3R_VERSION);
}
if (ver_online) {
// Only display the notification if the version available online is newer AND if we haven't seen it before
if (*ver_online > *ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) {
GUI::MsgUpdateSlic3r notification(*ver_slic3r, *ver_online);
notification.ShowModal();
if (notification.disable_version_check()) {
app_config->set("version_check", "0");
p->enabled_version_check = false;
}
}
app_config->set("version_online_seen", ver_online_str);
}
}
bool PresetUpdater::config_update() const
{
if (! p->enabled_config_update) { return true; }
auto updates = p->get_config_updates();
if (updates.incompats.size() > 0) {
BOOST_LOG_TRIVIAL(info) << boost::format("%1% bundles incompatible. Asking for action...") % updates.incompats.size();
std::unordered_map<std::string, wxString> incompats_map;
for (const auto &incompat : updates.incompats) {
auto vendor = incompat.name();
const auto min_slic3r = incompat.version.min_slic3r_version;
const auto max_slic3r = incompat.version.max_slic3r_version;
wxString restrictions;
if (min_slic3r != Semver::zero() && max_slic3r != Semver::inf()) {
restrictions = wxString::Format(_(L("requires min. %s and max. %s")),
min_slic3r.to_string(),
max_slic3r.to_string()
);
} else if (min_slic3r != Semver::zero()) {
restrictions = wxString::Format(_(L("requires min. %s")), min_slic3r.to_string());
} else {
restrictions = wxString::Format(_(L("requires max. %s")), max_slic3r.to_string());
}
incompats_map.emplace(std::make_pair(std::move(vendor), std::move(restrictions)));
}
p->had_config_update = true; // This needs to be done before a dialog is shown because of OnIdle() + CallAfter() in Perl
GUI::MsgDataIncompatible dlg(std::move(incompats_map));
const auto res = dlg.ShowModal();
if (res == wxID_REPLACE) {
BOOST_LOG_TRIVIAL(info) << "User wants to re-configure...";
p->perform_updates(std::move(updates));
GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT);
if (! wizard.run(GUI::get_preset_bundle(), this)) {
return false;
}
GUI::load_current_presets();
} else {
BOOST_LOG_TRIVIAL(info) << "User wants to exit Slic3r, bye...";
return false;
}
}
else if (updates.updates.size() > 0) {
BOOST_LOG_TRIVIAL(info) << boost::format("Update of %1% bundles available. Asking for confirmation ...") % updates.updates.size();
std::unordered_map<std::string, std::string> updates_map;
for (const auto &update : updates.updates) {
auto vendor = update.name();
auto ver_str = update.version.config_version.to_string();
if (! update.version.comment.empty()) {
ver_str += std::string(" (") + update.version.comment + ")";
}
updates_map.emplace(std::make_pair(std::move(vendor), std::move(ver_str)));
}
p->had_config_update = true; // Ditto, see above
GUI::MsgUpdateConfig dlg(std::move(updates_map));
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(updates));
// Reload global configuration
auto *app_config = GUI::get_app_config();
GUI::get_preset_bundle()->load_presets(*app_config);
GUI::load_current_presets();
} else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
}
} else {
BOOST_LOG_TRIVIAL(info) << "No configuration updates available.";
}
return true;
}
void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
{
Updates updates;
BOOST_LOG_TRIVIAL(info) << boost::format("Installing %1% bundles from resources ...") % bundles.size();
for (const auto &bundle : bundles) {
auto path_in_rsrc = p->rsrc_path / bundle;
auto path_in_vendors = p->vendor_path / bundle;
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version());
}
p->perform_updates(std::move(updates), snapshot);
}
}

View file

@ -0,0 +1,42 @@
#ifndef slic3r_PresetUpdate_hpp_
#define slic3r_PresetUpdate_hpp_
#include <memory>
#include <vector>
namespace Slic3r {
class AppConfig;
class PresetBundle;
class PresetUpdater
{
public:
PresetUpdater(int version_online_event);
PresetUpdater(PresetUpdater &&) = delete;
PresetUpdater(const PresetUpdater &) = delete;
PresetUpdater &operator=(PresetUpdater &&) = delete;
PresetUpdater &operator=(const PresetUpdater &) = delete;
~PresetUpdater();
// If either version check or config updating is enabled, get the appropriate data in the background and cache it.
void sync(PresetBundle *preset_bundle);
// If version check is enabled, check if chaced online slic3r version is newer, notify if so.
void slic3r_update_notify();
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
// A false return value implies Slic3r should exit due to incompatibility of configuration.
bool config_update() const;
// "Update" a list of bundles from resources (behaves like an online update).
void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
private:
struct priv;
std::unique_ptr<priv> p;
};
}
#endif

Some files were not shown because too many files have changed in this diff Show more