mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-27 10:41:15 -06:00
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:
parent
3ddaccb641
commit
0558b53493
1706 changed files with 7413 additions and 7638 deletions
167
src/slic3r/AppController.cpp
Normal file
167
src/slic3r/AppController.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
263
src/slic3r/AppController.hpp
Normal file
263
src/slic3r/AppController.hpp
Normal 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
|
||||
302
src/slic3r/AppControllerWx.cpp
Normal file
302
src/slic3r/AppControllerWx.cpp
Normal 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
106
src/slic3r/CMakeLists.txt
Normal 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)
|
||||
532
src/slic3r/Config/Snapshot.cpp
Normal file
532
src/slic3r/Config/Snapshot.cpp
Normal 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 §ion : 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
|
||||
129
src/slic3r/Config/Snapshot.hpp
Normal file
129
src/slic3r/Config/Snapshot.hpp
Normal 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_ */
|
||||
319
src/slic3r/Config/Version.cpp
Normal file
319
src/slic3r/Config/Version.cpp
Normal 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
|
||||
88
src/slic3r/Config/Version.hpp
Normal file
88
src/slic3r/Config/Version.hpp
Normal 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
183
src/slic3r/GUI/2DBed.cpp
Normal 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
52
src/slic3r/GUI/2DBed.hpp
Normal 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
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
603
src/slic3r/GUI/3DScene.hpp
Normal 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
|
||||
136
src/slic3r/GUI/AboutDialog.cpp
Normal file
136
src/slic3r/GUI/AboutDialog.cpp
Normal 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 © 2016-2018 Prusa Research. <br />"
|
||||
"Copyright © 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
|
||||
36
src/slic3r/GUI/AboutDialog.hpp
Normal file
36
src/slic3r/GUI/AboutDialog.hpp
Normal 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
|
||||
266
src/slic3r/GUI/AppConfig.cpp
Normal file
266
src/slic3r/GUI/AppConfig.cpp
Normal 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 §ion : 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
|
||||
140
src/slic3r/GUI/AppConfig.hpp
Normal file
140
src/slic3r/GUI/AppConfig.hpp
Normal 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 §ion, 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 §ion, 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 §ion, 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 §ion, 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 §ion, 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 §ion)
|
||||
{ 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_ */
|
||||
167
src/slic3r/GUI/BackgroundSlicingProcess.cpp
Normal file
167
src/slic3r/GUI/BackgroundSlicingProcess.cpp
Normal 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
|
||||
91
src/slic3r/GUI/BackgroundSlicingProcess.hpp
Normal file
91
src/slic3r/GUI/BackgroundSlicingProcess.hpp
Normal 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_ */
|
||||
343
src/slic3r/GUI/BedShapeDialog.cpp
Normal file
343
src/slic3r/GUI/BedShapeDialog.cpp
Normal 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
|
||||
56
src/slic3r/GUI/BedShapeDialog.hpp
Normal file
56
src/slic3r/GUI/BedShapeDialog.hpp
Normal 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_ */
|
||||
172
src/slic3r/GUI/BitmapCache.cpp
Normal file
172
src/slic3r/GUI/BitmapCache.cpp
Normal 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
|
||||
44
src/slic3r/GUI/BitmapCache.hpp
Normal file
44
src/slic3r/GUI/BitmapCache.hpp
Normal 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 */
|
||||
200
src/slic3r/GUI/BonjourDialog.cpp
Normal file
200
src/slic3r/GUI/BonjourDialog.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
49
src/slic3r/GUI/BonjourDialog.hpp
Normal file
49
src/slic3r/GUI/BonjourDialog.hpp
Normal 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
|
||||
84
src/slic3r/GUI/ButtonsDescription.cpp
Normal file
84
src/slic3r/GUI/ButtonsDescription.cpp
Normal 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
|
||||
|
||||
27
src/slic3r/GUI/ButtonsDescription.hpp
Normal file
27
src/slic3r/GUI/ButtonsDescription.hpp
Normal 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
|
||||
|
||||
15
src/slic3r/GUI/ConfigExceptions.hpp
Normal file
15
src/slic3r/GUI/ConfigExceptions.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
140
src/slic3r/GUI/ConfigSnapshotDialog.cpp
Normal file
140
src/slic3r/GUI/ConfigSnapshotDialog.cpp
Normal 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
|
||||
34
src/slic3r/GUI/ConfigSnapshotDialog.hpp
Normal file
34
src/slic3r/GUI/ConfigSnapshotDialog.hpp
Normal 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_ */
|
||||
915
src/slic3r/GUI/ConfigWizard.cpp
Normal file
915
src/slic3r/GUI/ConfigWizard.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
50
src/slic3r/GUI/ConfigWizard.hpp
Normal file
50
src/slic3r/GUI/ConfigWizard.hpp
Normal 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
|
||||
241
src/slic3r/GUI/ConfigWizard_private.hpp
Normal file
241
src/slic3r/GUI/ConfigWizard_private.hpp
Normal 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
784
src/slic3r/GUI/Field.cpp
Normal 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
466
src/slic3r/GUI/Field.hpp
Normal 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 */
|
||||
846
src/slic3r/GUI/FirmwareDialog.cpp
Normal file
846
src/slic3r/GUI/FirmwareDialog.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
31
src/slic3r/GUI/FirmwareDialog.hpp
Normal file
31
src/slic3r/GUI/FirmwareDialog.hpp
Normal 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
|
||||
5522
src/slic3r/GUI/GLCanvas3D.cpp
Normal file
5522
src/slic3r/GUI/GLCanvas3D.cpp
Normal file
File diff suppressed because it is too large
Load diff
765
src/slic3r/GUI/GLCanvas3D.hpp
Normal file
765
src/slic3r/GUI/GLCanvas3D.hpp
Normal 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_
|
||||
819
src/slic3r/GUI/GLCanvas3DManager.cpp
Normal file
819
src/slic3r/GUI/GLCanvas3DManager.cpp
Normal 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
|
||||
192
src/slic3r/GUI/GLCanvas3DManager.hpp
Normal file
192
src/slic3r/GUI/GLCanvas3DManager.hpp
Normal 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
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
366
src/slic3r/GUI/GLGizmo.hpp
Normal 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
256
src/slic3r/GUI/GLShader.cpp
Normal 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, ¶ms);
|
||||
if (params == GL_FALSE) {
|
||||
// Compilation failed. Get the log.
|
||||
glGetShaderiv(this->fragment_program_id, GL_INFO_LOG_LENGTH, ¶ms);
|
||||
std::vector<char> msg(params);
|
||||
glGetShaderInfoLog(this->fragment_program_id, params, ¶ms, 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, ¶ms);
|
||||
if (params == GL_FALSE) {
|
||||
// Compilation failed. Get the log.
|
||||
glGetShaderiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, ¶ms);
|
||||
std::vector<char> msg(params);
|
||||
glGetShaderInfoLog(this->vertex_program_id, params, ¶ms, 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, ¶ms);
|
||||
if (params == GL_FALSE) {
|
||||
// Linking failed. Get the log.
|
||||
glGetProgramiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, ¶ms);
|
||||
std::vector<char> msg(params);
|
||||
glGetProgramInfoLog(this->vertex_program_id, params, ¶ms, 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
|
||||
41
src/slic3r/GUI/GLShader.hpp
Normal file
41
src/slic3r/GUI/GLShader.hpp
Normal 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_ */
|
||||
199
src/slic3r/GUI/GLTexture.cpp
Normal file
199
src/slic3r/GUI/GLTexture.cpp
Normal 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
|
||||
60
src/slic3r/GUI/GLTexture.hpp
Normal file
60
src/slic3r/GUI/GLTexture.hpp
Normal 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_
|
||||
|
||||
722
src/slic3r/GUI/GLToolbar.cpp
Normal file
722
src/slic3r/GUI/GLToolbar.cpp
Normal 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
|
||||
175
src/slic3r/GUI/GLToolbar.hpp
Normal file
175
src/slic3r/GUI/GLToolbar.hpp
Normal 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
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
260
src/slic3r/GUI/GUI.hpp
Normal 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
|
||||
2041
src/slic3r/GUI/GUI_ObjectParts.cpp
Normal file
2041
src/slic3r/GUI/GUI_ObjectParts.cpp
Normal file
File diff suppressed because it is too large
Load diff
147
src/slic3r/GUI/GUI_ObjectParts.hpp
Normal file
147
src/slic3r/GUI/GUI_ObjectParts.hpp
Normal 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_
|
||||
199
src/slic3r/GUI/LambdaObjectDialog.cpp
Normal file
199
src/slic3r/GUI/LambdaObjectDialog.cpp
Normal 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
|
||||
40
src/slic3r/GUI/LambdaObjectDialog.hpp
Normal file
40
src/slic3r/GUI/LambdaObjectDialog.hpp
Normal 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_
|
||||
88
src/slic3r/GUI/MsgDialog.cpp
Normal file
88
src/slic3r/GUI/MsgDialog.cpp
Normal 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() {}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
67
src/slic3r/GUI/MsgDialog.hpp
Normal file
67
src/slic3r/GUI/MsgDialog.hpp
Normal 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
|
||||
545
src/slic3r/GUI/OptionsGroup.cpp
Normal file
545
src/slic3r/GUI/OptionsGroup.cpp
Normal 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
|
||||
271
src/slic3r/GUI/OptionsGroup.hpp
Normal file
271
src/slic3r/GUI/OptionsGroup.hpp
Normal 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_ */
|
||||
134
src/slic3r/GUI/Preferences.cpp
Normal file
134
src/slic3r/GUI/Preferences.cpp
Normal 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
|
||||
31
src/slic3r/GUI/Preferences.hpp
Normal file
31
src/slic3r/GUI/Preferences.hpp
Normal 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
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
444
src/slic3r/GUI/Preset.hpp
Normal 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_ */
|
||||
1401
src/slic3r/GUI/PresetBundle.cpp
Normal file
1401
src/slic3r/GUI/PresetBundle.cpp
Normal file
File diff suppressed because it is too large
Load diff
168
src/slic3r/GUI/PresetBundle.hpp
Normal file
168
src/slic3r/GUI/PresetBundle.hpp
Normal 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_ */
|
||||
278
src/slic3r/GUI/PresetHints.cpp
Normal file
278
src/slic3r/GUI/PresetHints.cpp
Normal 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
|
||||
30
src/slic3r/GUI/PresetHints.hpp
Normal file
30
src/slic3r/GUI/PresetHints.hpp
Normal 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_ */
|
||||
70
src/slic3r/GUI/ProgressIndicator.hpp
Normal file
70
src/slic3r/GUI/ProgressIndicator.hpp
Normal 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
|
||||
152
src/slic3r/GUI/ProgressStatusBar.cpp
Normal file
152
src/slic3r/GUI/ProgressStatusBar.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
68
src/slic3r/GUI/ProgressStatusBar.hpp
Normal file
68
src/slic3r/GUI/ProgressStatusBar.hpp
Normal 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
|
||||
279
src/slic3r/GUI/RammingChart.cpp
Normal file
279
src/slic3r/GUI/RammingChart.cpp
Normal 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()
|
||||
115
src/slic3r/GUI/RammingChart.hpp
Normal file
115
src/slic3r/GUI/RammingChart.hpp
Normal 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
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
385
src/slic3r/GUI/Tab.hpp
Normal 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_ */
|
||||
20
src/slic3r/GUI/TabIface.cpp
Normal file
20
src/slic3r/GUI/TabIface.cpp
Normal 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
|
||||
41
src/slic3r/GUI/TabIface.hpp
Normal file
41
src/slic3r/GUI/TabIface.hpp
Normal 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_ */
|
||||
196
src/slic3r/GUI/UpdateDialogs.cpp
Normal file
196
src/slic3r/GUI/UpdateDialogs.cpp
Normal 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() {}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
81
src/slic3r/GUI/UpdateDialogs.hpp
Normal file
81
src/slic3r/GUI/UpdateDialogs.hpp
Normal 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
16
src/slic3r/GUI/Widget.hpp
Normal 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
|
||||
338
src/slic3r/GUI/WipeTowerDialog.cpp
Normal file
338
src/slic3r/GUI/WipeTowerDialog.cpp
Normal 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();
|
||||
}
|
||||
90
src/slic3r/GUI/WipeTowerDialog.hpp
Normal file
90
src/slic3r/GUI/WipeTowerDialog.hpp
Normal 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_
|
||||
30
src/slic3r/GUI/callback.hpp
Normal file
30
src/slic3r/GUI/callback.hpp
Normal 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_ */
|
||||
1662
src/slic3r/GUI/wxExtensions.cpp
Normal file
1662
src/slic3r/GUI/wxExtensions.cpp
Normal file
File diff suppressed because it is too large
Load diff
773
src/slic3r/GUI/wxExtensions.hpp
Normal file
773
src/slic3r/GUI/wxExtensions.hpp
Normal 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
25
src/slic3r/GUI/wxinit.h
Normal 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
|
||||
1954
src/slic3r/Utils/ASCIIFolding.cpp
Normal file
1954
src/slic3r/Utils/ASCIIFolding.cpp
Normal file
File diff suppressed because it is too large
Load diff
15
src/slic3r/Utils/ASCIIFolding.hpp
Normal file
15
src/slic3r/Utils/ASCIIFolding.hpp
Normal 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_ */
|
||||
781
src/slic3r/Utils/Bonjour.cpp
Normal file
781
src/slic3r/Utils/Bonjour.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
64
src/slic3r/Utils/Bonjour.hpp
Normal file
64
src/slic3r/Utils/Bonjour.hpp
Normal 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
279
src/slic3r/Utils/Duet.cpp
Normal 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
47
src/slic3r/Utils/Duet.hpp
Normal 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
|
||||
402
src/slic3r/Utils/FixModelByWin10.cpp
Normal file
402
src/slic3r/Utils/FixModelByWin10.cpp
Normal 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 */
|
||||
26
src/slic3r/Utils/FixModelByWin10.hpp
Normal file
26
src/slic3r/Utils/FixModelByWin10.hpp
Normal 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_ */
|
||||
106
src/slic3r/Utils/HexFile.cpp
Normal file
106
src/slic3r/Utils/HexFile.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
33
src/slic3r/Utils/HexFile.hpp
Normal file
33
src/slic3r/Utils/HexFile.hpp
Normal 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
451
src/slic3r/Utils/Http.cpp
Normal 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
115
src/slic3r/Utils/Http.hpp
Normal 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
|
||||
173
src/slic3r/Utils/OctoPrint.cpp
Normal file
173
src/slic3r/Utils/OctoPrint.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
42
src/slic3r/Utils/OctoPrint.hpp
Normal file
42
src/slic3r/Utils/OctoPrint.hpp
Normal 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
|
||||
633
src/slic3r/Utils/PresetUpdater.cpp
Normal file
633
src/slic3r/Utils/PresetUpdater.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
42
src/slic3r/Utils/PresetUpdater.hpp
Normal file
42
src/slic3r/Utils/PresetUpdater.hpp
Normal 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
Loading…
Add table
Add a link
Reference in a new issue