Merged with dev

This commit is contained in:
bubnikv
2018-09-12 11:59:02 +02:00
1491 changed files with 514153 additions and 29226 deletions

View File

@@ -0,0 +1,502 @@
#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 <PrintConfig.hpp>
#include <Print.hpp>
#include <PrintExport.hpp>
#include <Geometry.hpp>
#include <Model.hpp>
#include <Utils.hpp>
#include <SLABasePool.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();
}
static const PrintObjectStep STEP_SLICE = posSlice;
static const PrintObjectStep STEP_PERIMETERS = posPerimeters;
static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill;
static const PrintObjectStep STEP_INFILL = posInfill;
static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial;
static const PrintStep STEP_SKIRT = psSkirt;
static const PrintStep STEP_BRIM = psBrim;
static const PrintStep STEP_WIPE_TOWER = psWipeTower;
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();
}
#if 0
void PrintController::make_skirt()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects()) make_perimeters(obj);
for(auto obj : print_->objects()) infill(obj);
for(auto obj : print_->objects()) gen_support_material(obj);
if(!print_->state.is_done(STEP_SKIRT)) {
print_->state.set_started(STEP_SKIRT);
print_->skirt.clear();
if(print_->has_skirt()) print_->_make_skirt();
print_->state.set_done(STEP_SKIRT);
}
}
void PrintController::make_brim()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects()) make_perimeters(obj);
for(auto obj : print_->objects()) infill(obj);
for(auto obj : print_->objects()) gen_support_material(obj);
make_skirt();
if(!print_->state.is_done(STEP_BRIM)) {
print_->state.set_started(STEP_BRIM);
// since this method must be idempotent, we clear brim paths *before*
// checking whether we need to generate them
print_->brim.clear();
if(print_->config.brim_width > 0) print_->_make_brim();
print_->state.set_done(STEP_BRIM);
}
}
void PrintController::make_wipe_tower()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects()) make_perimeters(obj);
for(auto obj : print_->objects()) infill(obj);
for(auto obj : print_->objects()) gen_support_material(obj);
make_skirt();
make_brim();
if(!print_->state.is_done(STEP_WIPE_TOWER)) {
print_->state.set_started(STEP_WIPE_TOWER);
// since this method must be idempotent, we clear brim paths *before*
// checking whether we need to generate them
print_->brim.clear();
if(print_->has_wipe_tower()) print_->_make_wipe_tower();
print_->state.set_done(STEP_WIPE_TOWER);
}
}
void PrintController::slice(PrintObject *pobj)
{
assert(pobj != nullptr && print_ != nullptr);
if(pobj->state.is_done(STEP_SLICE)) return;
pobj->state.set_started(STEP_SLICE);
pobj->_slice();
auto msg = pobj->_fix_slicing_errors();
if(!msg.empty()) report_issue(IssueType::WARN, msg);
// simplify slices if required
if (print_->config.resolution)
pobj->_simplify_slices(scale_(print_->config.resolution));
if(pobj->layers.empty())
report_issue(IssueType::ERR,
_(L("No layers were detected. You might want to repair your "
"STL file(s) or check their size or thickness and retry"))
);
pobj->state.set_done(STEP_SLICE);
}
void PrintController::make_perimeters(PrintObject *pobj)
{
assert(pobj != nullptr);
slice(pobj);
if (!pobj->state.is_done(STEP_PERIMETERS)) {
pobj->_make_perimeters();
}
}
void PrintController::infill(PrintObject *pobj)
{
assert(pobj != nullptr);
make_perimeters(pobj);
if (!pobj->state.is_done(STEP_PREPARE_INFILL)) {
pobj->state.set_started(STEP_PREPARE_INFILL);
pobj->_prepare_infill();
pobj->state.set_done(STEP_PREPARE_INFILL);
}
pobj->_infill();
}
void PrintController::gen_support_material(PrintObject *pobj)
{
assert(pobj != nullptr);
// prerequisites
slice(pobj);
if(!pobj->state.is_done(STEP_SUPPORTMATERIAL)) {
pobj->state.set_started(STEP_SUPPORTMATERIAL);
pobj->clear_support_layers();
if((pobj->config.support_material || pobj->config.raft_layers > 0)
&& pobj->layers.size() > 1) {
pobj->_generate_support_material();
}
pobj->state.set_done(STEP_SUPPORTMATERIAL);
}
}
PrintController::PngExportData
PrintController::query_png_export_data(const DynamicPrintConfig& conf)
{
PngExportData ret;
auto zippath = query_destination_path("Output zip file", "*.zip", "out");
ret.zippath = zippath;
ret.width_mm = conf.opt_float("display_width");
ret.height_mm = conf.opt_float("display_height");
ret.width_px = conf.opt_int("display_pixels_x");
ret.height_px = conf.opt_int("display_pixels_y");
auto opt_corr = conf.opt<ConfigOptionFloats>("printer_correction");
if(opt_corr) {
ret.corr_x = opt_corr->values[0];
ret.corr_y = opt_corr->values[1];
ret.corr_z = opt_corr->values[2];
}
ret.exp_time_first_s = conf.opt_float("initial_exposure_time");
ret.exp_time_s = conf.opt_float("exposure_time");
return ret;
}
void PrintController::slice(AppControllerBoilerplate::ProgresIndicatorPtr pri)
{
auto st = pri->state();
Slic3r::trace(3, "Starting the slicing process.");
pri->update(st+20, _(L("Generating perimeters")));
for(auto obj : print_->objects()) make_perimeters(obj);
pri->update(st+60, _(L("Infilling layers")));
for(auto obj : print_->objects()) infill(obj);
pri->update(st+70, _(L("Generating support material")));
for(auto obj : print_->objects()) gen_support_material(obj);
pri->message_fmt(_(L("Weight: %.1fg, Cost: %.1f")),
print_->total_weight, print_->total_cost);
pri->state(st+85);
pri->update(st+88, _(L("Generating skirt")));
make_skirt();
pri->update(st+90, _(L("Generating brim")));
make_brim();
pri->update(st+95, _(L("Generating wipe tower")));
make_wipe_tower();
pri->update(st+100, _(L("Done")));
// time to make some statistics..
Slic3r::trace(3, _(L("Slicing process finished.")));
}
void PrintController::slice()
{
auto pri = global_progress_indicator();
if(!pri) pri = create_progress_indicator(100, L("Slicing"));
slice(pri);
}
void PrintController::slice_to_png()
{
using Pointf3 = Vec3d;
auto presetbundle = GUI::get_preset_bundle();
assert(presetbundle);
auto pt = presetbundle->printers.get_selected_preset().printer_technology();
if(pt != ptSLA) {
report_issue(IssueType::ERR, _("Printer technology is not SLA!"),
_("Error"));
return;
}
auto conf = presetbundle->full_config();
conf.validate();
auto exd = query_png_export_data(conf);
if(exd.zippath.empty()) return;
try {
print_->apply_config(conf);
print_->validate();
} catch(std::exception& e) {
report_issue(IssueType::ERR, e.what(), "Error");
return;
}
// TODO: copy the model and work with the copy only
bool correction = false;
if(exd.corr_x != 1.0 || exd.corr_y != 1.0 || exd.corr_z != 1.0) {
correction = true;
print_->invalidate_all_steps();
for(auto po : print_->objects()) {
po->model_object()->scale(
Pointf3(exd.corr_x, exd.corr_y, exd.corr_z)
);
po->model_object()->invalidate_bounding_box();
po->reload_model_instances();
po->invalidate_all_steps();
}
}
// Turn back the correction scaling on the model.
auto scale_back = [this, correction, exd]() {
if(correction) { // scale the model back
print_->invalidate_all_steps();
for(auto po : print_->objects()) {
po->model_object()->scale(
Pointf3(1.0/exd.corr_x, 1.0/exd.corr_y, 1.0/exd.corr_z)
);
po->model_object()->invalidate_bounding_box();
po->reload_model_instances();
po->invalidate_all_steps();
}
}
};
auto print_bb = print_->bounding_box();
Vec2d punsc = unscale(print_bb.size());
// If the print does not fit into the print area we should cry about it.
if(px(punsc) > exd.width_mm || py(punsc) > exd.height_mm) {
std::stringstream ss;
ss << _(L("Print will not fit and will be truncated!")) << "\n"
<< _(L("Width needed: ")) << px(punsc) << " mm\n"
<< _(L("Height needed: ")) << py(punsc) << " mm\n";
if(!report_issue(IssueType::WARN_Q, ss.str(), _(L("Warning")))) {
scale_back();
return;
}
}
// std::async(supports_asynch()? std::launch::async : std::launch::deferred,
// [this, exd, scale_back]()
// {
auto pri = create_progress_indicator(
200, _(L("Slicing to zipped png files...")));
try {
pri->update(0, _(L("Slicing...")));
slice(pri);
} catch (std::exception& e) {
pri->cancel();
report_issue(IssueType::ERR, e.what(), _(L("Exception occured")));
scale_back();
return;
}
auto pbak = print_->progressindicator;
print_->progressindicator = pri;
try {
print_to<FilePrinterFormat::PNG>( *print_, exd.zippath,
exd.width_mm, exd.height_mm,
exd.width_px, exd.height_px,
exd.exp_time_s, exd.exp_time_first_s);
} catch (std::exception& e) {
pri->cancel();
report_issue(IssueType::ERR, e.what(), _(L("Exception occured")));
}
print_->progressindicator = pbak;
scale_back();
// });
}
const PrintConfig &PrintController::config() const
{
return print_->config;
}
#endif
void ProgressIndicator::message_fmt(
const 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>;
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([](){
std::cout << "Cannot be cancelled!" << std::endl;
});
}
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
[pind, count](unsigned rem) {
if(pind)
pind->update(count - rem, _(L("Arranging objects...")));
});
} 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, _(L("Arranging done.")));
pind->on_cancel(/*remove cancel function*/);
}
}
}

View File

@@ -0,0 +1,303 @@
#ifndef APPCONTROLLER_HPP
#define APPCONTROLLER_HPP
#include <string>
#include <vector>
#include <memory>
#include <atomic>
#include <iostream>
#include "ProgressIndicator.hpp"
namespace Slic3r {
class Model;
class Print;
class PrintObject;
class PrintConfig;
class ProgressStatusBar;
/**
* @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 = 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 string& title,
const std::string& extensions) const;
/**
* @brief Same as query_destination_paths but works for directories only.
*/
PathList query_destination_dirs(
const string& title) const;
/**
* @brief Same as query_destination_paths but returns only one path.
*/
Path query_destination_path(
const 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 string& description,
const string& brief);
bool report_issue(IssueType issuetype,
const 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 string& title,
const string& firstmsg) const;
ProgresIndicatorPtr create_progress_indicator(
unsigned statenum,
const 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;
protected:
void make_skirt();
void make_brim();
void make_wipe_tower();
void make_perimeters(PrintObject *pobj);
void infill(PrintObject *pobj);
void gen_support_material(PrintObject *pobj);
// Data structure with the png export input data
struct PngExportData {
std::string zippath; // output zip file
unsigned long width_px = 1440; // resolution - rows
unsigned long height_px = 2560; // resolution columns
double width_mm = 68.0, height_mm = 120.0; // dimensions in mm
double exp_time_first_s = 35.0; // first exposure time
double exp_time_s = 8.0; // global exposure time
double corr_x = 1.0; // offsetting in x
double corr_y = 1.0; // offsetting in y
double corr_z = 1.0; // offsetting in y
};
// Should display a dialog with the input fields for printing to png
PngExportData query_png_export_data(const DynamicPrintConfig&);
// The previous export data, to pre-populate the dialog
PngExportData prev_expdata_;
/**
* @brief Slice one pront object.
* @param pobj The print object.
*/
void slice(PrintObject *pobj);
void slice(ProgresIndicatorPtr pri);
public:
// Must be public for perl to use it
explicit inline PrintController(Print *print): print_(print) {}
PrintController(const PrintController&) = delete;
PrintController(PrintController&&) = delete;
using Ptr = std::unique_ptr<PrintController>;
inline static Ptr create(Print *print) {
return PrintController::Ptr( new PrintController(print) );
}
/**
* @brief Slice the loaded print scene.
*/
void slice();
/**
* @brief Slice the print into zipped png files.
*/
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;
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
* IProgressIndicator interface so we can use this GUI widget from C++.
*
* This function should be called from perl.
*
* @param gauge_id The ID of the gague widget of the status bar.
* @param statusbar_id The ID of the status bar.
*/
void set_global_progress_indicator(ProgressStatusBar *prs);
void arrange_model();
};
}
#endif // APPCONTROLLER_HPP

View File

@@ -0,0 +1,302 @@
#include "AppController.hpp"
#include <thread>
#include <future>
#include <slic3r/GUI/GUI.hpp>
#include <slic3r/GUI/ProgressStatusBar.hpp>
#include <wx/app.h>
#include <wx/filedlg.h>
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include <wx/gauge.h>
#include <wx/statusbr.h>
#include <wx/event.h>
// This source file implements the UI dependent methods of the AppControllers.
// It will be clear what is needed to be reimplemented in case of a UI framework
// change or a CLI client creation. In this particular case we use wxWidgets to
// implement everything.
namespace Slic3r {
bool AppControllerBoilerplate::supports_asynch() const
{
return true;
}
void AppControllerBoilerplate::process_events()
{
wxSafeYield();
}
AppControllerBoilerplate::PathList
AppControllerBoilerplate::query_destination_paths(
const 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 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 string &description,
const 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 string &description)
{
return report_issue(issuetype, description, 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);
gauge_.Update(static_cast<int>(st), message_);
}
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 string& title,
const string& firstmsg) :
gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(),
wxPD_APP_MODAL | wxPD_AUTO_HIDE),
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 cancel() override {
update(max(), "Abort");
ProgressIndicator::cancel();
}
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 string & msg) override {
message_ = msg;
}
virtual void messageFmt(const string& fmt, ...) {
va_list arglist;
va_start(arglist, fmt);
message_ = wxString::Format(wxString(fmt), arglist);
va_end(arglist);
}
virtual void title(const string & title) override {
title_ = title;
}
};
}
AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::create_progress_indicator(
unsigned statenum, const string& title, const 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 string &title) const
{
return create_progress_indicator(statenum, title, string());
}
namespace {
class Wrapper: public ProgressIndicator, public wxEvtHandler {
ProgressStatusBar *sbar_;
using Base = ProgressIndicator;
std::string 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 string & msg) override {
message_ = msg;
}
virtual void message_fmt(const string& fmt, ...) override {
va_list arglist;
va_start(arglist, fmt);
message_ = wxString::Format(fmt, arglist);
va_end(arglist);
}
virtual void title(const 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));
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
#include "2DBed.hpp";
#include "2DBed.hpp"
#include <wx/dcbuffer.h>
#include "BoundingBox.hpp"
@@ -32,17 +32,17 @@ void Bed_2D::repaint()
cw--;
ch--;
auto cbb = BoundingBoxf(Pointf(0, 0),Pointf(cw, ch));
auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch));
// leave space for origin point
cbb.min.translate(4, 0);
cbb.max.translate(-4, -4);
cbb.min(0) += 4;
cbb.max -= Vec2d(4., 4.);
// leave space for origin label
cbb.max.translate(0, -13);
cbb.max(1) -= 13;
// read new size
cw = cbb.size().x;
ch = cbb.size().y;
cw = cbb.size()(0);
ch = cbb.size()(1);
auto ccenter = cbb.center();
@@ -50,113 +50,110 @@ void Bed_2D::repaint()
auto bed_shape = m_bed_shape;
auto bed_polygon = Polygon::new_scale(m_bed_shape);
auto bb = BoundingBoxf(m_bed_shape);
bb.merge(Pointf(0, 0)); // origin needs to be in the visible area
auto bw = bb.size().x;
auto bh = bb.size().y;
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 = Pointf(
ccenter.x - bcenter.x * sfactor,
ccenter.y - bcenter.y * sfactor
auto shift = Vec2d(
ccenter(0) - bcenter(0) * sfactor,
ccenter(1) - bcenter(1) * sfactor
);
m_scale_factor = sfactor;
m_shift = Pointf(shift.x + cbb.min.x,
shift.y - (cbb.max.y - GetSize().GetHeight()));
m_shift = Vec2d(shift(0) + cbb.min(0),
shift(1) - (cbb.max(1) - GetSize().GetHeight()));
// draw bed fill
dc.SetBrush(*new wxBrush(*new wxColour(255, 255, 255), wxSOLID));
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.x, pt_pix.y));
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.x - fmod(bb.min.x, step) + step; x < bb.max.x; x += step) {
Polyline pl = Polyline::new_scale({ Pointf(x, bb.min.y), Pointf(x, bb.max.y) });
polylines.push_back(pl);
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.y - fmod(bb.min.y, step) + step; y < bb.max.y; y += step) {
polylines.push_back(Polyline::new_scale({ Pointf(bb.min.x, y), Pointf(bb.max.x, y) }));
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(*new wxPen(*new wxColour(230, 230, 230), 1, wxSOLID));
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(Pointf::new_unscale(pl.points[i]));
Point pt2 = to_pixels(Pointf::new_unscale(pl.points[i+1]));
dc.DrawLine(pt1.x, pt1.y, pt2.x, pt2.y);
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(*new wxPen(*new wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(*new wxBrush(*new wxColour(0, 0, 0), wxTRANSPARENT));
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(Pointf(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(*new wxPen(*new wxColour(255, 0, 0), 2, wxSOLID)); // red
auto x_end = Pointf(origin_px.x + axes_len, origin_px.y);
dc.DrawLine(wxPoint(origin_px.x, origin_px.y), wxPoint(x_end.x, x_end.y));
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 = x_end;
end.translate(-arrow_len, 0);
end.rotate(angle, x_end);
dc.DrawLine(wxPoint(x_end.x, x_end.y), wxPoint(end.x, end.y));
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(*new wxPen(*new wxColour(0, 255, 0), 2, wxSOLID)); // green
auto y_end = Pointf(origin_px.x, origin_px.y - axes_len);
dc.DrawLine(wxPoint(origin_px.x, origin_px.y), wxPoint(y_end.x, y_end.y));
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 = y_end;
end.translate(0, +arrow_len);
end.rotate(angle, y_end);
dc.DrawLine(wxPoint(y_end.x, y_end.y), wxPoint(end.x, end.y));
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(*new wxPen(*new wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(*new wxBrush(*new wxColour(0, 0, 0), wxSOLID));
dc.DrawCircle(origin_px.x, origin_px.y, 3);
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);
dc.SetTextForeground(*new wxColour(0, 0, 0));
dc.SetFont(*new wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL));
dc.DrawText("(0,0)", origin_px.x + 1, origin_px.y + 2);
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!= Pointf(0, 0)) {
if (m_pos!= Vec2d(0, 0)) {
auto pos_px = to_pixels(m_pos);
dc.SetPen(*new wxPen(*new wxColour(200, 0, 0), 2, wxSOLID));
dc.SetBrush(*new wxBrush(*new wxColour(200, 0, 0), wxTRANSPARENT));
dc.DrawCircle(pos_px.x, pos_px.y, 5);
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.x - 15, pos_px.y, pos_px.x + 15, pos_px.y);
dc.DrawLine(pos_px.x, pos_px.y - 15, pos_px.x, pos_px.y + 15);
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(Pointf point){
auto p = Pointf(point);
p.scale(m_scale_factor);
p.translate(m_shift);
return Point(p.x, GetSize().GetHeight() - p.y);
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){
@@ -164,7 +161,7 @@ void Bed_2D::mouse_event(wxMouseEvent event){
if (!m_painted) return;
auto pos = event.GetPosition();
auto point = to_units(Point(pos.x, pos.y));
auto point = to_units(Point(pos.x, pos.y));
if (event.LeftDown() || event.Dragging()) {
if (m_on_move)
m_on_move(point) ;
@@ -173,14 +170,11 @@ void Bed_2D::mouse_event(wxMouseEvent event){
}
// convert pixels into G - code coordinates
Pointf Bed_2D::to_units(Point point){
auto p = Pointf(point.x, GetSize().GetHeight() - point.y);
p.translate(m_shift.negative());
p.scale(1 / m_scale_factor);
return p;
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(Pointf pos){
void Bed_2D::set_pos(Vec2d pos){
m_pos = pos;
Refresh();
}

View File

@@ -1,3 +1,6 @@
#ifndef slic3r_2DBed_hpp_
#define slic3r_2DBed_hpp_
#include <wx/wx.h>
#include "Config.hpp"
@@ -6,27 +9,29 @@ namespace GUI {
class Bed_2D : public wxPanel
{
bool m_user_drawn_background = false;
bool m_user_drawn_background = true;
bool m_painted = false;
bool m_interactive = false;
double m_scale_factor;
Pointf m_shift;
Pointf m_pos;
std::function<void(Pointf)> m_on_move = nullptr;
Vec2d m_shift = Vec2d::Zero();
Vec2d m_pos = Vec2d::Zero();
std::function<void(Vec2d)> m_on_move = nullptr;
Point to_pixels(Pointf point);
Pointf to_units(Point point);
Point to_pixels(Vec2d point);
Vec2d to_units(Point point);
void repaint();
void mouse_event(wxMouseEvent event);
void set_pos(Pointf pos);
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';
m_user_drawn_background = true;
#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()*/; }));
@@ -36,10 +41,12 @@ public:
}
~Bed_2D(){}
std::vector<Pointf> m_bed_shape;
std::vector<Vec2d> m_bed_shape;
};
} // GUI
} // Slic3r
#endif /* slic3r_2DBed_hpp_ */

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,11 @@
#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 {
@@ -17,6 +20,11 @@ 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.
@@ -111,8 +119,8 @@ public:
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
}
inline void push_geometry(const Pointf3& p, const Vectorf3& n) {
push_geometry(p.x, p.y, p.z, n.x, n.y, n.z);
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) {
@@ -168,17 +176,17 @@ public:
BoundingBoxf3 bbox;
if (! this->vertices_and_normals_interleaved.empty()) {
bbox.defined = true;
bbox.min.x = bbox.max.x = this->vertices_and_normals_interleaved[3];
bbox.min.y = bbox.max.y = this->vertices_and_normals_interleaved[4];
bbox.min.z = bbox.max.z = this->vertices_and_normals_interleaved[5];
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.x = std::min<coordf_t>(bbox.min.x, verts[0]);
bbox.min.y = std::min<coordf_t>(bbox.min.y, verts[1]);
bbox.min.z = std::min<coordf_t>(bbox.min.z, verts[2]);
bbox.max.x = std::max<coordf_t>(bbox.max.x, verts[0]);
bbox.max.y = std::max<coordf_t>(bbox.max.y, verts[1]);
bbox.max.z = std::max<coordf_t>(bbox.max.z, verts[2]);
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;
@@ -192,10 +200,10 @@ private:
}
};
class GLTexture
class LayersTexture
{
public:
GLTexture() : width(0), height(0), levels(0), cells(0) {}
LayersTexture() : width(0), height(0), levels(0), cells(0) {}
// Texture data
std::vector<char> data;
@@ -217,7 +225,7 @@ class GLVolume {
// 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
PrintObject* print_object;
const PrintObject* print_object;
float z_cursor_relative;
float edit_band_width;
@@ -233,7 +241,7 @@ class GLVolume {
edit_band_width = 0.0f;
}
bool can_use() { return (texture_id > 0) && (shader_id > 0) && (print_object != nullptr); }
bool can_use() const { return (texture_id > 0) && (shader_id > 0) && (print_object != nullptr); }
};
public:
@@ -242,41 +250,35 @@ public:
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) :
composite_id(-1),
select_group_id(-1),
drag_group_id(-1),
selected(false),
is_active(true),
zoom_to_volumes(true),
outside_printer_detection_enabled(true),
is_outside(false),
hover(false),
tverts_range(0, size_t(-1)),
qverts_range(0, size_t(-1))
{
color[0] = r;
color[1] = g;
color[2] = b;
color[3] = a;
set_render_color(r, g, b, a);
}
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]) {}
std::vector<int> load_object(
const ModelObject *model_object,
const std::vector<int> &instance_idxs,
const std::string &color_by,
const std::string &select_by,
const std::string &drag_by);
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;
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs);
public:
// Bounding box of this volume, in unscaled coordinates.
BoundingBoxf3 bounding_box;
// Offset of the volume to be rendered.
Pointf3 origin;
// Color of the triangles / quads held by this volume.
float color[4];
// Color used to render this volume.
@@ -287,18 +289,26 @@ public:
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.
bool outside_printer_detection_enabled;
// 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;
@@ -317,10 +327,26 @@ public:
// 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; }
BoundingBoxf3 transformed_bounding_box() const { BoundingBoxf3 bb = this->bounding_box; bb.translate(this->origin); return bb; }
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(); }
@@ -328,11 +354,14 @@ public:
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<GLTexture> 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;
@@ -352,13 +381,10 @@ public:
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 {
return (this->layer_height_texture.get() == nullptr) ? 0. :
double(this->layer_height_texture->cells - 1) / (double(this->layer_height_texture->width) * bounding_box.max.z);
}
void generate_layer_height_texture(PrintObject *print_object, bool force);
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, PrintObject* print_object, float z_cursor_relative, float edit_band_width)
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;
@@ -392,7 +418,7 @@ public:
bool use_VBOs);
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs);
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;
@@ -416,10 +442,18 @@ public:
print_box_max[0] = max_x; print_box_max[1] = max_y; print_box_max[2] = max_z;
}
void update_outside_state(const DynamicPrintConfig* config, bool all_inside);
// 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() const;
std::vector<double> get_current_print_zs(bool active_only) const;
private:
GLVolumeCollection(const GLVolumeCollection &other);
@@ -428,150 +462,139 @@ private:
class _3DScene
{
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(); }
};
static GCodePreviewVolumeIndex s_gcode_preview_volume_index;
class TextureBase
{
protected:
unsigned int m_tex_id;
unsigned int m_tex_width;
unsigned int m_tex_height;
// generate() fills in m_data with the pixels, while finalize() moves the data to the GPU before rendering.
std::vector<unsigned char> m_data;
public:
TextureBase() : m_tex_id(0), m_tex_width(0), m_tex_height(0) {}
virtual ~TextureBase() { _destroy_texture(); }
// If not loaded, load the texture data into the GPU. Return a texture ID or 0 if the texture has zero size.
unsigned int finalize();
unsigned int get_texture_id() const { return m_tex_id; }
unsigned int get_texture_width() const { return m_tex_width; }
unsigned int get_texture_height() const { return m_tex_height; }
void reset_texture() { _destroy_texture(); }
private:
void _destroy_texture();
};
class WarningTexture : public TextureBase
{
static const unsigned char Background_Color[3];
static const unsigned char Opacity;
public:
WarningTexture() : TextureBase() {}
// Generate a texture data, but don't load it into the GPU yet, as the glcontext may not be valid yet.
bool generate(const std::string& msg);
};
class LegendTexture : public TextureBase
{
static const unsigned int Px_Title_Offset = 5;
static const unsigned int Px_Text_Offset = 5;
static const unsigned int Px_Square = 20;
static const unsigned int Px_Square_Contour = 1;
static const unsigned 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;
public:
LegendTexture() : TextureBase() {}
// Generate a texture data, but don't load it into the GPU yet, as the glcontext may not be valid yet.
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
};
static LegendTexture s_legend_texture;
static WarningTexture s_warning_texture;
static GUI::GLCanvas3DManager s_canvas_mgr;
public:
static void _glew_init();
static void init_gl();
static std::string get_gl_info(bool format_as_html, bool extensions);
static bool use_VBOs();
static void load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs);
static bool add_canvas(wxGLCanvas* canvas);
static bool remove_canvas(wxGLCanvas* canvas);
static void remove_all_canvases();
static unsigned int get_legend_texture_width();
static unsigned int get_legend_texture_height();
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 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 unsigned int finalize_legend_texture();
static unsigned int get_warning_texture_width();
static unsigned int get_warning_texture_height();
// generates a warning texture containing the given message
static void generate_warning_texture(const std::string& msg);
static void reset_warning_texture();
static unsigned int finalize_warning_texture();
static void _load_print_toolpaths(
const Print *print,
GLVolumeCollection *volumes,
const std::vector<std::string> &tool_colors,
bool use_VBOs);
static void _load_print_object_toolpaths(
const PrintObject *print_object,
GLVolumeCollection *volumes,
const std::vector<std::string> &tool_colors,
bool use_VBOs);
static void _load_wipe_tower_toolpaths(
const Print *print,
GLVolumeCollection *volumes,
const std::vector<std::string> &tool_colors_str,
bool use_VBOs);
private:
// generates gcode extrusion paths geometry
static void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs);
// generates gcode travel paths geometry
static void _load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs);
static bool _travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
static bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
static bool _travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors);
// generates gcode retractions geometry
static void _load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs);
// generates gcode unretractions geometry
static void _load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs);
// sets gcode geometry visibility according to user selection
static void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
// generates the legend texture in dependence of the current shown view type
static void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates objects and wipe tower geometry
static void _load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs);
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);
};
}

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
#include <GL/glew.h>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Utils.hpp"
#include "AppConfig.hpp"
@@ -9,15 +7,22 @@
#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();
@@ -42,13 +47,19 @@ void AppConfig::set_defaults()
set("no_defaults", "1");
if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0");
// Version check is enabled by default in the config, but it is not implemented yet.
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");
}
void AppConfig::load()
@@ -67,6 +78,19 @@ void AppConfig::load()
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];
@@ -75,6 +99,17 @@ void AppConfig::load()
}
}
// 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;
@@ -96,10 +131,57 @@ void AppConfig::save()
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");
@@ -145,11 +227,29 @@ 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());

View File

@@ -1,15 +1,25 @@
#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) { this->reset(); }
AppConfig() :
m_dirty(false),
m_orig_version(Semver::invalid()),
m_legacy_datadir(false)
{
this->reset();
}
// Clear and reset to defaults.
void reset();
@@ -65,6 +75,14 @@ public:
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
typedef std::map<std::string, std::map<std::string, std::set<std::string>>> VendorMap;
bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const;
void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable);
void set_vendors(const AppConfig &from);
void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; }
void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
const VendorMap& vendors() const { return m_vendors; }
// return recent/skein_directory or recent/config_directory or empty string.
std::string get_last_dir() const;
void update_config_dir(const std::string &dir);
@@ -73,17 +91,40 @@ public:
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

View File

@@ -1,11 +1,12 @@
#include "BackgroundSlicingProcess.hpp"
#include "GUI.hpp"
#include "../../libslic3r/Print.hpp"
#include <wx/event.h>
#include <wx/panel.h>
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
#include "../../libslic3r/Print.hpp"
//#undef NDEBUG
#include <cassert>
#include <stdexcept>

View File

@@ -9,6 +9,8 @@
#include "Model.hpp"
#include "boost/nowide/iostream.hpp"
#include <algorithm>
namespace Slic3r {
namespace GUI {
@@ -34,7 +36,7 @@ void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt)
void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
{
// on_change(nullptr);
// on_change(nullptr);
auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape")));
auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL);
@@ -46,14 +48,14 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
auto optgroup = init_shape_options_page(_(L("Rectangular")));
ConfigOptionDef def;
def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Pointf(200, 200) };
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{ Pointf(0, 0) };
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");
@@ -146,25 +148,22 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
// okay, it's a rectangle
// find origin
// the || 0 hack prevents "-0" which might confuse the user
int x_min, x_max, y_min, y_max;
x_max = x_min = points->values[0].x;
y_max = y_min = points->values[0].y;
for (auto pt : points->values){
if (x_min > pt.x) x_min = pt.x;
if (x_max < pt.x) x_max = pt.x;
if (y_min > pt.y) y_min = pt.y;
if (y_max < pt.y) y_max = pt.y;
}
if (x_min < 0) x_min = 0;
if (x_max < 0) x_max = 0;
if (y_min < 0) y_min = 0;
if (y_max < 0) y_max = 0;
auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) };
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{ Pointf(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
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;
@@ -179,11 +178,12 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
double avg_dist = 0;
for (auto pt: polygon.points)
{
double distance = center.distance_to(pt);
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)
{
@@ -195,7 +195,7 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
// 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(avg_dist * 2), 0);
boost::any ret = wxNumberFormatter::ToString(unscale<double>(avg_dist * 2), 0);
optgroup->set_value("diameter", ret);
update_shape();
return;
@@ -206,8 +206,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
// 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{ Pointf(200, 200) });
optgroup->set_value("rect_origin", new ConfigOptionPoints{ Pointf(0, 0) });
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) });
optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) });
update_shape();
return;
}
@@ -230,19 +230,21 @@ void BedShapePanel::update_shape()
{
auto page_idx = m_shape_options_book->GetSelection();
if (page_idx == SHAPE_RECTANGULAR) {
Pointf rect_size, rect_origin;
Vec2d rect_size(Vec2d::Zero());
Vec2d rect_origin(Vec2d::Zero());
try{
rect_size = boost::any_cast<Pointf>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
rect_size = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
catch (const std::exception &e){
return;}
return;
}
try{
rect_origin = boost::any_cast<Pointf>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
rect_origin = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
}
catch (const std::exception &e){
return;}
auto x = rect_size.x;
auto y = rect_size.y;
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;
@@ -250,17 +252,17 @@ void BedShapePanel::update_shape()
double x1 = x;
double y1 = y;
auto dx = rect_origin.x;
auto dy = rect_origin.y;
auto dx = rect_origin(0);
auto dy = rect_origin(1);
x0 -= dx;
x1 -= dx;
y0 -= dy;
y1 -= dy;
m_canvas->m_bed_shape = { Pointf(x0, y0),
Pointf(x1, y0),
Pointf(x1, y1),
Pointf(x0, y1)};
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;
@@ -274,10 +276,10 @@ void BedShapePanel::update_shape()
auto r = diameter / 2;
auto twopi = 2 * PI;
auto edges = 60;
std::vector<Pointf> points;
std::vector<Vec2d> points;
for (size_t i = 1; i <= 60; ++i){
auto angle = i * twopi / edges;
points.push_back(Pointf(r*cos(angle), r*sin(angle)));
points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
}
m_canvas->m_bed_shape = points;
}
@@ -330,9 +332,9 @@ void BedShapePanel::load_stl()
}
auto polygon = expolygons[0].contour;
std::vector<Pointf> points;
std::vector<Vec2d> points;
for (auto pt : polygon.points)
points.push_back(Pointf::new_unscale(pt));
points.push_back(unscale(pt));
m_canvas->m_bed_shape = points;
update_preview();
}

View File

@@ -1,3 +1,5 @@
#ifndef slic3r_BedShapeDialog_hpp_
#define slic3r_BedShapeDialog_hpp_
// The bed shape dialog.
// The dialog opens from Print Settins tab->Bed Shape : Set...
@@ -32,7 +34,7 @@ public:
void load_stl();
// Returns the resulting bed shape polygon. This value will be stored to the ini file.
std::vector<Pointf> GetValue() { return m_canvas->m_bed_shape; }
std::vector<Vec2d> GetValue() { return m_canvas->m_bed_shape; }
};
class BedShapeDialog : public wxDialog
@@ -44,8 +46,11 @@ public:
~BedShapeDialog(){ }
void build_dialog(ConfigOptionPoints* default_pt);
std::vector<Pointf> GetValue() { return m_panel->GetValue(); }
std::vector<Vec2d> GetValue() { return m_panel->GetValue(); }
};
} // GUI
} // Slic3r
#endif /* slic3r_BedShapeDialog_hpp_ */

View File

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

View File

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

View File

@@ -191,7 +191,7 @@ void BonjourDialog::on_timer(wxTimerEvent &)
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."))));
label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished"))+"."));
timer->Stop();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,915 @@
#include "ConfigWizard_private.hpp"
#include <algorithm>
#include <utility>
#include <unordered_map>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <wx/settings.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/dcclient.h>
#include <wx/statbmp.h>
#include <wx/checkbox.h>
#include <wx/statline.h>
#include "libslic3r/Utils.hpp"
#include "PresetBundle.hpp"
#include "GUI.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
namespace Slic3r {
namespace GUI {
// Printer model picker GUI control
struct PrinterPickerEvent : public wxEvent
{
std::string vendor_id;
std::string model_id;
std::string variant_name;
bool enable;
PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) :
wxEvent(winid, eventType),
vendor_id(std::move(vendor_id)),
model_id(std::move(model_id)),
variant_name(std::move(variant_name)),
enable(enable)
{}
virtual wxEvent *Clone() const
{
return new PrinterPickerEvent(*this);
}
};
wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors) :
wxPanel(parent),
vendor_id(vendor.id),
variants_checked(0)
{
const auto &models = vendor.models;
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20);
printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
sizer->Add(printer_grid);
auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
namefont.SetWeight(wxFONTWEIGHT_BOLD);
// wxGrid appends widgets by rows, but we need to construct them in columns.
// These vectors are used to hold the elements so that they can be appended in the right order.
std::vector<wxStaticText*> titles;
std::vector<wxStaticBitmap*> bitmaps;
std::vector<wxPanel*> variants_panels;
for (const auto &model : models) {
auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model.id);
wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG);
auto *title = new wxStaticText(this, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
title->SetFont(namefont);
title->Wrap(std::max((int)MODEL_MIN_WRAP, bitmap.GetWidth()));
titles.push_back(title);
auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
bitmaps.push_back(bitmap_widget);
auto *variants_panel = new wxPanel(this);
auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
variants_panel->SetSizer(variants_sizer);
const auto model_id = model.id;
bool default_variant = true; // Mark the first variant as default in the GUI
for (const auto &variant : model.variants) {
const auto label = wxString::Format("%s %s %s %s", variant.name, _(L("mm")), _(L("nozzle")),
(default_variant ? _(L("(default)")) : wxString()));
default_variant = false;
auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
const size_t idx = cboxes.size();
cboxes.push_back(cbox);
bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant.name);
variants_checked += enabled;
cbox->SetValue(enabled);
variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
cbox->Bind(wxEVT_CHECKBOX, [this, idx](wxCommandEvent &event) {
if (idx >= this->cboxes.size()) { return; }
this->on_checkbox(this->cboxes[idx], event.IsChecked());
});
}
variants_panels.push_back(variants_panel);
}
for (auto title : titles) { printer_grid->Add(title, 0, wxBOTTOM, 3); }
for (auto bitmap : bitmaps) { printer_grid->Add(bitmap, 0, wxBOTTOM, 20); }
for (auto vp : variants_panels) { printer_grid->Add(vp); }
auto *all_none_sizer = new wxBoxSizer(wxHORIZONTAL);
auto *sel_all = new wxButton(this, wxID_ANY, _(L("Select all")));
auto *sel_none = new wxButton(this, wxID_ANY, _(L("Select none")));
sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true); });
sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
all_none_sizer->AddStretchSpacer();
all_none_sizer->Add(sel_all);
all_none_sizer->Add(sel_none);
sizer->AddStretchSpacer();
sizer->Add(all_none_sizer, 0, wxEXPAND);
SetSizer(sizer);
}
void PrinterPicker::select_all(bool select)
{
for (const auto &cb : cboxes) {
if (cb->GetValue() != select) {
cb->SetValue(select);
on_checkbox(cb, select);
}
}
}
void PrinterPicker::select_one(size_t i, bool select)
{
if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
cboxes[i]->SetValue(select);
on_checkbox(cboxes[i], select);
}
}
void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
{
variants_checked += checked ? 1 : -1;
PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
AddPendingEvent(evt);
}
// Wizard page base
ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname) :
wxPanel(parent->p->hscroll),
parent(parent),
shortname(std::move(shortname)),
p_prev(nullptr),
p_next(nullptr)
{
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
auto font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
font.SetWeight(wxFONTWEIGHT_BOLD);
font.SetPointSize(14);
text->SetFont(font);
sizer->Add(text, 0, wxALIGN_LEFT, 0);
sizer->AddSpacer(10);
content = new wxBoxSizer(wxVERTICAL);
sizer->Add(content, 1);
SetSizer(sizer);
this->Hide();
Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
this->Layout();
event.Skip();
});
}
ConfigWizardPage::~ConfigWizardPage() {}
ConfigWizardPage* ConfigWizardPage::chain(ConfigWizardPage *page)
{
if (p_next != nullptr) { p_next->p_prev = nullptr; }
p_next = page;
if (page != nullptr) {
if (page->p_prev != nullptr) { page->p_prev->p_next = nullptr; }
page->p_prev = this;
}
return page;
}
void ConfigWizardPage::append_text(wxString text)
{
auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
widget->Wrap(WRAP_WIDTH);
widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
append(widget);
}
void ConfigWizardPage::append_spacer(int space)
{
content->AddSpacer(space);
}
bool ConfigWizardPage::Show(bool show)
{
if (extra_buttons() != nullptr) { extra_buttons()->Show(show); }
return wxPanel::Show(show);
}
void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable); }
// Wizard pages
PageWelcome::PageWelcome(ConfigWizard *parent, bool check_first_variant) :
ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))),
printer_picker(nullptr),
others_buttons(new wxPanel(parent)),
cbox_reset(nullptr)
{
if (wizard_p()->run_reason == ConfigWizard::RR_DATA_EMPTY) {
wxString::Format(_(L("Run %s")), ConfigWizard::name());
append_text(wxString::Format(
_(L("Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")),
ConfigWizard::name())
);
} else {
cbox_reset = new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")));
append(cbox_reset);
}
const auto &vendors = wizard_p()->vendors;
const auto vendor_prusa = vendors.find("PrusaResearch");
if (vendor_prusa != vendors.cend()) {
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors);
if (check_first_variant) {
// Select the default (first) model/variant on the Prusa vendor
printer_picker->select_one(0, true);
}
printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
this->on_variant_checked();
});
append(printer_picker);
}
const size_t num_other_vendors = vendors.size() - (vendor_prusa != vendors.cend());
auto *sizer = new wxBoxSizer(wxHORIZONTAL);
auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors")));
other_vendors->Enable(num_other_vendors > 0);
auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup")));
sizer->Add(other_vendors);
sizer->AddSpacer(BTN_SPACING);
sizer->Add(custom_setup);
other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); });
custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); });
others_buttons->SetSizer(sizer);
}
void PageWelcome::on_page_set()
{
chain(wizard_p()->page_update);
on_variant_checked();
}
void PageWelcome::on_variant_checked()
{
enable_next(printer_picker != nullptr ? printer_picker->variants_checked > 0 : false);
}
PageUpdate::PageUpdate(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))),
version_check(true),
preset_update(true)
{
const AppConfig *app_config = GUI::get_app_config();
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for application updates")));
box_slic3r->SetValue(app_config->get("version_check") == "1");
append(box_slic3r);
append_text(_(L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.")));
append_spacer(VERTICAL_SPACING);
auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically")));
box_presets->SetValue(app_config->get("preset_update") == "1");
append(box_presets);
append_text(_(L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.")));
const auto text_bold = _(L("Updates are never applied without user's consent and never overwrite user's customized settings."));
auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
label_bold->SetFont(boldfont);
label_bold->Wrap(WRAP_WIDTH);
append(label_bold);
append_text(_(L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")));
box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
}
PageVendors::PageVendors(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors")))
{
append_text(_(L("Pick another vendor supported by Slic3r PE:")));
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
wxArrayString choices_vendors;
for (const auto vendor_pair : wizard_p()->vendors) {
const auto &vendor = vendor_pair.second;
if (vendor.id == "PrusaResearch") { continue; }
auto *picker = new PrinterPicker(this, vendor, appconfig_vendors);
picker->Hide();
pickers.push_back(picker);
choices_vendors.Add(vendor.name);
picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
this->on_variant_checked();
});
}
auto *vendor_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices_vendors);
if (choices_vendors.GetCount() > 0) {
vendor_picker->SetSelection(0);
on_vendor_pick(0);
}
vendor_picker->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) {
this->on_vendor_pick(evt.GetInt());
});
append(vendor_picker);
for (PrinterPicker *picker : pickers) { this->append(picker); }
}
void PageVendors::on_page_set()
{
on_variant_checked();
}
void PageVendors::on_vendor_pick(size_t i)
{
for (PrinterPicker *picker : pickers) { picker->Hide(); }
if (i < pickers.size()) {
pickers[i]->Show();
wizard_p()->layout_fit();
}
}
void PageVendors::on_variant_checked()
{
size_t variants_checked = 0;
for (const PrinterPicker *picker : pickers) { variants_checked += picker->variants_checked; }
enable_next(variants_checked > 0);
}
PageFirmware::PageFirmware(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))),
gcode_opt(print_config_def.options["gcode_flavor"]),
gcode_picker(nullptr)
{
append_text(_(L("Choose the type of firmware used by your printer.")));
append_text(gcode_opt.tooltip);
wxArrayString choices;
choices.Alloc(gcode_opt.enum_labels.size());
for (const auto &label : gcode_opt.enum_labels) {
choices.Add(label);
}
gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
const auto &enum_values = gcode_opt.enum_values;
auto needle = enum_values.cend();
if (gcode_opt.default_value != nullptr) {
needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
}
if (needle != enum_values.cend()) {
gcode_picker->SetSelection(needle - enum_values.cbegin());
} else {
gcode_picker->SetSelection(0);
}
append(gcode_picker);
}
void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
{
ConfigOptionEnum<GCodeFlavor> opt;
auto sel = gcode_picker->GetSelection();
if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) {
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;
GUI::get_current_screen_size(width, height);
wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN)));
wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN)));
SetMinSize(wizard_size);
Fit();
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });
p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); });
p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->EndModal(wxID_OK); });
}
ConfigWizard::~ConfigWizard() {}
bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater)
{
BOOST_LOG_TRIVIAL(info) << "Running ConfigWizard, reason: " << p->run_reason;
if (ShowModal() == wxID_OK) {
auto *app_config = GUI::get_app_config();
p->apply_config(app_config, preset_bundle, updater);
app_config->set_legacy_datadir(false);
BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
return true;
} else {
BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
return false;
}
}
const wxString& ConfigWizard::name()
{
// A different naming convention is used for the Wizard on Windows vs. OSX & GTK.
#if WIN32
static const wxString config_wizard_name = L("Configuration Wizard");
#else
static const wxString config_wizard_name = L("Configuration Assistant");
#endif
return config_wizard_name;
}
}
}

View File

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

View File

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

View File

@@ -12,19 +12,54 @@ namespace Slic3r { namespace GUI {
wxString double_to_string(double const value)
{
int precision = 10 * value - int(10 * value) == 0 ? 1 : 2;
return value - int(value) == 0 ?
wxString::Format(_T("%i"), int(value)) :
wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
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(){
m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
// use bouth of temporary_icons till don't have "undo_icon"
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
if (wxMSW) m_Undo_btn->SetBackgroundColour(color);
m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG));
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();
}
@@ -45,36 +80,40 @@ namespace Slic3r { namespace GUI {
m_on_change(m_opt_id, get_value());
}
void Field::on_back_to_initial_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 = L_str(m_opt.tooltip);
wxString tooltip = _(m_opt.tooltip);
if (tooltip.length() > 0)
tooltip_text = tooltip + "(" + _(L("default")) + ": " +
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") +
default_string + ")";
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(std::string string, std::string pattern)
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);
}
boost::any Field::get_value_by_opt_type(wxString str)
void Field::get_value_by_opt_type(wxString& str)
{
boost::any ret_val;
switch (m_opt.type){
case coInt:
ret_val = wxAtoi(str);
m_value = wxAtoi(str);
break;
case coPercent:
case coPercents:
@@ -82,20 +121,39 @@ namespace Slic3r { namespace GUI {
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;
str.ToCDouble(&val);
ret_val = 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:
ret_val = str.ToStdString();
m_value = str.ToStdString();
break;
default:
break;
}
return ret_val;
}
void TextCtrl::BUILD() {
@@ -124,10 +182,10 @@ namespace Slic3r { namespace GUI {
case coFloat:
{
double val = m_opt.type == coFloats ?
static_cast<const ConfigOptionFloats*>(m_opt.default_value)->get_at(0) :
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(0);
static_cast<const ConfigOptionPercents*>(m_opt.default_value)->get_at(m_opt_idx);
text_value = double_to_string(val);
break;
}
@@ -137,10 +195,8 @@ namespace Slic3r { namespace GUI {
case coStrings:
{
const ConfigOptionStrings *vec = static_cast<const ConfigOptionStrings*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break;
if (vec->size() > 1)
break;
text_value = vec->values.at(0);
if (vec == nullptr || vec->empty()) break; //for the case of empty default value
text_value = vec->get_at(m_opt_idx);
break;
}
default:
@@ -156,32 +212,70 @@ namespace Slic3r { namespace GUI {
//! to allow the default handling
event.Skip();
//! eliminating the g-code pop up text description
temp->GetToolTip()->Enable(false);
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)
{
on_kill_focus(e);
e.Skip();// on_kill_focus(e);
temp->GetToolTip()->Enable(true);
}), temp->GetId());
#endif // __WXGTK__
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent) { on_change_field(); }), temp->GetId());
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()
boost::any& TextCtrl::get_value()
{
wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
boost::any ret_val = get_value_by_opt_type(ret_str);
get_value_by_opt_type(ret_str);
return ret_val;
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);
@@ -189,7 +283,7 @@ void CheckBox::BUILD() {
bool check_value = m_opt.type == coBool ?
m_opt.default_value->getBool() : m_opt.type == coBools ?
static_cast<ConfigOptionBools*>(m_opt.default_value)->values.at(0) :
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);
@@ -204,15 +298,15 @@ void CheckBox::BUILD() {
window = dynamic_cast<wxWindow*>(temp);
}
boost::any CheckBox::get_value()
boost::any& CheckBox::get_value()
{
boost::any ret_val;
// boost::any m_value;
bool value = dynamic_cast<wxCheckBox*>(window)->GetValue();
if (m_opt.type == coBool)
ret_val = static_cast<bool>(value);
m_value = static_cast<bool>(value);
else
ret_val = static_cast<unsigned char>(value);
return ret_val;
m_value = static_cast<unsigned char>(value);
return m_value;
}
int undef_spin_val = -9999; //! Probably, It's not necessary
@@ -245,16 +339,14 @@ void SpinCtrl::BUILD() {
break;
}
const int min_val = m_opt_id == "standby_temperature_delta" ?
-500 : m_opt.min > 0 ?
m_opt.min : 0;
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_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
@@ -295,7 +387,7 @@ void Choice::BUILD() {
}
else{
for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels){
const wxString& str = m_opt_id == "support" ? L_str(el) : el;
const wxString& str = _(el);//m_opt_id == "support" ? _(el) : el;
temp->Append(str);
}
set_selection();
@@ -348,7 +440,7 @@ void Choice::set_selection()
break;
}
case coStrings:{
text_value = static_cast<const ConfigOptionStrings*>(m_opt.default_value)->values.at(0);
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)
@@ -365,7 +457,7 @@ void Choice::set_selection()
}
}
void Choice::set_value(const std::string value, bool change_event) //! Redundant?
void Choice::set_value(const std::string& value, bool change_event) //! Redundant?
{
m_disable_change_event = !change_event;
@@ -384,7 +476,7 @@ void Choice::set_value(const std::string value, bool change_event) //! Redundan
m_disable_change_event = false;
}
void Choice::set_value(boost::any value, bool change_event)
void Choice::set_value(const boost::any& value, bool change_event)
{
m_disable_change_event = !change_event;
@@ -392,6 +484,7 @@ void Choice::set_value(boost::any value, bool change_event)
case coInt:
case coFloat:
case coPercent:
case coString:
case coStrings:{
wxString text_value;
if (m_opt.type == coInt)
@@ -405,14 +498,39 @@ void Choice::set_value(boost::any value, bool change_event)
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:{
dynamic_cast<wxComboBox*>(window)->SetSelection(boost::any_cast<int>(value));
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:
@@ -423,7 +541,7 @@ void Choice::set_value(boost::any value, bool change_event)
}
//! it's needed for _update_serial_ports()
void Choice::set_values(const std::vector<std::string> values)
void Choice::set_values(const std::vector<std::string>& values)
{
if (values.empty())
return;
@@ -434,6 +552,7 @@ void Choice::set_values(const std::vector<std::string> values)
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);
@@ -441,16 +560,19 @@ void Choice::set_values(const std::vector<std::string> values)
m_disable_change_event = false;
}
boost::any Choice::get_value()
boost::any& Choice::get_value()
{
boost::any ret_val;
// boost::any m_value;
wxString ret_str = static_cast<wxComboBox*>(window)->GetValue();
if (m_opt_id == "support")
return ret_str;
// 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)
ret_val = get_value_by_opt_type(ret_str);
/*m_value = */get_value_by_opt_type(ret_str);
else
{
int ret_enum = static_cast<wxComboBox*>(window)->GetSelection();
@@ -461,22 +583,24 @@ boost::any Choice::get_value()
t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
int value = map_names.at(key);
ret_val = static_cast<InfillPattern>(value);
m_value = static_cast<InfillPattern>(value);
}
else
ret_val = static_cast<InfillPattern>(0);
m_value = static_cast<InfillPattern>(0);
}
if (m_opt_id.compare("fill_pattern") == 0)
ret_val = static_cast<InfillPattern>(ret_enum);
m_value = static_cast<InfillPattern>(ret_enum);
else if (m_opt_id.compare("gcode_flavor") == 0)
ret_val = static_cast<GCodeFlavor>(ret_enum);
m_value = static_cast<GCodeFlavor>(ret_enum);
else if (m_opt_id.compare("support_material_pattern") == 0)
ret_val = static_cast<SupportMaterialPattern>(ret_enum);
m_value = static_cast<SupportMaterialPattern>(ret_enum);
else if (m_opt_id.compare("seam_position") == 0)
ret_val = static_cast<SeamPosition>(ret_enum);
m_value = static_cast<SeamPosition>(ret_enum);
else if (m_opt_id.compare("host_type") == 0)
m_value = static_cast<PrintHostType>(ret_enum);
}
return ret_val;
return m_value;
}
void ColourPicker::BUILD()
@@ -485,7 +609,7 @@ void ColourPicker::BUILD()
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxString clr(static_cast<ConfigOptionStrings*>(m_opt.default_value)->values.at(0));
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
@@ -496,14 +620,14 @@ void ColourPicker::BUILD()
temp->SetToolTip(get_tooltip_text(clr));
}
boost::any ColourPicker::get_value(){
boost::any ret_val;
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());
ret_val = clr_str.ToStdString();
m_value = clr_str.ToStdString();
return ret_val;
return m_value;
}
void PointCtrl::BUILD()
@@ -517,10 +641,10 @@ void PointCtrl::BUILD()
//
wxSize field_size(40, -1);
auto default_pt = static_cast<ConfigOptionPoints*>(m_opt.default_value)->values.at(0);
double val = default_pt.x;
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.y;
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);
@@ -541,22 +665,22 @@ void PointCtrl::BUILD()
y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
}
void PointCtrl::set_value(const Pointf value, bool change_event)
void PointCtrl::set_value(const Vec2d& value, bool change_event)
{
m_disable_change_event = !change_event;
double val = value.x;
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.y;
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(boost::any value, bool change_event)
void PointCtrl::set_value(const boost::any& value, bool change_event)
{
Pointf pt;
Pointf *ptf = boost::any_cast<Pointf>(&value);
Vec2d pt(Vec2d::Zero());
const Vec2d *ptf = boost::any_cast<Vec2d>(&value);
if (!ptf)
{
ConfigOptionPoints* pts = boost::any_cast<ConfigOptionPoints*>(value);
@@ -564,35 +688,96 @@ void PointCtrl::set_value(boost::any value, bool change_event)
}
else
pt = *ptf;
// try
// {
// pt = boost::any_cast<ConfigOptionPoints*>(value)->values.at(0);
// }
// catch (const std::exception &e)
// {
// try{
// pt = boost::any_cast<Pointf>(value);
// }
// catch (const std::exception &e)
// {
// std::cerr << "Error! Can't cast PointCtrl value" << m_opt_id << "\n";
// return;
// }
// }
set_value(pt, change_event);
}
boost::any PointCtrl::get_value()
boost::any& PointCtrl::get_value()
{
Pointf ret_point;
double val;
x_textctrl->GetValue().ToDouble(&val);
ret_point.x = val;
y_textctrl->GetValue().ToDouble(&val);
ret_point.y = val;
return ret_point;
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

View File

@@ -31,11 +31,36 @@ 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, boost::any)>;
using t_back_to_init = std::function<void(std::string)>;
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.
@@ -52,6 +77,8 @@ protected:
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)
@@ -63,38 +90,40 @@ public:
/// 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.
/// 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};
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
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(boost::any value, bool change_event) = 0;
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 boost::any& get_value() = 0;
virtual void enable() = 0;
virtual void disable() = 0;
wxStaticText* m_Label = nullptr;
wxButton* m_Undo_btn = nullptr;
/// Fires the enable or disable function, based on the input.
/// 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) {};
@@ -102,8 +131,8 @@ public:
virtual wxSizer* getSizer() { return nullptr; }
virtual wxWindow* getWindow() { return nullptr; }
bool is_matched(std::string string, std::string pattern);
boost::any get_value_by_opt_type(wxString str);
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>
@@ -113,6 +142,84 @@ public:
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
@@ -120,32 +227,37 @@ public:
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; }
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(std::string value, bool change_event = false) {
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(boost::any value, bool 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;
boost::any& get_value() override;
virtual void enable();
virtual void disable();
@@ -157,6 +269,7 @@ class CheckBox : public 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;
@@ -166,12 +279,12 @@ public:
dynamic_cast<wxCheckBox*>(window)->SetValue(value);
m_disable_change_event = false;
}
void set_value(boost::any value, bool 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;
boost::any& get_value() override;
void enable() override { dynamic_cast<wxCheckBox*>(window)->Enable(); }
void disable() override { dynamic_cast<wxCheckBox*>(window)->Disable(); }
@@ -183,24 +296,27 @@ class SpinCtrl : public 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) {
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(boost::any value, bool change_event = false) {
void set_value(const boost::any& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxSpinCtrl*>(window)->SetValue(boost::any_cast<int>(value));
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(dynamic_cast<wxSpinCtrl*>(window)->GetValue());
boost::any& get_value() override {
// return boost::any(tmp_value);
return m_value = tmp_value;
}
void enable() override { dynamic_cast<wxSpinCtrl*>(window)->Enable(); }
@@ -213,15 +329,16 @@ class Choice : public 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(boost::any value, bool change_event = false);
void set_values(const std::vector<std::string> values);
boost::any get_value() override;
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(); };
@@ -233,22 +350,23 @@ class ColourPicker : public 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) {
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(boost::any value, bool 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;
boost::any& get_value() override;
void enable() override { dynamic_cast<wxColourPickerCtrl*>(window)->Enable(); };
void disable() override{ dynamic_cast<wxColourPickerCtrl*>(window)->Disable(); };
@@ -260,29 +378,89 @@ class PointCtrl : public 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;
wxTextCtrl* y_textctrl;
wxTextCtrl* x_textctrl{ nullptr };
wxTextCtrl* y_textctrl{ nullptr };
void BUILD() override;
void set_value(const Pointf value, bool change_event = false);
void set_value(boost::any value, bool change_event = false);
boost::any get_value() 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(); };
y_textctrl->Enable(); }
void disable() override{
x_textctrl->Disable();
y_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); }
};
#endif
} // GUI
} // Slic3r
#endif /* SLIC3R_GUI_FIELD_HPP */

View File

@@ -0,0 +1,839 @@
#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()
{
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(
"The Multi Material Control device was not found.\n"
"If the device is connected, please press the Reset button next to the USB connector ..."
)));
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);
} 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);
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY);
p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
port_sizer->Add(p->btn_rescan, 0);
port_sizer->Add(p->port_autodetect, 1, wxEXPAND);
p->enable_port_picker(true);
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:")));
p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready")));
p->txt_status->SetFont(status_font);
auto *grid = new wxFlexGridSizer(2, SPACING, SPACING);
grid->AddGrowableCol(1);
grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->hex_picker, 0, wxEXPAND);
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(port_sizer, 0, wxEXPAND);
grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->txt_status, 0, wxEXPAND);
vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING);
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE);
auto *spoiler_pane = p->spoiler->GetPane();
auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL);
p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
p->txt_stdout->SetFont(mono_font);
spoiler_sizer->Add(p->txt_stdout, 1, wxEXPAND);
spoiler_pane->SetSizer(spoiler_sizer);
// The doc says proportion need to be 0 for wxCollapsiblePane.
// Experience says it needs to be 1, otherwise things won't get sized properly.
vsizer->Add(p->spoiler, 1, wxEXPAND | wxBOTTOM, SPACING);
p->btn_close = new wxButton(panel, wxID_CLOSE);
p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready);
p->btn_flash->Disable();
auto *bsizer = new wxBoxSizer(wxHORIZONTAL);
bsizer->Add(p->btn_close);
bsizer->AddStretchSpacer();
bsizer->Add(p->btn_flash);
vsizer->Add(bsizer, 0, wxEXPAND);
auto *topsizer = new wxBoxSizer(wxVERTICAL);
topsizer->Add(panel, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
SetSizerAndFit(topsizer);
const auto size = GetSize();
SetSize(std::max(size.GetWidth(), static_cast<int>(MIN_WIDTH)), std::max(size.GetHeight(), static_cast<int>(MIN_HEIGHT)));
Layout();
// Bind events
p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
if (wxFileExists(evt.GetPath())) {
this->p->load_hex_file(evt.GetPath());
this->p->btn_flash->Enable();
}
});
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) {
if (evt.GetCollapsed()) {
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight();
this->SetSize(this->GetSize().GetWidth(), new_height);
} else {
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED));
}
this->Layout();
this->p->fit_no_shrink();
});
p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); });
p->btn_rescan->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->p->find_serial_ports(); });
p->btn_flash->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
if (this->p->avrdude) {
// Flashing is in progress, ask the user if they're really sure about canceling it
wxMessageDialog dlg(this,
_(L("Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!")),
_(L("Confirmation")),
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
if (dlg.ShowModal() == wxID_YES) {
this->p->set_txt_status(_(L("Cancelling...")));
this->p->user_cancel();
}
} else {
// Start a flashing task
this->p->perform_upload();
}
});
Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); });
Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); });
Bind(EVT_ASYNC_DIALOG, [this](wxCommandEvent &evt) { this->p->on_async_dialog(evt); });
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
if (this->p->avrdude) {
evt.Veto();
} else {
evt.Skip();
}
});
p->find_serial_ports();
}
FirmwareDialog::~FirmwareDialog()
{
// Needed bacuse of forward defs
}
void FirmwareDialog::run(wxWindow *parent)
{
FirmwareDialog dialog(parent);
dialog.ShowModal();
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,766 @@
#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(); }
};
public:
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;
};
private:
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;
void reload_scene(bool force);
void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
void load_preview(const std::vector<std::string>& str_tool_colors);
void register_on_viewport_changed_callback(void* callback);
void register_on_double_click_callback(void* callback);
void register_on_right_click_callback(void* callback);
void register_on_select_object_callback(void* callback);
void register_on_model_update_callback(void* callback);
void register_on_remove_object_callback(void* callback);
void register_on_arrange_callback(void* callback);
void register_on_rotate_object_left_callback(void* callback);
void register_on_rotate_object_right_callback(void* callback);
void register_on_scale_object_uniformly_callback(void* callback);
void register_on_increase_objects_callback(void* callback);
void register_on_decrease_objects_callback(void* callback);
void register_on_instance_moved_callback(void* callback);
void register_on_wipe_tower_moved_callback(void* callback);
void register_on_enable_action_buttons_callback(void* callback);
void register_on_gizmo_scale_uniformly_callback(void* callback);
void register_on_gizmo_rotate_callback(void* callback);
void register_on_gizmo_flatten_callback(void* callback);
void register_on_update_geometry_info_callback(void* callback);
void register_action_add_callback(void* callback);
void register_action_delete_callback(void* callback);
void register_action_deleteall_callback(void* callback);
void register_action_arrange_callback(void* callback);
void register_action_more_callback(void* callback);
void register_action_fewer_callback(void* callback);
void register_action_split_callback(void* callback);
void register_action_cut_callback(void* callback);
void register_action_settings_callback(void* callback);
void register_action_layersediting_callback(void* callback);
void register_action_selectbyparts_callback(void* callback);
void bind_event_handlers();
void unbind_event_handlers();
void on_size(wxSizeEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_char(wxKeyEvent& evt);
void on_mouse_wheel(wxMouseEvent& evt);
void on_timer(wxTimerEvent& evt);
void on_mouse(wxMouseEvent& evt);
void on_paint(wxPaintEvent& evt);
void on_key_down(wxKeyEvent& evt);
Size get_canvas_size() const;
Point get_local_mouse_position() const;
void reset_legend_texture();
void set_tooltip(const std::string& tooltip);
private:
bool _is_shown_on_screen() const;
void _force_zoom_to_bed();
bool _init_toolbar();
void _resize(unsigned int w, unsigned int h);
BoundingBoxf3 _max_bounding_box() const;
BoundingBoxf3 _selected_volumes_bounding_box() const;
void _zoom_to_bounding_box(const BoundingBoxf3& bbox);
float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const;
void _deregister_callbacks();
void _mark_volumes_for_layer_height() const;
void _refresh_if_shown_on_screen();
void _camera_tranform() const;
void _picking_pass() const;
void _render_background() const;
void _render_bed(float theta) const;
void _render_axes(bool depth_test) const;
void _render_objects() const;
void _render_cutting_plane() const;
void _render_warning_texture() const;
void _render_legend_texture() const;
void _render_layer_editing_overlay() const;
void _render_volumes(bool fake_colors) const;
void _render_current_gizmo() const;
void _render_gizmos_overlay() const;
void _render_toolbar() const;
float _get_layers_editing_cursor_z_relative() const;
void _perform_layer_editing_action(wxMouseEvent* evt = nullptr);
// Convert the screen space coordinate to an object space coordinate.
// If the Z screen space coordinate is not provided, a depth buffer value is substituted.
Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
// Convert the screen space coordinate to world coordinate on the bed.
Vec3d _mouse_to_bed_3d(const Point& mouse_pos);
// Returns the view ray line, in world coordinate, at the given mouse position.
Linef3 mouse_ray(const Point& mouse_pos);
void _start_timer();
void _stop_timer();
int _get_first_selected_object_id() const;
int _get_first_selected_volume_id(int object_id) const;
// Create 3D thick extrusion lines for a skirt and brim.
// Adds a new Slic3r::GUI::3DScene::Volume to volumes.
void _load_print_toolpaths();
// Create 3D thick extrusion lines for object forming extrusions.
// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
// one for perimeters, one for infill and one for supports.
void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors);
// Create 3D thick extrusion lines for wipe tower extrusions
void _load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
// generates gcode extrusion paths geometry
void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates gcode travel paths geometry
void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
bool _travel_paths_by_type(const GCodePreviewData& preview_data);
bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data);
bool _travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates gcode retractions geometry
void _load_gcode_retractions(const GCodePreviewData& preview_data);
// generates gcode unretractions geometry
void _load_gcode_unretractions(const GCodePreviewData& preview_data);
// generates objects and wipe tower geometry
void _load_shells();
// sets gcode geometry visibility according to user selection
void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
void _update_toolpath_volumes_outside_state();
void _show_warning_texture_if_needed();
void _on_move(const std::vector<int>& volume_idxs);
void _on_select(int volume_idx, int object_idx);
// generates the legend texture in dependence of the current shown view type
void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates a warning texture containing the given message
void _generate_warning_texture(const std::string& msg);
void _reset_warning_texture();
bool _is_any_volume_outside() const;
void _resize_toolbar() const;
static std::vector<float> _parse_colors(const std::vector<std::string>& colors);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLCanvas3D_hpp_

View File

@@ -0,0 +1,813 @@
#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;
}
void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->reload_scene(force);
}
void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
{
if (preview_data == nullptr)
return;
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->load_gcode_preview(*preview_data, str_tool_colors);
}
void GLCanvas3DManager::load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->load_preview(str_tool_colors);
}
void GLCanvas3DManager::reset_legend_texture()
{
for (CanvasesMap::value_type& canvas : m_canvases)
{
if (canvas.second != nullptr)
canvas.second->reset_legend_texture();
}
}
void GLCanvas3DManager::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_viewport_changed_callback(callback);
}
void GLCanvas3DManager::register_on_double_click_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_double_click_callback(callback);
}
void GLCanvas3DManager::register_on_right_click_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_right_click_callback(callback);
}
void GLCanvas3DManager::register_on_select_object_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_select_object_callback(callback);
}
void GLCanvas3DManager::register_on_model_update_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_model_update_callback(callback);
}
void GLCanvas3DManager::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_remove_object_callback(callback);
}
void GLCanvas3DManager::register_on_arrange_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_arrange_callback(callback);
}
void GLCanvas3DManager::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_rotate_object_left_callback(callback);
}
void GLCanvas3DManager::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_rotate_object_right_callback(callback);
}
void GLCanvas3DManager::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_scale_object_uniformly_callback(callback);
}
void GLCanvas3DManager::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_increase_objects_callback(callback);
}
void GLCanvas3DManager::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_decrease_objects_callback(callback);
}
void GLCanvas3DManager::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_instance_moved_callback(callback);
}
void GLCanvas3DManager::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_wipe_tower_moved_callback(callback);
}
void GLCanvas3DManager::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_enable_action_buttons_callback(callback);
}
void GLCanvas3DManager::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_gizmo_scale_uniformly_callback(callback);
}
void GLCanvas3DManager::register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_gizmo_rotate_callback(callback);
}
void GLCanvas3DManager::register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_gizmo_flatten_callback(callback);
}
void GLCanvas3DManager::register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_on_update_geometry_info_callback(callback);
}
void GLCanvas3DManager::register_action_add_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_add_callback(callback);
}
void GLCanvas3DManager::register_action_delete_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_delete_callback(callback);
}
void GLCanvas3DManager::register_action_deleteall_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_deleteall_callback(callback);
}
void GLCanvas3DManager::register_action_arrange_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_arrange_callback(callback);
}
void GLCanvas3DManager::register_action_more_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_more_callback(callback);
}
void GLCanvas3DManager::register_action_fewer_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_fewer_callback(callback);
}
void GLCanvas3DManager::register_action_split_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_split_callback(callback);
}
void GLCanvas3DManager::register_action_cut_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_cut_callback(callback);
}
void GLCanvas3DManager::register_action_settings_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_settings_callback(callback);
}
void GLCanvas3DManager::register_action_layersediting_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_layersediting_callback(callback);
}
void GLCanvas3DManager::register_action_selectbyparts_callback(wxGLCanvas* canvas, void* callback)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->register_action_selectbyparts_callback(callback);
}
GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas)
{
return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
}
GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const
{
return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
}
bool GLCanvas3DManager::_init(GLCanvas3D& canvas)
{
if (!m_gl_initialized)
init_gl();
return canvas.init(m_use_VBOs, m_use_legacy_opengl);
}
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,191 @@
#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;
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_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,359 @@
#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 HalfSize;
static const float DraggingScaleFactor;
Vec3d center;
Vec3d angles;
float color[3];
bool enabled;
bool dragging;
Grabber();
void render(bool hover) const;
void render_for_picking() const { render(color, false); }
private:
void render(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;
void render_grabbers_for_picking() 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;
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;
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;
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_displacement(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;
std::vector<Vec2d> m_instances_positions;
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_

View File

@@ -2,6 +2,9 @@
#include "GLShader.hpp"
#include "../../libslic3r/Utils.hpp"
#include <boost/nowide/fstream.hpp>
#include <string>
#include <utility>
#include <assert.h>
@@ -22,7 +25,7 @@ inline std::string gl_get_string_safe(GLenum param)
return std::string(value ? value : "N/A");
}
bool GLShader::load(const char *fragment_shader, const char *vertex_shader)
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());
@@ -123,6 +126,41 @@ bool GLShader::load(const char *fragment_shader, const char *vertex_shader)
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) {
@@ -176,6 +214,17 @@ bool GLShader::set_uniform(const char *name, float value) const
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

View File

@@ -16,13 +16,16 @@ public:
{}
~GLShader();
bool load(const char *fragment_shader, const char *vertex_shader);
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;

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,175 @@
#ifndef slic3r_GLToolbar_hpp_
#define slic3r_GLToolbar_hpp_
#include "../../slic3r/GUI/GLTexture.hpp"
#include "../../libslic3r/Utils.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_

File diff suppressed because it is too large Load Diff

View File

@@ -3,12 +3,16 @@
#include <string>
#include <vector>
#include "Config.hpp"
#include "PrintConfig.hpp"
#include "../../libslic3r/Utils.hpp"
#include "GUI_ObjectParts.hpp"
#include <wx/intl.h>
#include <wx/string.h>
class wxApp;
class wxWindow;
class wxFrame;
class wxWindow;
class wxMenuBar;
class wxNotebook;
class wxPanel;
@@ -19,6 +23,10 @@ class wxArrayLong;
class wxColour;
class wxBoxSizer;
class wxFlexGridSizer;
class wxButton;
class wxFileDialog;
class wxStaticBitmap;
class wxFont;
namespace Slic3r {
@@ -26,8 +34,19 @@ class PresetBundle;
class PresetCollection;
class Print;
class AppConfig;
class PresetUpdater;
class DynamicPrintConfig;
class TabIface;
class _3DScene;
#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))
@@ -52,22 +71,28 @@ 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["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";
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();
std::vector<std::string> scan_serial_ports();
bool debugged();
void break_to_debugger();
@@ -78,28 +103,80 @@ 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_3DScene(_3DScene *scene);
void set_objects_from_perl( wxWindow* parent,
wxBoxSizer *frequently_changed_parameters_sizer,
wxBoxSizer *expert_mode_part_sizer,
wxBoxSizer *scrolled_window_sizer,
wxButton *btn_export_gcode,
wxButton *btn_export_stl,
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();
wxColour* get_modified_label_clr();
AppConfig* get_app_config();
wxApp* get_app();
PresetBundle* get_preset_bundle();
wxFrame* get_main_frame();
wxNotebook * get_tab_panel();
wxNotebook* get_tab_panel();
void add_debug_menu(wxMenuBar *menu, int event_language_change);
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
void open_preferences_dialog(int event_preferences);
extern void open_preferences_dialog(int event_preferences);
// Create a new preset tab (print, filament and printer),
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed);
TabIface* get_preset_tab_iface(char *name);
// add it at the end of the tab panel.
void add_created_tab(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, t_config_option_key opt_key, boost::any value, int opt_index = 0);
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
void show_error(wxWindow* parent, wxString message);
void show_info(wxWindow* parent, wxString message, wxString title);
void warning_catcher(wxWindow* parent, wxString message);
// 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.
@@ -113,6 +190,8 @@ void save_language();
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();
std::vector<Tab *>& get_tabs_list();
bool checked_tab(Tab* tab);
@@ -132,12 +211,36 @@ wxString L_str(const std::string &str);
// Return wxString from std::string in UTF8
wxString from_u8(const std::string &str);
void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer,
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();
ConfigOptionsGroup* get_optgroup();
// 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
void get_current_screen_size(unsigned &width, unsigned &height);
// Display an About dialog
extern void about();
// Ask the destop to open the datadir using the default file explorer.
extern void desktop_open_datadir_folder();
} // namespace GUI
} // namespace Slic3r
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,131 @@
#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 wxControl;
namespace Slic3r {
class ModelObject;
class Model;
namespace GUI {
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;
};
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);
// Remove objects/sub-object from the list
void remove();
//void create_double_slider(wxWindow* parent, wxControl* slider);
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();
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( wxWindow* parent, ModelObject* model_object,
wxArrayString& part_names, const bool is_modifier);
void load_lambda(wxWindow* parent, ModelObject* model_object,
wxArrayString& part_names, const bool is_modifier);
void on_btn_load(wxWindow* parent, 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);
} //namespace GUI
} //namespace Slic3r
#endif //slic3r_GUI_ObjectParts_hpp_

View File

@@ -0,0 +1,176 @@
#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)
{
Create(parent, wxID_ANY, _(L("Lambda Object")),
wxDefaultPosition, 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
m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxCHB_TOP);
sizer->Add(m_modificator_options_book, 1, wxEXPAND| wxALL, 10);
auto optgroup = init_modificator_options_page(_(L("Box")));
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);
};
ConfigOptionDef def;
def.width = 70;
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")));
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");
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")));
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");
option = Option(def, "sph_rho");
optgroup->append_single_option_line(option);
optgroup = init_modificator_options_page(_(L("Slab")));
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.label = L("H");
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;
}
}));
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(wxString title){
auto panel = new wxPanel(m_modificator_options_book);
ConfigOptionsGroupShp optgroup;
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Add")) + " " +title + " " +dots);
optgroup->label_width = 100;
m_optgroups.push_back(optgroup);
panel->SetSizerAndFit(optgroup->sizer);
m_modificator_options_book->AddPage(panel, title);
return optgroup;
}
} //namespace GUI
} //namespace Slic3r

View File

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

View File

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

View File

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

View File

@@ -22,15 +22,18 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
// 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>(m_parent, opt, id)));
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>(m_parent, opt, id)));
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>(m_parent, opt, id)));
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:
@@ -40,21 +43,21 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
case coPercents:
case coString:
case coStrings:
m_fields.emplace(id, STDMOVE(TextCtrl::Create<TextCtrl>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(TextCtrl::Create<TextCtrl>(parent(), opt, id)));
break;
case coBool:
case coBools:
m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(parent(), opt, id)));
break;
case coInt:
case coInts:
m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(parent(), opt, id)));
break;
case coEnum:
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
break;
case coPoints:
m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(parent(), opt, id)));
break;
case coNone: break;
default:
@@ -82,13 +85,28 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
if (!m_disabled)
this->back_to_initial_value(opt_id);
};
if (!m_is_tab_opt) field->m_Undo_btn->Hide();
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::append_line(const Line& line) {
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) {
@@ -109,27 +127,72 @@ void OptionsGroup::append_line(const Line& line) {
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);
sizer->Add(field->m_Undo_btn);
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))
sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
if (is_sizer_field(field))
sizer->Add(field->getSizer(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
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, 0);
}
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));
wxDefaultPosition, wxSize(label_width, -1), label_style);
label->SetFont(label_font);
label->Wrap(label_width); // avoid a Linux/GTK bug
grid_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL,0);
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);
}
@@ -137,67 +200,82 @@ void OptionsGroup::append_line(const Line& line) {
// If there's a widget, build it and add the result to the sizer.
if (line.widget != nullptr) {
auto wgt = line.widget(parent());
grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 5);
// 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 have a single option with no sidetext just add it directly to the grid sizer
auto sizer = new wxBoxSizer(wxHORIZONTAL);
grid_sizer->Add(sizer, 0, wxEXPAND | wxALL, 0);
// 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);
sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL);
add_undo_buttuns_to_sizer(sizer, field);
if (is_window_field(field))
sizer->Add(field->getWindow(), 0, (option.opt.full_width ? wxEXPAND : 0) |
wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, wxOSX ? 0 : 2);
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(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
sizer->Add(field->getSizer(), 1, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
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
for (auto opt : option_set) {
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 = L_str(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->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0);
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);
sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL, 0);
add_undo_buttuns_to_sizer(sizer_tmp, field);
is_sizer_field(field) ?
sizer->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) :
sizer->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0);
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, L_str(option.sidetext), wxDefaultPosition, wxDefaultSize);
auto sidetext = new wxStaticText( parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition,
wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT);
sidetext->SetFont(sidetext_font);
sizer->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
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->Add(opt.side_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification
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) //! istead of (opt != option_set.back())
if (opt.opt_id != option_set.back().opt_id && m_flag != ogSIDE_OPTIONS_VERTICAL) //! istead of (opt != option_set.back())
{
sizer->AddSpacer(6);
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()) {
@@ -206,22 +284,22 @@ void OptionsGroup::append_line(const Line& line) {
}
Line OptionsGroup::create_single_option_line(const Option& option) const {
Line retval{ L_str(option.opt.label), L_str(option.opt.tooltip) };
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(t_config_option_key id, /*config_value*/boost::any value) {
void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) {
if (m_on_change != nullptr)
m_on_change(id, value);
m_on_change(opt_id, value);
}
Option ConfigOptionsGroup::get_option(const std::string opt_key, int opt_index /*= -1*/)
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.";
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);
@@ -231,7 +309,7 @@ Option ConfigOptionsGroup::get_option(const std::string opt_key, int opt_index /
return Option(*m_config->def()->get(opt_key), opt_id);
}
void ConfigOptionsGroup::on_change_OG(t_config_option_key opt_id, boost::any value)
void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value)
{
if (!m_opt_map.empty())
{
@@ -254,16 +332,7 @@ void ConfigOptionsGroup::on_change_OG(t_config_option_key opt_id, boost::any val
if (opt_index != -1){
// die "Can't set serialized option indexed value" ;
}
// # Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values.
// # Currently used for the post_process config value only.
// my @values = split / ; / , $field_value;
// $self->config->set($opt_key, \@values);
std::string str = boost::any_cast<std::string>(value);
if (str.back() == ';')
str.pop_back();
std::vector<std::string> values;
boost::split(values, str, boost::is_any_of(";"));
change_opt_value(*m_config, opt_key, values);
change_opt_value(*m_config, opt_key, value);
}
else {
if (opt_index == -1) {
@@ -283,11 +352,24 @@ void ConfigOptionsGroup::on_change_OG(t_config_option_key opt_id, boost::any val
OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this
}
void ConfigOptionsGroup::back_to_initial_value(const std::string opt_key)
void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key)
{
if (m_get_initial_config == nullptr)
return;
DynamicPrintConfig config = m_get_initial_config();
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"));
@@ -300,15 +382,18 @@ void ConfigOptionsGroup::back_to_initial_value(const std::string opt_key)
int opt_index = m_opt_map.at(opt_id).second;
value = get_config_value(config, opt_short_key, opt_index);
}
else
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 (std::map< std::string, std::pair<std::string, int> >::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
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;
@@ -318,7 +403,7 @@ void ConfigOptionsGroup::reload_config(){
}
boost::any ConfigOptionsGroup::config_value(std::string opt_key, int opt_index, bool deserialize){
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.
@@ -335,7 +420,7 @@ boost::any ConfigOptionsGroup::config_value(std::string opt_key, int opt_index,
}
}
boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index/* = -1*/)
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;
@@ -375,12 +460,17 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
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;
for (auto el : values)
text_value += el + ";";
if (!values.empty() && values[0].compare("") != 0)
for (auto el : values)
text_value += el + ";";
ret = text_value;
}
else
@@ -409,12 +499,19 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
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)
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:
ret = config.option<ConfigOptionPoints>(opt_key)->get_at(idx);
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:
@@ -423,12 +520,12 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
return ret;
}
Field* ConfigOptionsGroup::get_fieldc(t_config_option_key opt_key, int opt_index){
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 (std::map< std::string, std::pair<std::string, int> >::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
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;
@@ -437,10 +534,10 @@ Field* ConfigOptionsGroup::get_fieldc(t_config_option_key opt_key, int opt_index
return opt_id.empty() ? nullptr : get_field(opt_id);
}
void ogStaticText::SetText(wxString value)
void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
{
SetLabel(value);
Wrap(400);
if (wrap) Wrap(400);
GetParent()->Layout();
}

View File

@@ -1,3 +1,6 @@
#ifndef slic3r_OptionsGroup_hpp_
#define slic3r_OptionsGroup_hpp_
#include <wx/wx.h>
#include <wx/stattext.h>
#include <wx/settings.h>
@@ -23,9 +26,13 @@
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*)>;
using column_t = std::function<wxSizer*(const Line&)>;
//auto default_label_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
//auto modified_label_clr = *new wxColour(254, 189, 101);
@@ -50,6 +57,7 @@ public:
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);
@@ -68,9 +76,13 @@ private:
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("")};
@@ -79,30 +91,44 @@ public:
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 { return m_parent; }
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);
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(t_config_option_key id) const{
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(t_config_option_key id, boost::any value, bool change_event = false) {
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(t_config_option_key id) {
boost::any get_value(const t_config_option_key& id) {
boost::any out;
if (m_fields.find(id) == m_fields.end()) ;
else
@@ -110,22 +136,51 @@ public:
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); }
OptionsGroup(wxWindow* _parent, wxString title, bool is_tab_opt=false) :
m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), staticbox(title!="") {
sizer = (staticbox ? new wxStaticBoxSizer(new wxStaticBox(_parent, wxID_ANY, title), wxVERTICAL) : new wxBoxSizer(wxVERTICAL));
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){
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, 0,0);
static_cast<wxFlexGridSizer*>(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL);
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);
sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0: 5);
#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};
@@ -137,7 +192,16 @@ protected:
bool m_disabled {false};
wxGridSizer* m_grid_sizer {nullptr};
// "true" if option is created in preset tabs
bool m_is_tab_opt{ false };
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
@@ -145,24 +209,27 @@ protected:
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(t_config_option_key opt_id, boost::any value);
virtual void back_to_initial_value(const std::string opt_key){};
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, wxString title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false) :
OptionsGroup(parent, title, is_tab_opt), m_config(_config) {}
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};
std::map< std::string, std::pair<std::string, int> > m_opt_map;
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 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);
}
@@ -175,14 +242,16 @@ public:
append_single_option_line(option);
}
void on_change_OG(t_config_option_key opt_id, boost::any value) override;
void back_to_initial_value(const std::string opt_key) override;
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(std::string opt_key, int opt_index, bool deserialize);
boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize);
// return option value from config
boost::any get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index = -1);
Field* get_fieldc(t_config_option_key opt_key, int opt_index);
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.
@@ -192,7 +261,9 @@ public:
ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){}
~ogStaticText(){}
void SetText(wxString value);
void SetText(const wxString& value, bool wrap = true);
};
}}
#endif /* slic3r_OptionsGroup_hpp_ */

View File

@@ -5,15 +5,22 @@
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 = 200;
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',
@@ -28,8 +35,8 @@ void PreferencesDialog::build()
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->get("remember_output_path")[0] == '1' }; // 1;
Option option(def, "remember_output_path");
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");
@@ -48,6 +55,22 @@ void PreferencesDialog::build()
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("Disable USB/serial connection");
def.type = coBool;
def.tooltip = L("Disable communication with the printer over a serial / USB cable. "

View File

@@ -1,3 +1,6 @@
#ifndef slic3r_Preferences_hpp_
#define slic3r_Preferences_hpp_
#include "GUI.hpp"
#include <wx/dialog.h>
@@ -14,8 +17,7 @@ class PreferencesDialog : public wxDialog
std::shared_ptr<ConfigOptionsGroup> m_optgroup;
int m_event_preferences;
public:
PreferencesDialog(wxWindow* parent, int event_preferences) : wxDialog(parent, wxID_ANY, _(L("Preferences")),
wxDefaultPosition, wxDefaultSize), m_event_preferences(event_preferences) { build(); }
PreferencesDialog(wxWindow* parent, int event_preferences);
~PreferencesDialog(){ }
void build();
@@ -25,3 +27,5 @@ public:
} // GUI
} // Slic3r
#endif /* slic3r_Preferences_hpp_ */

View File

@@ -2,9 +2,14 @@
#include <cassert>
#include "Preset.hpp"
#include "AppConfig.hpp"
#include "BitmapCache.hpp"
#include <fstream>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/cenv.hpp>
@@ -13,6 +18,7 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
#include <wx/image.h>
#include <wx/choice.h>
@@ -23,14 +29,16 @@
#include "../../libslic3r/Utils.hpp"
#include "../../libslic3r/PlaceholderParser.hpp"
using boost::property_tree::ptree;
namespace Slic3r {
ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree)
ConfigFileType guess_config_file_type(const ptree &tree)
{
size_t app_config = 0;
size_t bundle = 0;
size_t config = 0;
for (const boost::property_tree::ptree::value_type &v : tree) {
for (const ptree::value_type &v : tree) {
if (v.second.empty()) {
if (v.first == "background_processing" ||
v.first == "last_output_path" ||
@@ -58,12 +66,96 @@ ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree)
(bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG;
}
VendorProfile VendorProfile::from_ini(const boost::filesystem::path &path, bool load_all)
{
ptree tree;
boost::filesystem::ifstream ifs(path);
boost::property_tree::read_ini(ifs, tree);
return VendorProfile::from_ini(tree, path, load_all);
}
VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all)
{
static const std::string printer_model_key = "printer_model:";
const std::string id = path.stem().string();
if (! boost::filesystem::exists(path)) {
throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str());
}
VendorProfile res(id);
auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator
{
auto res = tree.find(key);
if (res == tree.not_found()) {
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str());
}
return res;
};
const auto &vendor_section = get_or_throw(tree, "vendor")->second;
res.name = get_or_throw(vendor_section, "name")->second.data();
auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data();
auto config_version = Semver::parse(config_version_str);
if (! config_version) {
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str());
} else {
res.config_version = std::move(*config_version);
}
auto config_update_url = vendor_section.find("config_update_url");
if (config_update_url != vendor_section.not_found()) {
res.config_update_url = config_update_url->second.data();
}
if (! load_all) {
return res;
}
for (auto &section : tree) {
if (boost::starts_with(section.first, printer_model_key)) {
VendorProfile::PrinterModel model;
model.id = section.first.substr(printer_model_key.size());
model.name = section.second.get<std::string>("name", model.id);
auto technology_field = section.second.get<std::string>("technology", "FFF");
if (! ConfigOptionEnum<PrinterTechnology>::from_string(technology_field, model.technology)) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Invalid printer technology field: `%2%`") % id % technology_field;
model.technology = ptFFF;
}
section.second.get<std::string>("variants", "");
const auto variants_field = section.second.get<std::string>("variants", "");
std::vector<std::string> variants;
if (Slic3r::unescape_strings_cstyle(variants_field, variants)) {
for (const std::string &variant_name : variants) {
if (model.variant(variant_name) == nullptr)
model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name));
}
} else {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field;
}
if (! model.id.empty() && ! model.variants.empty())
res.models.push_back(std::move(model));
}
}
return res;
}
// Suffix to be added to a modified preset name in the combo box.
static std::string g_suffix_modified = " (modified)";
const std::string& Preset::suffix_modified()
{
return g_suffix_modified;
}
void Preset::update_suffix_modified()
{
g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data();
}
// Remove an optional "(modified)" suffix from a name.
// This converts a UI name to a unique preset identifier.
std::string Preset::remove_suffix_modified(const std::string &name)
@@ -80,7 +172,7 @@ void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extr
auto *opt = config.option(key, false);
assert(opt != nullptr);
assert(opt->is_vector());
if (opt != nullptr && opt->is_vector())
if (opt != nullptr && opt->is_vector() && key != "default_filament_profile")
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
}
}
@@ -90,7 +182,7 @@ void Preset::normalize(DynamicPrintConfig &config)
{
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
if (nozzle_diameter != nullptr)
// Loaded the Printer settings. Verify, that all extruder dependent values have enough values.
// Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values.
set_num_extruders(config, (unsigned int)nozzle_diameter->values.size());
if (config.option("filament_diameter") != nullptr) {
// This config contains single or multiple filament presets.
@@ -101,20 +193,25 @@ void Preset::normalize(DynamicPrintConfig &config)
if (key == "compatible_printers")
continue;
auto *opt = config.option(key, false);
assert(opt != nullptr);
assert(opt->is_vector());
/*assert(opt != nullptr);
assert(opt->is_vector());*/
if (opt != nullptr && opt->is_vector())
static_cast<ConfigOptionVectorBase*>(opt)->resize(n, defaults.option(key));
}
// The following keys are mandatory for the UI, but they are not part of FullPrintConfig, therefore they are handled separately.
for (const std::string &key : { "filament_settings_id" }) {
auto *opt = config.option(key, false);
assert(opt != nullptr);
assert(opt->type() == coStrings);
if (opt != nullptr && opt->type() == coStrings)
static_cast<ConfigOptionStrings*>(opt)->values.resize(n, std::string());
}
}
}
// Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file.
// In case of a "default" config item, return the default values.
DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys)
DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys, const StaticPrintConfig &defaults)
{
// Set the configuration from the defaults.
Slic3r::FullPrintConfig defaults;
this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys);
if (! this->is_default) {
// Load the preset file, apply preset values on top of defaults.
@@ -144,12 +241,12 @@ std::string Preset::label() const
bool Preset::is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const
{
auto *condition = dynamic_cast<const ConfigOptionString*>(this->config.option("compatible_printers_condition"));
auto &condition = this->compatible_printers_condition();
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_printers"));
bool has_compatible_printers = compatible_printers != nullptr && ! compatible_printers->values.empty();
if (! has_compatible_printers && condition != nullptr && ! condition->value.empty()) {
if (! has_compatible_printers && ! condition.empty()) {
try {
return PlaceholderParser::evaluate_boolean_expression(condition->value, active_printer.config, extra_config);
return PlaceholderParser::evaluate_boolean_expression(condition, active_printer.config, extra_config);
} catch (const std::runtime_error &err) {
//FIXME in case of an error, return "compatible with everything".
printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.name.c_str(), err.what());
@@ -165,8 +262,9 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer) const
{
DynamicPrintConfig config;
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
config.set_key_value("num_extruders", new ConfigOptionInt(
(int)static_cast<const ConfigOptionFloats*>(active_printer.config.option("nozzle_diameter"))->values.size()));
const ConfigOption *opt = active_printer.config.option("nozzle_diameter");
if (opt)
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
return this->is_compatible_with_printer(active_printer, &config);
}
@@ -175,6 +273,15 @@ bool Preset::update_compatible_with_printer(const Preset &active_printer, const
return this->is_compatible = is_compatible_with_printer(active_printer, extra_config);
}
void Preset::set_visible_from_appconfig(const AppConfig &app_config)
{
if (vendor == nullptr) { return; }
const std::string &model = config.opt_string("printer_model");
const std::string &variant = config.opt_string("printer_variant");
if (model.empty() || variant.empty()) { return; }
is_visible = app_config.get_variant(vendor->id, model, variant);
}
const std::vector<std::string>& Preset::print_options()
{
static std::vector<std::string> s_opts {
@@ -199,8 +306,8 @@ const std::vector<std::string>& Preset::print_options()
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_per_color_wipe",
"compatible_printers", "compatible_printers_condition"
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming",
"compatible_printers", "compatible_printers_condition","inherits"
};
return s_opts;
}
@@ -208,12 +315,13 @@ const std::vector<std::string>& Preset::print_options()
const std::vector<std::string>& Preset::filament_options()
{
static std::vector<std::string> s_opts {
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
"extrusion_multiplier", "filament_density", "filament_cost", "temperature", "first_layer_temperature", "bed_temperature",
"first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed",
"disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode",
"end_filament_gcode",
"compatible_printers", "compatible_printers_condition"
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
"extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
"temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
"start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", "inherits"
};
return s_opts;
}
@@ -223,16 +331,26 @@ const std::vector<std::string>& Preset::printer_options()
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"printer_technology",
"bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
"octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"host_type", "print_host", "printhost_apikey", "printhost_cafile",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"between_objects_gcode", "printer_notes", "max_print_height"
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
"cooling_tube_length", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits",
"remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting",
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
"machine_min_extruding_rate", "machine_min_travel_rate",
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e"
};
s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end());
}
return s_opts;
}
// The following nozzle options of a printer profile will be adjusted to match the size
// of the nozzle_diameter vector.
const std::vector<std::string>& Preset::nozzle_options()
{
// ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
@@ -240,20 +358,53 @@ const std::vector<std::string>& Preset::nozzle_options()
"nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
"retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed",
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe",
"retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour"
"retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour",
"default_filament_profile"
};
return s_opts;
}
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys) :
const std::vector<std::string>& Preset::sla_printer_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"printer_technology",
"bed_shape", "max_print_height",
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
"printer_correction",
"printer_notes",
"inherits"
};
}
return s_opts;
}
const std::vector<std::string>& Preset::sla_material_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"layer_height", "initial_layer_height",
"exposure_time", "initial_exposure_time",
"material_correction_printing", "material_correction_curing",
"material_notes",
"compatible_printers",
"compatible_printers_condition", "inherits"
};
}
return s_opts;
}
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) :
m_type(type),
m_edited_preset(type, "", false),
m_idx_selected(0),
m_bitmap_main_frame(new wxBitmap)
m_bitmap_main_frame(new wxBitmap),
m_bitmap_cache(new GUI::BitmapCache)
{
// Insert just the default preset.
m_presets.emplace_back(Preset(type, "- default -", true));
m_presets.front().load(keys);
this->add_default_preset(keys, defaults, default_name);
m_edited_preset.config.apply(m_presets.front().config);
}
@@ -261,30 +412,39 @@ PresetCollection::~PresetCollection()
{
delete m_bitmap_main_frame;
m_bitmap_main_frame = nullptr;
delete m_bitmap_cache;
m_bitmap_cache = nullptr;
}
void PresetCollection::reset(bool delete_files)
{
if (m_presets.size() > 1) {
if (m_presets.size() > m_num_default_presets) {
if (delete_files) {
// Erase the preset files.
for (Preset &preset : m_presets)
if (! preset.is_default && ! preset.is_external)
if (! preset.is_default && ! preset.is_external && ! preset.is_system)
boost::nowide::remove(preset.file.c_str());
}
// Don't use m_presets.resize() here as it requires a default constructor for Preset.
m_presets.erase(m_presets.begin() + 1, m_presets.end());
m_presets.erase(m_presets.begin() + m_num_default_presets, m_presets.end());
this->select_preset(0);
}
}
void PresetCollection::add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name)
{
// Insert just the default preset.
m_presets.emplace_back(Preset(this->type(), preset_name, true));
m_presets.back().load(keys, defaults);
++ m_num_default_presets;
}
// Load all presets found in dir_path.
// Throws an exception on error.
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
{
boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred();
m_dir_path = dir.string();
m_presets.erase(m_presets.begin()+1, m_presets.end());
t_config_option_keys keys = this->default_preset().config.keys();
std::string errors_cummulative;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
@@ -292,17 +452,24 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
std::string name = dir_entry.path().filename().string();
// Remove the .ini suffix.
name.erase(name.size() - 4);
if (this->find_preset(name, false)) {
// This happens when there's is a preset (most likely legacy one) with the same name as a system preset
// that's already been loaded from a bundle.
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name;
continue;
}
try {
Preset preset(m_type, name, false);
preset.file = dir_entry.path().string();
preset.load(keys);
//FIXME One should initialize with SLAFullPrintConfig for the SLA profiles!
preset.load(keys, static_cast<const HostConfig&>(FullPrintConfig::defaults()));
m_presets.emplace_back(preset);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
}
}
std::sort(m_presets.begin() + 1, m_presets.end());
std::sort(m_presets.begin() + m_num_default_presets, m_presets.end());
this->select_preset(first_visible_idx());
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
@@ -314,7 +481,90 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string
{
DynamicPrintConfig cfg(this->default_preset().config);
cfg.apply_only(config, cfg.keys(), true);
return this->load_preset(path, name, std::move(cfg));
return this->load_preset(path, name, std::move(cfg), select);
}
static bool profile_print_params_same(const DynamicPrintConfig &cfg1, const DynamicPrintConfig &cfg2)
{
t_config_option_keys diff = cfg1.diff(cfg2);
// Following keys are used by the UI, not by the slicing core, therefore they are not important
// when comparing profiles for equality. Ignore them.
for (const char *key : { "compatible_printers", "compatible_printers_condition", "inherits",
"print_settings_id", "filament_settings_id", "printer_settings_id",
"printer_model", "printer_variant", "default_print_profile", "default_filament_profile" })
diff.erase(std::remove(diff.begin(), diff.end(), key), diff.end());
// Preset with the same name as stored inside the config exists.
return diff.empty();
}
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications.
// In case
Preset& PresetCollection::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)
{
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
DynamicPrintConfig cfg(this->default_preset().config);
cfg.apply_only(config, cfg.keys(), true);
// Is there a preset already loaded with the name stored inside the config?
std::deque<Preset>::iterator it = this->find_preset_internal(original_name);
if (it != m_presets.end() && it->name == original_name && profile_print_params_same(it->config, cfg)) {
// The preset exists and it matches the values stored inside config.
if (select)
this->select_preset(it - m_presets.begin());
return *it;
}
// Update the "inherits" field.
std::string &inherits = Preset::inherits(cfg);
if (it != m_presets.end() && inherits.empty()) {
// There is a profile with the same name already loaded. Should we update the "inherits" field?
if (it->vendor == nullptr)
inherits = it->inherits();
else
inherits = it->name;
}
// The external preset does not match an internal preset, load the external preset.
std::string new_name;
for (size_t idx = 0;; ++ idx) {
std::string suffix;
if (original_name.empty()) {
if (idx > 0)
suffix = " (" + std::to_string(idx) + ")";
} else {
if (idx == 0)
suffix = " (" + original_name + ")";
else
suffix = " (" + original_name + "-" + std::to_string(idx) + ")";
}
new_name = name + suffix;
it = this->find_preset_internal(new_name);
if (it == m_presets.end() || it->name != new_name)
// Unique profile name. Insert a new profile.
break;
if (profile_print_params_same(it->config, cfg)) {
// The preset exists and it matches the values stored inside config.
if (select)
this->select_preset(it - m_presets.begin());
return *it;
}
// Form another profile name.
}
// Insert a new profile.
Preset &preset = this->load_preset(path, new_name, std::move(cfg), select);
preset.is_external = true;
if (&this->get_selected_preset() == &preset)
this->get_edited_preset().is_external = true;
return preset;
}
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select)
@@ -342,16 +592,32 @@ void PresetCollection::save_current_preset(const std::string &new_name)
if (it != m_presets.end() && it->name == new_name) {
// Preset with the same name found.
Preset &preset = *it;
if (preset.is_default)
if (preset.is_default || preset.is_external || preset.is_system)
// Cannot overwrite the default preset.
return;
// Overwriting an existing preset.
preset.config = std::move(m_edited_preset.config);
} else {
// Creating a new preset.
Preset &preset = *m_presets.insert(it, m_edited_preset);
Preset &preset = *m_presets.insert(it, m_edited_preset);
std::string &inherits = preset.inherits();
std::string old_name = preset.name;
preset.name = new_name;
preset.file = this->path_from_name(new_name);
preset.vendor = nullptr;
if (preset.is_system) {
// Inheriting from a system preset.
inherits = /* preset.vendor->name + "/" + */ old_name;
} else if (inherits.empty()) {
// Inheriting from a user preset. Link the new preset to the old preset.
// inherits = old_name;
} else {
// Inherited from a user preset. Just maintain the "inherited" flag,
// meaning it will inherit from either the system preset, or the inherited user preset.
}
preset.is_default = false;
preset.is_system = false;
preset.is_external = false;
}
// 2) Activate the saved preset.
this->select_preset_by_name(new_name, true);
@@ -364,7 +630,7 @@ void PresetCollection::delete_current_preset()
const Preset &selected = this->get_selected_preset();
if (selected.is_default)
return;
if (! selected.is_external) {
if (! selected.is_external && ! selected.is_system) {
// Erase the preset file.
boost::nowide::remove(selected.file.c_str());
}
@@ -384,6 +650,29 @@ bool PresetCollection::load_bitmap_default(const std::string &file_name)
return m_bitmap_main_frame->LoadFile(wxString::FromUTF8(Slic3r::var(file_name).c_str()), wxBITMAP_TYPE_PNG);
}
const Preset* PresetCollection::get_selected_preset_parent() const
{
const std::string &inherits = this->get_edited_preset().inherits();
if (inherits.empty())
return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr;
const Preset* preset = this->find_preset(inherits, false);
return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset;
}
const Preset* PresetCollection::get_preset_parent(const Preset& child) const
{
const std::string &inherits = child.inherits();
if (inherits.empty())
// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr;
return nullptr;
const Preset* preset = this->find_preset(inherits, false);
return (preset == nullptr/* || preset->is_default */|| preset->is_external) ? nullptr : preset;
}
const std::string& PresetCollection::get_suffix_modified() {
return g_suffix_modified;
}
// 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* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found)
@@ -398,8 +687,8 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
size_t PresetCollection::first_visible_idx() const
{
size_t idx = m_default_suppressed ? 1 : 0;
for (; idx < m_presets.size(); ++ idx)
size_t idx = m_default_suppressed ? m_num_default_presets : 0;
for (; idx < this->m_presets.size(); ++ idx)
if (m_presets[idx].is_visible)
break;
if (idx == m_presets.size())
@@ -407,45 +696,32 @@ size_t PresetCollection::first_visible_idx() const
return idx;
}
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
size_t PresetCollection::first_compatible_idx() const
{
size_t idx = m_default_suppressed ? 1 : 0;
for (; idx < m_presets.size(); ++ idx)
if (m_presets[idx].is_compatible)
break;
if (idx == m_presets.size())
idx = 0;
return idx;
}
void PresetCollection::set_default_suppressed(bool default_suppressed)
{
if (m_default_suppressed != default_suppressed) {
m_default_suppressed = default_suppressed;
m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > 1 && m_idx_selected > 0);
m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > m_num_default_presets && m_idx_selected > 0);
}
}
void PresetCollection::update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible)
size_t PresetCollection::update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible)
{
DynamicPrintConfig config;
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
config.set_key_value("num_extruders", new ConfigOptionInt(
(int)static_cast<const ConfigOptionFloats*>(active_printer.config.option("nozzle_diameter"))->values.size()));
for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) {
const ConfigOption *opt = active_printer.config.option("nozzle_diameter");
if (opt)
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) {
bool selected = idx_preset == m_idx_selected;
Preset &preset_selected = m_presets[idx_preset];
Preset &preset_edited = selected ? m_edited_preset : preset_selected;
if (! preset_edited.update_compatible_with_printer(active_printer, &config) &&
selected && select_other_if_incompatible)
selected && unselect_if_incompatible)
m_idx_selected = (size_t)-1;
if (selected)
preset_selected.is_compatible = preset_edited.is_compatible;
}
if (m_idx_selected == (size_t)-1)
// Find some other compatible preset, or the "-- default --" preset.
this->select_preset(first_compatible_idx());
return m_idx_selected;
}
// Save the preset under a new name. If the name is different from the old one,
@@ -465,36 +741,131 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui)
// Otherwise fill in the list from scratch.
ui->Freeze();
ui->Clear();
for (size_t i = m_presets.front().is_visible ? 0 : 1; i < m_presets.size(); ++ i) {
const Preset &preset = m_presets[i];
size_t selected_preset_item = 0;
const Preset &selected_preset = this->get_selected_preset();
// Show wide icons if the currently selected preset is not compatible with the current printer,
// and draw a red flag in front of the selected preset.
bool wide_icons = !selected_preset.is_compatible && m_bitmap_incompatible != nullptr;
std::map<wxString, wxBitmap*> nonsys_presets;
wxString selected = "";
if (!this->m_presets.front().is_visible)
ui->Append("------- " +_(L("System presets")) + " -------", wxNullBitmap);
for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) {
const Preset &preset = this->m_presets[i];
if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected))
continue;
const wxBitmap *bmp = (i == 0 || preset.is_compatible) ? m_bitmap_main_frame : m_bitmap_incompatible;
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
if (i == m_idx_selected)
ui->SetSelection(ui->GetCount() - 1);
}
ui->Thaw();
std::string bitmap_key = "";
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
// to the filament color image.
if (wide_icons)
bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt";
bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst";
wxBitmap *bmp = m_bitmap_cache->find(bitmap_key);
if (bmp == nullptr) {
// Create the bitmap with color bars.
std::vector<wxBitmap> bmps;
if (wide_icons)
// Paint a red flag for incompatible presets.
bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(16, 16) : *m_bitmap_incompatible);
// Paint the color bars.
bmps.emplace_back(m_bitmap_cache->mkclear(4, 16));
bmps.emplace_back(*m_bitmap_main_frame);
// Paint a lock at the system presets.
bmps.emplace_back(m_bitmap_cache->mkclear(6, 16));
bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(16, 16));
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
}
if (preset.is_default || preset.is_system){
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
if (i == m_idx_selected)
selected_preset_item = ui->GetCount() - 1;
}
else
{
nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/);
if (i == m_idx_selected)
selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
}
if (i + 1 == m_num_default_presets)
ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap);
}
if (!nonsys_presets.empty())
{
ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap);
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
ui->Append(it->first, *it->second);
if (it->first == selected)
selected_preset_item = ui->GetCount() - 1;
}
}
ui->SetSelection(selected_preset_item);
ui->SetToolTip(ui->GetString(selected_preset_item));
ui->Thaw();
}
void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible)
size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible)
{
if (ui == nullptr)
return;
return 0;
ui->Freeze();
ui->Clear();
for (size_t i = m_presets.front().is_visible ? 0 : 1; i < m_presets.size(); ++ i) {
const Preset &preset = m_presets[i];
size_t selected_preset_item = 0;
std::map<wxString, wxBitmap*> nonsys_presets;
wxString selected = "";
if (!this->m_presets.front().is_visible)
ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap);
for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) {
const Preset &preset = this->m_presets[i];
if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected))
continue;
const wxBitmap *bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible;
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
if (i == m_idx_selected)
ui->SetSelection(ui->GetCount() - 1);
std::string bitmap_key = "tab";
bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt";
bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst";
wxBitmap *bmp = m_bitmap_cache->find(bitmap_key);
if (bmp == nullptr) {
// Create the bitmap with color bars.
std::vector<wxBitmap> bmps;
const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible;
bmps.emplace_back((tmp_bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *tmp_bmp);
// Paint a lock at the system presets.
bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(16, 16));
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
}
if (preset.is_default || preset.is_system){
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
if (i == m_idx_selected)
selected_preset_item = ui->GetCount() - 1;
}
else
{
nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/);
if (i == m_idx_selected)
selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
}
if (i + 1 == m_num_default_presets)
ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap);
}
if (!nonsys_presets.empty())
{
ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap);
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
ui->Append(it->first, *it->second);
if (it->first == selected)
selected_preset_item = ui->GetCount() - 1;
}
}
ui->SetSelection(selected_preset_item);
ui->SetToolTip(ui->GetString(selected_preset_item));
ui->Thaw();
return selected_preset_item;
}
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
@@ -527,16 +898,21 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui)
return was_dirty != is_dirty;
}
std::vector<std::string> PresetCollection::current_dirty_options() const
{
std::vector<std::string> changed = this->get_selected_preset().config.diff(this->get_edited_preset().config);
// The "compatible_printers" option key is handled differently from the others:
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
// If the key exists and it is empty, it means it is compatible with no printer.
std::initializer_list<const char*> optional_keys { "compatible_printers", "compatible_printers_condition" };
for (auto &opt_key : optional_keys) {
if (this->get_selected_preset().config.has(opt_key) != this->get_edited_preset().config.has(opt_key))
changed.emplace_back(opt_key);
std::vector<std::string> PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/)
{
std::vector<std::string> changed;
if (edited != nullptr && reference != nullptr) {
changed = deep_compare ?
reference->config.deep_diff(edited->config) :
reference->config.diff(edited->config);
// The "compatible_printers" option key is handled differently from the others:
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
// If the key exists and it is empty, it means it is compatible with no printer.
std::initializer_list<const char*> optional_keys { "compatible_printers" };
for (auto &opt_key : optional_keys) {
if (reference->config.has(opt_key) != edited->config.has(opt_key))
changed.emplace_back(opt_key);
}
}
return changed;
}
@@ -561,12 +937,12 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
// 1) Try to find the preset by its name.
auto it = this->find_preset_internal(name);
size_t idx = 0;
if (it != m_presets.end() && it->name == name)
// Preset found by its name.
if (it != m_presets.end() && it->name == name && it->is_visible)
// Preset found by its name and it is visible.
idx = it - m_presets.begin();
else {
// Find the first visible preset.
for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i)
for (size_t i = m_default_suppressed ? m_num_default_presets : 0; i < m_presets.size(); ++ i)
if (m_presets[i].is_visible) {
idx = i;
break;
@@ -583,6 +959,46 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
return false;
}
bool PresetCollection::select_preset_by_name_strict(const std::string &name)
{
// 1) Try to find the preset by its name.
auto it = this->find_preset_internal(name);
size_t idx = (size_t)-1;
if (it != m_presets.end() && it->name == name && it->is_visible)
// Preset found by its name.
idx = it - m_presets.begin();
// 2) Select the new preset.
if (idx != (size_t)-1) {
this->select_preset(idx);
return true;
}
m_idx_selected = idx;
return false;
}
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> PresetCollection::merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors)
{
std::vector<std::string> duplicates;
for (Preset &preset : other.m_presets) {
if (preset.is_default || preset.is_external)
continue;
Preset key(m_type, preset.name);
auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
if (it == m_presets.end() || it->name != preset.name) {
if (preset.vendor != nullptr) {
// Re-assign a pointer to the vendor structure in the new PresetBundle.
auto it = new_vendors.find(*preset.vendor);
assert(it != new_vendors.end());
preset.vendor = &(*it);
}
this->m_presets.emplace(it, std::move(preset));
} else
duplicates.emplace_back(std::move(preset.name));
}
return duplicates;
}
std::string PresetCollection::name() const
{
switch (this->type()) {

View File

@@ -3,8 +3,12 @@
#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;
@@ -13,6 +17,13 @@ class wxItemContainer;
namespace Slic3r {
class AppConfig;
class PresetBundle;
namespace GUI {
class BitmapCache;
}
enum ConfigFileType
{
CONFIG_FILE_TYPE_UNKNOWN,
@@ -23,6 +34,48 @@ enum ConfigFileType
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:
@@ -31,6 +84,7 @@ public:
TYPE_INVALID,
TYPE_PRINT,
TYPE_FILAMENT,
TYPE_SLA_MATERIAL,
TYPE_PRINTER,
};
@@ -44,7 +98,10 @@ public:
// External preset points to a configuration, which has been loaded but not imported
// into the Slic3r default configuration location.
bool is_external = false;
// Preset is visible, if it is compatible with the active Printer.
// 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?
@@ -55,9 +112,11 @@ public:
// 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 will be true),
// 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;
@@ -66,8 +125,7 @@ public:
DynamicPrintConfig config;
// Load this profile for the following keys only.
// Throws std::runtime_error in case the file cannot be read.
DynamicPrintConfig& load(const std::vector<std::string> &keys);
DynamicPrintConfig& load(const std::vector<std::string> &keys, const StaticPrintConfig &defaults);
void save();
@@ -82,9 +140,26 @@ public:
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); }
@@ -98,6 +173,11 @@ public:
// 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;
@@ -113,15 +193,25 @@ class PresetCollection
{
public:
// Initialize the PresetCollection with the "- default -" preset.
PresetCollection(Preset::Type type, const std::vector<std::string> &keys);
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);
@@ -130,6 +220,18 @@ public:
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.
@@ -144,6 +246,8 @@ public:
// 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);
@@ -155,10 +259,26 @@ public:
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; }
// Return the selected preset including the user modifications.
// 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]; }
@@ -172,31 +292,66 @@ public:
{ return const_cast<PresetCollection*>(this)->find_preset(name, first_visible_if_not_found); }
size_t first_visible_idx() const;
size_t first_compatible_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.
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible);
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;
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.
void update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible);
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.
@@ -214,23 +369,44 @@ public:
// 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 in the sorted list of presets.
// 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() + 1, m_presets.end(), key);
return ((it == m_presets.end() || it->name != name) && m_presets.front().name == name) ? m_presets.begin() : it;
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.
@@ -242,16 +418,25 @@ private:
// Selected preset.
int m_idx_selected;
// Is the "- default -" preset suppressed?
bool m_default_suppressed = true;
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_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

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,15 @@
#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.
@@ -22,11 +29,10 @@ public:
void setup_directories();
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
void load_presets();
// 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);
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.
@@ -34,13 +40,31 @@ public:
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.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; }
{ return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); }
DynamicPrintConfig full_config() const;
@@ -69,7 +93,10 @@ public:
// 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_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);
@@ -98,7 +125,22 @@ public:
// 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.
@@ -106,12 +148,19 @@ private:
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;
// Caching color bitmaps for the
std::map<std::string, wxBitmap*> m_mapColorToBitmap;
// 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

View File

@@ -0,0 +1,151 @@
#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_();
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 std::string& txt)
{
self->SetStatusText(txt);
}
void ProgressStatusBar::show_cancel_button()
{
cancelbutton_->Show();
}
void ProgressStatusBar::hide_cancel_button()
{
cancelbutton_->Hide();
}
}

View File

@@ -0,0 +1,64 @@
#ifndef PROGRESSSTATUSBAR_HPP
#define PROGRESSSTATUSBAR_HPP
#include <memory>
#include <functional>
class wxTimer;
class wxGauge;
class wxButton;
class wxTimerEvent;
class wxStatusBar;
class wxWindow;
class wxFrame;
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 std::string& txt);
// Temporary methods to satisfy Perl side
void show_cancel_button();
void hide_cancel_button();
private:
bool busy_ = false;
CancelFn cancel_cb_;
};
namespace GUI {
using Slic3r::ProgressStatusBar;
}
}
#endif // PROGRESSSTATUSBAR_HPP

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,6 @@
#ifndef slic3r_Tab_hpp_
#define slic3r_Tab_hpp_
// The "Expert" tab at the right of the main tabbed window.
//
// This file implements following packages:
@@ -21,6 +24,7 @@
#include <wx/treectrl.h>
#include <wx/imaglist.h>
#include <wx/statbox.h>
#include <wx/dataview.h>
#include <map>
#include <vector>
@@ -33,6 +37,9 @@
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>;
@@ -50,10 +57,14 @@ public:
{
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;
@@ -64,9 +75,25 @@ public:
size_t iconID() const { return m_iconID; }
void set_config(DynamicPrintConfig* config_in) { m_config = config_in; }
void reload_config();
Field* get_field(t_config_option_key opt_key, int opt_index = -1) const;
bool set_value(t_config_option_key opt_key, boost::any value);
ConfigOptionsGroupShp new_optgroup(wxString title, int noncommon_label_width = -1);
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;
@@ -75,14 +102,16 @@ 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;
wxBitmap* m_bmp_show_incompatible_presets;
wxBitmap* m_bmp_hide_incompatible_presets;
wxBitmapButton* m_btn_hide_incompatible_presets;
wxBoxSizer* m_hsizer;
wxBoxSizer* m_left_sizer;
@@ -90,35 +119,94 @@ protected:
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 in $self->{icons}.
std::vector<PageShp> m_pages; // $self->{pages} = [];
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;
bool m_no_controller;
std::vector<std::string> m_reload_dependent_tabs = {};
std::vector<std::string> m_dirty_options = {};
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, wxString title, const char* name, bool no_controller) :
Tab(wxNotebook* parent, const wxString& title, const char* name, bool no_controller) :
m_parent(parent), m_title(title), m_name(name), m_no_controller(no_controller) {
Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name);
get_tabs_list().push_back(this);
}
~Tab() { delete_tab_from_list(this); }
~Tab(){
delete_tab_from_list(this);
}
wxWindow* parent() const { return m_parent; }
wxString title() const { return m_title; }
@@ -130,12 +218,13 @@ public:
void create_preset_tab(PresetBundle *preset_bundle);
void load_current_preset();
void rebuild_page_tree();
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, std::string new_printer_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 load_key_value(std::string opt_key, boost::any value);
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);
@@ -146,34 +235,46 @@ public:
void toggle_show_hide_incompatible();
void update_show_hide_incompatible_button();
void update_ui_from_settings();
void update_labels_colour();
void update_changed_ui();
PageShp add_options_page(wxString title, std::string icon, bool is_extruder_pages = false);
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();
virtual void OnActivate(){}
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(DynamicPrintConfig config);
void load_config(const DynamicPrintConfig& config);
virtual void reload_config();
Field* get_field(t_config_option_key opt_key, int opt_index = -1) const;
bool set_value(t_config_option_key opt_key, boost::any value);
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;
}
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(std::string opt_key, boost::any value);
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;
@@ -214,24 +315,55 @@ public:
//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_octoprint_host_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;
std::vector<PageShp> m_extruder_pages;
size_t m_sys_extruders_count;
PrinterTechnology m_printer_technology = ptFFF;
TabPrinter() {}
TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {}
~TabPrinter(){}
void build() override;
void update() 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, bool no_controller) :
Tab(parent, _(L("SLA Material Settings")), "sla_material", no_controller) {}
~TabSLAMaterial(){}
void build() override;
void update() override;
void init_options_list() override;
};
class SavePresetWindow :public wxDialog
@@ -243,10 +375,12 @@ public:
std::string m_chosen_name;
wxComboBox* m_combo;
void build(wxString title, std::string default_name, std::vector<std::string> &values);
void build(const wxString& title, const std::string& default_name, std::vector<std::string> &values);
void accept();
std::string get_name() { return m_chosen_name; }
};
} // GUI
} // Slic3r
#endif /* slic3r_Tab_hpp_ */

View File

@@ -11,6 +11,7 @@ void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*con
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; }

View File

@@ -1,3 +1,6 @@
#ifndef slic3r_TabIface_hpp_
#define slic3r_TabIface_hpp_
#include <vector>
#include <string>
@@ -27,9 +30,12 @@ public:
DynamicPrintConfig* get_config();
PresetCollection* get_presets();
std::vector<std::string> get_dependent_tabs();
size_t get_selected_preset_item();
protected:
GUI::Tab *m_tab;
};
}; // namespace GUI
}; // namespace Slic3r
#endif /* slic3r_TabIface_hpp_ */

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,15 @@
#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
{
@@ -12,6 +21,21 @@ class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
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();
@@ -25,4 +49,629 @@ public:
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__
// *****************************************************************************
// ----------------------------------------------------------------------------
// 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;
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 wxIcon& icon,
const wxString& extruder,
const int volume_id=-1) {
m_parent = parent;
m_name = sub_obj_name;
m_copy = wxEmptyString;
m_icon = icon;
m_type = "volume";
m_volume_id = volume_id;
m_extruder = extruder;
set_part_action_icon();
}
~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;
wxString m_copy;
std::string m_type;
int m_volume_id;
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)
{
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:{
wxDataViewIconText data;
data << variant;
m_icon = data.GetIcon();
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 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();
};
// ----------------------------------------------------------------------------
// PrusaObjectDataViewModel
// ----------------------------------------------------------------------------
class PrusaObjectDataViewModel :public wxDataViewModel
{
std::vector<PrusaObjectDataViewModelNode*> m_objects;
public:
PrusaObjectDataViewModel(){}
~PrusaObjectDataViewModel()
{
for (auto object : m_objects)
delete object;
}
wxDataViewItem Add(const wxString &name);
wxDataViewItem Add(const wxString &name, const int instances_count);
wxDataViewItem AddChild(const wxDataViewItem &parent_item,
const wxString &name,
const wxIcon& icon,
const int = 0,
const bool create_frst_child = true);
wxDataViewItem Delete(const wxDataViewItem &item);
void DeleteAll();
void DeleteChildren(wxDataViewItem& parent);
wxDataViewItem GetItemById(int obj_idx);
int GetIdByItem(wxDataViewItem& item);
int GetVolumeIdByItem(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;
// 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; }
};
// ----------------------------------------------------------------------------
// 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_HORIZONTAL,
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;
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 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);
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_

View File

@@ -0,0 +1,71 @@
#ifndef IPROGRESSINDICATOR_HPP
#define IPROGRESSINDICATOR_HPP
#include <string>
#include <functional>
#include "Strings.hpp"
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 string&) = 0;
/// Title of the operation.
virtual void title(const string&) = 0;
/// Formatted message for the next status update. Works just like sprintf.
virtual void message_fmt(const 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 string& msg) {
message(msg); state(st);
}
};
}
#endif // IPROGRESSINDICATOR_HPP

10
xs/src/slic3r/Strings.hpp Normal file
View File

@@ -0,0 +1,10 @@
#ifndef STRINGS_HPP
#define STRINGS_HPP
#include "GUI/GUI.hpp"
namespace Slic3r {
using string = wxString;
}
#endif // STRINGS_HPP

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,13 +3,17 @@
#include <cstdlib>
#include <functional>
#include <thread>
#include <tuple>
#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 {
@@ -34,18 +38,32 @@ struct Http::priv
::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();
@@ -55,7 +73,9 @@ Http::priv::priv(const std::string &url) :
curl(::curl_easy_init()),
form(nullptr),
form_end(nullptr),
headerlist(nullptr)
headerlist(nullptr),
limit(0),
cancel(false)
{
if (curl == nullptr) {
throw std::runtime_error(std::string("Could not construct Curl object"));
@@ -112,6 +132,71 @@ size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
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%)")
@@ -127,10 +212,20 @@ std::string Http::priv::body_size_error()
void Http::priv::http_perform()
{
::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
::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);
@@ -144,24 +239,38 @@ void Http::priv::http_perform()
::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);
long http_status = 0;
::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
if (res != CURLE_OK) {
std::string error;
if (res == CURLE_WRITE_ERROR) {
error = std::move(body_size_error());
} else {
error = std::move(curl_error(res));
};
if (errorfn) {
errorfn(std::move(buffer), std::move(error), http_status);
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 {
if (completefn) {
completefn(std::move(buffer), http_status);
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); }
}
}
}
@@ -232,17 +341,21 @@ Http& Http::form_add(const std::string &name, const std::string &contents)
return *this;
}
Http& Http::form_add_file(const std::string &name, const std::string &filename)
Http& Http::form_add_file(const std::string &name, const fs::path &path)
{
if (p) {
::curl_formadd(&p->form, &p->form_end,
CURLFORM_COPYNAME, name.c_str(),
CURLFORM_FILE, filename.c_str(),
CURLFORM_CONTENTTYPE, "application/octet-stream",
CURLFORM_END
);
}
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;
}
@@ -258,6 +371,12 @@ Http& Http::on_error(ErrorFn 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));
@@ -277,6 +396,11 @@ 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)});
@@ -297,5 +421,31 @@ bool Http::ca_file_supported()
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;
}
}

View File

@@ -4,6 +4,7 @@
#include <memory>
#include <string>
#include <functional>
#include <boost/filesystem/path.hpp>
namespace Slic3r {
@@ -14,12 +15,38 @@ 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();
@@ -28,26 +55,60 @@ public:
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);
Http& form_add_file(const std::string &name, const std::string &filename);
// 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 &);
}

View File

@@ -1,74 +1,140 @@
#include "OctoPrint.hpp"
#include "PrintHostSendDialog.hpp"
#include <iostream>
#include <algorithm>
#include <boost/format.hpp>
#include <wx/frame.h>
#include <wx/event.h>
#include <boost/log/trivial.hpp>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "Http.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
host(config->opt_string("octoprint_host")),
apikey(config->opt_string("octoprint_apikey")),
cafile(config->opt_string("octoprint_cafile"))
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 url = std::move(make_url("api/version"));
auto http = Http::get(std::move(url));
set_auth(http);
http.on_error([&](std::string, std::string error, unsigned status) {
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(error, status);
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;
}
void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const
wxString OctoPrint::get_test_ok_msg () const
{
auto http = Http::post(std::move(make_url("api/files/local")));
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_file("file", filename)
.on_complete([=](std::string body, unsigned status) {
wxWindow *window = wxWindow::FindWindowById(windowId);
if (window == nullptr) { return; }
wxCommandEvent* evt = new wxCommandEvent(completeEvt);
evt->SetString(_(L("G-code file successfully uploaded to the OctoPrint server")));
evt->SetInt(100);
wxQueueEvent(window, evt);
.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) {
wxWindow *window = wxWindow::FindWindowById(windowId);
if (window == nullptr) { return; }
wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt);
evt_complete->SetInt(100);
wxQueueEvent(window, evt_complete);
wxCommandEvent* evt_error = new wxCommandEvent(errorEvt);
evt_error->SetString(wxString::Format("%s: %s",
_(L("Error while uploading to the OctoPrint server")),
format_error(error, status)));
wxQueueEvent(window, evt_error);
.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;
})
.perform();
.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
@@ -84,24 +150,22 @@ std::string OctoPrint::make_url(const std::string &path) const
{
if (host.find("http://") == 0 || host.find("https://") == 0) {
if (host.back() == '/') {
return std::move((boost::format("%1%%2%") % host % path).str());
return (boost::format("%1%%2%") % host % path).str();
} else {
return std::move((boost::format("%1%/%2%") % host % path).str());
return (boost::format("%1%/%2%") % host % path).str();
}
} else {
return std::move((boost::format("http://%1%/%2%") % host % path).str());
return (boost::format("http://%1%/%2%") % host % path).str();
}
}
wxString OctoPrint::format_error(std::string error, unsigned status)
wxString OctoPrint::format_error(const std::string &body, const std::string &error, unsigned status)
{
const wxString wxerror = error;
if (status != 0) {
return wxString::Format("HTTP %u: %s", status,
(status == 401 ? _(L("Invalid API key")) : wxerror));
auto wxbody = wxString::FromUTF8(body.data());
return wxString::Format("HTTP %u: %s", status, wxbody);
} else {
return std::move(wxerror);
return wxString::FromUTF8(error.data());
}
}

View File

@@ -4,6 +4,8 @@
#include <string>
#include <wx/string.h>
#include "PrintHost.hpp"
namespace Slic3r {
@@ -11,13 +13,19 @@ namespace Slic3r {
class DynamicPrintConfig;
class Http;
class OctoPrint
class OctoPrint : public PrintHost
{
public:
OctoPrint(DynamicPrintConfig *config);
virtual ~OctoPrint();
bool test(wxString &curl_msg) const;
void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) 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;
@@ -25,7 +33,7 @@ private:
void set_auth(Http &http) const;
std::string make_url(const std::string &path) const;
static wxString format_error(std::string error, unsigned status);
static wxString format_error(const std::string &body, const std::string &error, unsigned status);
};

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
#include "OctoPrint.hpp"
#include "Duet.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
PrintHost::~PrintHost() {}
PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
{
PrintHostType kind = config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
if (kind == htOctoPrint) {
return new OctoPrint(config);
} else if (kind == htDuet) {
return new Duet(config);
}
return nullptr;
}
}

View File

@@ -0,0 +1,35 @@
#ifndef slic3r_PrintHost_hpp_
#define slic3r_PrintHost_hpp_
#include <memory>
#include <string>
#include <wx/string.h>
namespace Slic3r {
class DynamicPrintConfig;
class PrintHost
{
public:
virtual ~PrintHost();
virtual bool test(wxString &curl_msg) const = 0;
virtual wxString get_test_ok_msg () const = 0;
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
// Send gcode file to print host, filename is expected to be in UTF-8
virtual bool send_gcode(const std::string &filename) const = 0;
virtual bool has_auto_discovery() const = 0;
virtual bool can_test() const = 0;
static PrintHost* get_print_host(DynamicPrintConfig *config);
};
}
#endif

View File

@@ -0,0 +1,52 @@
#include "PrintHostSendDialog.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 "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) :
MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE),
txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())),
box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))),
can_start_print(can_start_print)
{
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
label_dir_hint->Wrap(CONTENT_WIDTH);
content_sizer->Add(txt_filename, 0, wxEXPAND);
content_sizer->Add(label_dir_hint);
content_sizer->AddSpacer(VERT_SPACING);
content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
txt_filename->SetFocus();
wxString stem(path.stem().wstring());
txt_filename->SetSelection(0, stem.Length());
box_print->Enable(can_start_print);
Fit();
}
fs::path PrintHostSendDialog::filename() const
{
return fs::path(txt_filename->GetValue().wx_str());
}
bool PrintHostSendDialog::print() const
{
return box_print->GetValue(); }
}

View File

@@ -0,0 +1,38 @@
#ifndef slic3r_PrintHostSendDialog_hpp_
#define slic3r_PrintHostSendDialog_hpp_
#include <string>
#include <boost/filesystem/path.hpp>
#include <wx/string.h>
#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 "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
namespace Slic3r {
class PrintHostSendDialog : public GUI::MsgDialog
{
private:
wxTextCtrl *txt_filename;
wxCheckBox *box_print;
bool can_start_print;
public:
PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
boost::filesystem::path filename() const;
bool print() const;
};
}
#endif

View File

@@ -0,0 +1,151 @@
#ifndef slic3r_Semver_hpp_
#define slic3r_Semver_hpp_
#include <string>
#include <cstring>
#include <ostream>
#include <stdexcept>
#include <boost/optional.hpp>
#include <boost/format.hpp>
#include "semver/semver.h"
namespace Slic3r {
class Semver
{
public:
struct Major { const int i; Major(int i) : i(i) {} };
struct Minor { const int i; Minor(int i) : i(i) {} };
struct Patch { const int i; Patch(int i) : i(i) {} };
Semver() : ver(semver_zero()) {}
Semver(int major, int minor, int patch,
boost::optional<const std::string&> metadata = boost::none,
boost::optional<const std::string&> prerelease = boost::none)
: ver(semver_zero())
{
ver.major = major;
ver.minor = minor;
ver.patch = patch;
set_metadata(metadata);
set_prerelease(prerelease);
}
Semver(const std::string &str) : ver(semver_zero())
{
auto parsed = parse(str);
if (! parsed) {
throw std::runtime_error(std::string("Could not parse version string: ") + str);
}
ver = parsed->ver;
parsed->ver = semver_zero();
}
static boost::optional<Semver> parse(const std::string &str)
{
semver_t ver = semver_zero();
if (::semver_parse(str.c_str(), &ver) == 0) {
return Semver(ver);
} else {
return boost::none;
}
}
static const Semver zero() { return Semver(semver_zero()); }
static const Semver inf()
{
static semver_t ver = { std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), nullptr, nullptr };
return Semver(ver);
}
static const Semver invalid()
{
static semver_t ver = { -1, 0, 0, nullptr, nullptr };
return Semver(ver);
}
Semver(Semver &&other) : ver(other.ver) { other.ver = semver_zero(); }
Semver(const Semver &other) : ver(::semver_copy(&other.ver)) {}
Semver &operator=(Semver &&other)
{
::semver_free(&ver);
ver = other.ver;
other.ver = semver_zero();
return *this;
}
Semver &operator=(const Semver &other)
{
::semver_free(&ver);
ver = ::semver_copy(&other.ver);
return *this;
}
~Semver() { ::semver_free(&ver); }
// const accessors
int maj() const { return ver.major; }
int min() const { return ver.minor; }
int patch() const { return ver.patch; }
const char* prerelease() const { return ver.prerelease; }
const char* metadata() const { return ver.metadata; }
// Setters
void set_maj(int maj) { ver.major = maj; }
void set_min(int min) { ver.minor = min; }
void set_patch(int patch) { ver.patch = patch; }
void set_metadata(boost::optional<const std::string&> meta) { ver.metadata = meta ? strdup(*meta) : nullptr; }
void set_prerelease(boost::optional<const std::string&> pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; }
// Comparison
bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; }
bool operator<=(const Semver &b) const { return ::semver_compare(ver, b.ver) <= 0; }
bool operator==(const Semver &b) const { return ::semver_compare(ver, b.ver) == 0; }
bool operator!=(const Semver &b) const { return ::semver_compare(ver, b.ver) != 0; }
bool operator>=(const Semver &b) const { return ::semver_compare(ver, b.ver) >= 0; }
bool operator>(const Semver &b) const { return ::semver_compare(ver, b.ver) == 1; }
// We're using '&' instead of the '~' operator here as '~' is unary-only:
// Satisfies patch if Major and minor are equal.
bool operator&(const Semver &b) const { return ::semver_satisfies_patch(ver, b.ver); }
bool operator^(const Semver &b) const { return ::semver_satisfies_caret(ver, b.ver); }
bool in_range(const Semver &low, const Semver &high) const { return low <= *this && *this <= high; }
// Conversion
std::string to_string() const {
auto res = (boost::format("%1%.%2%.%3%") % ver.major % ver.minor % ver.patch).str();
if (ver.prerelease != nullptr) { res += '-'; res += ver.prerelease; }
if (ver.metadata != nullptr) { res += '+'; res += ver.metadata; }
return res;
}
// Arithmetics
Semver& operator+=(const Major &b) { ver.major += b.i; return *this; }
Semver& operator+=(const Minor &b) { ver.minor += b.i; return *this; }
Semver& operator+=(const Patch &b) { ver.patch += b.i; return *this; }
Semver& operator-=(const Major &b) { ver.major -= b.i; return *this; }
Semver& operator-=(const Minor &b) { ver.minor -= b.i; return *this; }
Semver& operator-=(const Patch &b) { ver.patch -= b.i; return *this; }
Semver operator+(const Major &b) const { Semver res(*this); return res += b; }
Semver operator+(const Minor &b) const { Semver res(*this); return res += b; }
Semver operator+(const Patch &b) const { Semver res(*this); return res += b; }
Semver operator-(const Major &b) const { Semver res(*this); return res -= b; }
Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; }
Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; }
private:
semver_t ver;
Semver(semver_t ver) : ver(ver) {}
static semver_t semver_zero() { return { 0, 0, 0, nullptr, nullptr }; }
static char * strdup(const std::string &str) { return ::semver_strdup(const_cast<char*>(str.c_str())); }
};
}
#endif

View File

@@ -0,0 +1,490 @@
#include "Serial.hpp"
#include <algorithm>
#include <string>
#include <vector>
#include <chrono>
#include <thread>
#include <fstream>
#include <stdexcept>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#include <boost/optional.hpp>
#if _WIN32
#include <Windows.h>
#include <Setupapi.h>
#include <initguid.h>
#include <devguid.h>
#include <regex>
// Undefine min/max macros incompatible with the standard library
// For example, std::numeric_limits<std::streamsize>::max()
// produces some weird errors
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#include "boost/nowide/convert.hpp"
#pragma comment(lib, "user32.lib")
#elif __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFString.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/serial/ioss.h>
#include <sys/syslimits.h>
#endif
#ifndef _WIN32
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/unistd.h>
#include <sys/select.h>
#endif
#if defined(__APPLE__) || defined(__OpenBSD__)
#include <termios.h>
#elif defined __linux__
#include <fcntl.h>
#include <asm-generic/ioctls.h>
#endif
using boost::optional;
namespace Slic3r {
namespace Utils {
static bool looks_like_printer(const std::string &friendly_name)
{
return friendly_name.find("Original Prusa") != std::string::npos;
}
#if _WIN32
void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi)
{
unsigned vid, pid;
std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*");
std::smatch matches;
if (std::regex_match(hardware_id, matches, pattern)) {
try {
vid = std::stoul(matches[1].str(), 0, 16);
pid = std::stoul(matches[2].str(), 0, 16);
spi.id_vendor = vid;
spi.id_product = pid;
}
catch (...) {}
}
}
#endif
#ifdef __linux__
optional<std::string> sysfs_tty_prop(const std::string &tty_dev, const std::string &name)
{
const auto prop_path = (boost::format("/sys/class/tty/%1%/device/../%2%") % tty_dev % name).str();
std::ifstream file(prop_path);
std::string res;
std::getline(file, res);
if (file.good()) { return res; }
else { return boost::none; }
}
optional<unsigned long> sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name)
{
auto prop = sysfs_tty_prop(tty_dev, name);
if (!prop) { return boost::none; }
try { return std::stoul(*prop, 0, 16); }
catch (...) { return boost::none; }
}
#endif
std::vector<SerialPortInfo> scan_serial_ports_extended()
{
std::vector<SerialPortInfo> output;
#ifdef _WIN32
SP_DEVINFO_DATA devInfoData = { 0 };
devInfoData.cbSize = sizeof(devInfoData);
// Get the tree containing the info for the ports.
HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, 0, nullptr, DIGCF_PRESENT);
if (hDeviceInfo != INVALID_HANDLE_VALUE) {
// Iterate over all the devices in the tree.
for (int nDevice = 0; SetupDiEnumDeviceInfo(hDeviceInfo, nDevice, &devInfoData); ++ nDevice) {
SerialPortInfo port_info;
// Get the registry key which stores the ports settings.
HKEY hDeviceKey = SetupDiOpenDevRegKey(hDeviceInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
if (hDeviceKey) {
// Read in the name of the port.
wchar_t pszPortName[4096];
DWORD dwSize = sizeof(pszPortName);
DWORD dwType = 0;
if (RegQueryValueEx(hDeviceKey, L"PortName", NULL, &dwType, (LPBYTE)pszPortName, &dwSize) == ERROR_SUCCESS)
port_info.port = boost::nowide::narrow(pszPortName);
RegCloseKey(hDeviceKey);
if (port_info.port.empty())
continue;
}
// Find the size required to hold the device info.
DWORD regDataType;
DWORD reqSize = 0;
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
std::vector<wchar_t> hardware_id(reqSize > 1 ? reqSize : 1);
// Now store it in a buffer.
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, &regDataType, (BYTE*)hardware_id.data(), reqSize, nullptr))
continue;
parse_hardware_id(boost::nowide::narrow(hardware_id.data()), port_info);
// Find the size required to hold the friendly name.
reqSize = 0;
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
std::vector<wchar_t> friendly_name;
friendly_name.reserve(reqSize > 1 ? reqSize : 1);
// Now store it in a buffer.
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, (BYTE*)friendly_name.data(), reqSize, nullptr)) {
port_info.friendly_name = port_info.port;
} else {
port_info.friendly_name = boost::nowide::narrow(friendly_name.data());
port_info.is_printer = looks_like_printer(port_info.friendly_name);
}
output.emplace_back(std::move(port_info));
}
}
#elif __APPLE__
// inspired by https://sigrok.org/wiki/Libserialport
CFMutableDictionaryRef classes = IOServiceMatching(kIOSerialBSDServiceValue);
if (classes != 0) {
io_iterator_t iter;
if (IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iter) == KERN_SUCCESS) {
io_object_t port;
while ((port = IOIteratorNext(iter)) != 0) {
CFTypeRef cf_property = IORegistryEntryCreateCFProperty(port, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
if (cf_property) {
char path[PATH_MAX];
Boolean result = CFStringGetCString((CFStringRef)cf_property, path, sizeof(path), kCFStringEncodingUTF8);
CFRelease(cf_property);
if (result) {
SerialPortInfo port_info;
port_info.port = path;
// Attempt to read out the device friendly name
if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
CFSTR("USB Interface Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
CFSTR("USB Product Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
CFSTR("Product Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntryCreateCFProperty(port,
CFSTR(kIOTTYDeviceKey), kCFAllocatorDefault, 0))) {
// Description limited to 127 char, anything longer would not be user friendly anyway.
char description[128];
if (CFStringGetCString((CFStringRef)cf_property, description, sizeof(description), kCFStringEncodingUTF8)) {
port_info.friendly_name = std::string(description) + " (" + port_info.port + ")";
port_info.is_printer = looks_like_printer(port_info.friendly_name);
}
CFRelease(cf_property);
}
if (port_info.friendly_name.empty())
port_info.friendly_name = port_info.port;
// Attempt to read out the VID & PID
int vid, pid;
auto cf_vendor = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idVendor"),
kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
auto cf_product = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idProduct"),
kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
if (cf_vendor && cf_product) {
if (CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberIntType, &vid) &&
CFNumberGetValue((CFNumberRef)cf_product, kCFNumberIntType, &pid)) {
port_info.id_vendor = vid;
port_info.id_product = pid;
}
}
if (cf_vendor) { CFRelease(cf_vendor); }
if (cf_product) { CFRelease(cf_product); }
output.emplace_back(std::move(port_info));
}
}
IOObjectRelease(port);
}
}
}
#else
// UNIX / Linux
std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" };
for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) {
std::string name = dir_entry.path().filename().string();
for (const char *prefix : prefixes) {
if (boost::starts_with(name, prefix)) {
const auto path = dir_entry.path().string();
SerialPortInfo spi;
spi.port = path;
#ifdef __linux__
auto friendly_name = sysfs_tty_prop(name, "product");
spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path;
auto vid = sysfs_tty_prop_hex(name, "idVendor");
auto pid = sysfs_tty_prop_hex(name, "idProduct");
if (vid && pid) {
spi.id_vendor = *vid;
spi.id_product = *pid;
}
#else
spi.friendly_name = path;
#endif
output.emplace_back(std::move(spi));
break;
}
}
}
#endif
output.erase(std::remove_if(output.begin(), output.end(),
[](const SerialPortInfo &info) {
return boost::starts_with(info.port, "Bluetooth") || boost::starts_with(info.port, "FireFly");
}),
output.end());
return output;
}
std::vector<std::string> scan_serial_ports()
{
std::vector<SerialPortInfo> ports = scan_serial_ports_extended();
std::vector<std::string> output;
output.reserve(ports.size());
for (const SerialPortInfo &spi : ports)
output.emplace_back(std::move(spi.port));
return output;
}
// Class Serial
namespace asio = boost::asio;
using boost::system::error_code;
Serial::Serial(asio::io_service& io_service) :
asio::serial_port(io_service)
{}
Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) :
asio::serial_port(io_service, name)
{
set_baud_rate(baud_rate);
}
Serial::~Serial() {}
void Serial::set_baud_rate(unsigned baud_rate)
{
try {
// This does not support speeds > 115200
set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
} catch (boost::system::system_error &) {
auto handle = native_handle();
auto handle_errno = [](int retval) {
if (retval != 0) {
throw std::runtime_error(
(boost::format("Could not set baud rate: %1%") % strerror(errno)).str()
);
}
};
#if __APPLE__
termios ios;
handle_errno(::tcgetattr(handle, &ios));
handle_errno(::cfsetspeed(&ios, baud_rate));
speed_t newSpeed = baud_rate;
handle_errno(::ioctl(handle, IOSSIOSPEED, &newSpeed));
handle_errno(::tcsetattr(handle, TCSANOW, &ios));
#elif __linux
/* The following definitions are kindly borrowed from:
/usr/include/asm-generic/termbits.h
Unfortunately we cannot just include that one because
it would redefine the "struct termios" already defined
the <termios.h> already included by Boost.ASIO. */
#define K_NCCS 19
struct termios2 {
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[K_NCCS];
speed_t c_ispeed;
speed_t c_ospeed;
};
#define BOTHER CBAUDEX
termios2 ios;
handle_errno(::ioctl(handle, TCGETS2, &ios));
ios.c_ispeed = ios.c_ospeed = baud_rate;
ios.c_cflag &= ~CBAUD;
ios.c_cflag |= BOTHER | CLOCAL | CREAD;
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
ios.c_cc[VTIME] = 1;
handle_errno(::ioctl(handle, TCSETS2, &ios));
#elif __OpenBSD__
struct termios ios;
handle_errno(::tcgetattr(handle, &ios));
handle_errno(::cfsetspeed(&ios, baud_rate));
handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios));
#else
throw std::runtime_error("Custom baud rates are not currently supported on this OS");
#endif
}
}
void Serial::set_DTR(bool on)
{
auto handle = native_handle();
#if defined(_WIN32) && !defined(__SYMBIAN32__)
if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) {
throw std::runtime_error("Could not set serial port DTR");
}
#else
int status;
if (::ioctl(handle, TIOCMGET, &status) == 0) {
on ? status |= TIOCM_DTR : status &= ~TIOCM_DTR;
if (::ioctl(handle, TIOCMSET, &status) == 0) {
return;
}
}
throw std::runtime_error(
(boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str()
);
#endif
}
void Serial::reset_line_num()
{
// See https://github.com/MarlinFirmware/Marlin/wiki/M110
write_string("M110 N0\n");
m_line_num = 0;
}
bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec)
{
auto &io_service = get_io_service();
asio::deadline_timer timer(io_service);
char c = 0;
bool fail = false;
while (true) {
io_service.reset();
asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) {
if (ec || size == 0) {
fail = true;
ec = read_ec; // FIXME: only if operation not aborted
}
timer.cancel(); // FIXME: ditto
});
if (timeout > 0) {
timer.expires_from_now(boost::posix_time::milliseconds(timeout));
timer.async_wait([&](const error_code &ec) {
// Ignore timer aborts
if (!ec) {
fail = true;
this->cancel();
}
});
}
io_service.run();
if (fail) {
return false;
} else if (c != '\n') {
line += c;
} else {
return true;
}
}
}
void Serial::printer_setup()
{
printer_reset();
write_string("\r\r\r\r\r\r\r\r\r\r"); // Gets rid of line noise, if any
}
size_t Serial::write_string(const std::string &str)
{
// TODO: might be wise to timeout here as well
return asio::write(*this, asio::buffer(str));
}
bool Serial::printer_ready_wait(unsigned retries, unsigned timeout)
{
std::string line;
error_code ec;
for (; retries > 0; retries--) {
reset_line_num();
while (read_line(timeout, line, ec)) {
if (line == "ok") {
return true;
}
line.clear();
}
line.clear();
}
return false;
}
size_t Serial::printer_write_line(const std::string &line, unsigned line_num)
{
const auto formatted_line = Utils::Serial::printer_format_line(line, line_num);
return write_string(formatted_line);
}
size_t Serial::printer_write_line(const std::string &line)
{
m_line_num++;
return printer_write_line(line, m_line_num);
}
void Serial::printer_reset()
{
this->set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
this->set_DTR(true);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
this->set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
std::string Serial::printer_format_line(const std::string &line, unsigned line_num)
{
const auto line_num_str = std::to_string(line_num);
unsigned checksum = 'N';
for (auto c : line_num_str) { checksum ^= c; }
checksum ^= ' ';
for (auto c : line) { checksum ^= c; }
return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str();
}
} // namespace Utils
} // namespace Slic3r

View File

@@ -0,0 +1,82 @@
#ifndef slic3r_GUI_Utils_Serial_hpp_
#define slic3r_GUI_Utils_Serial_hpp_
#include <vector>
#include <string>
#include <boost/system/error_code.hpp>
#include <boost/asio.hpp>
namespace Slic3r {
namespace Utils {
struct SerialPortInfo {
std::string port;
unsigned id_vendor = -1;
unsigned id_product = -1;
std::string friendly_name;
bool is_printer = false;
bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; }
};
inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2)
{
return
sp1.port == sp2.port &&
sp1.id_vendor == sp2.id_vendor &&
sp1.id_product == sp2.id_product &&
sp1.is_printer == sp2.is_printer;
}
extern std::vector<std::string> scan_serial_ports();
extern std::vector<SerialPortInfo> scan_serial_ports_extended();
class Serial : public boost::asio::serial_port
{
public:
Serial(boost::asio::io_service &io_service);
Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate);
Serial(const Serial &) = delete;
Serial &operator=(const Serial &) = delete;
~Serial();
void set_baud_rate(unsigned baud_rate);
void set_DTR(bool on);
// Resets the line number both internally as well as with the firmware using M110
void reset_line_num();
// Reads a line or times out, the timeout is in milliseconds
bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec);
// Perform an initial setup for communicating with a printer
void printer_setup();
// Write data from a string
size_t write_string(const std::string &str);
// Attempts to reset the line numer and waits until the printer says "ok"
bool printer_ready_wait(unsigned retries, unsigned timeout);
// Write Marlin-formatted line, with a line number and a checksum
size_t printer_write_line(const std::string &line, unsigned line_num);
// Same as above, but with internally-managed line number
size_t printer_write_line(const std::string &line);
// Toggles DTR to reset the printer
void printer_reset();
// Formats a line Marlin-style, ie. with a sequential number and a checksum
static std::string printer_format_line(const std::string &line, unsigned line_num);
private:
unsigned m_line_num = 0;
};
} // Utils
} // Slic3r
#endif /* slic3r_GUI_Utils_Serial_hpp_ */

View File

@@ -0,0 +1,80 @@
#include "Time.hpp"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#endif /* WIN32 */
namespace Slic3r {
namespace Utils {
time_t parse_time_ISO8601Z(const std::string &sdate)
{
int y, M, d, h, m, s;
if (sscanf(sdate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &y, &M, &d, &h, &m, &s) != 6)
return (time_t)-1;
struct tm tms;
tms.tm_year = y - 1900; // Year since 1900
tms.tm_mon = M - 1; // 0-11
tms.tm_mday = d; // 1-31
tms.tm_hour = h; // 0-23
tms.tm_min = m; // 0-59
tms.tm_sec = s; // 0-61 (0-60 in C++11)
return mktime(&tms);
}
std::string format_time_ISO8601Z(time_t time)
{
struct tm tms;
#ifdef WIN32
gmtime_s(&tms, &time);
#else
gmtime_r(&time, &tms);
#endif
char buf[128];
sprintf(buf, "%04d%02d%02dT%02d%02d%02dZ",
tms.tm_year + 1900,
tms.tm_mon + 1,
tms.tm_mday,
tms.tm_hour,
tms.tm_min,
tms.tm_sec);
return buf;
}
std::string format_local_date_time(time_t time)
{
struct tm tms;
#ifdef WIN32
localtime_s(&tms, &time);
#else
localtime_r(&time, &tms);
#endif
char buf[80];
strftime(buf, 80, "%x %X", &tms);
return buf;
}
time_t get_current_time_utc()
{
#ifdef WIN32
SYSTEMTIME st;
::GetSystemTime(&st);
std::tm tm;
tm.tm_sec = st.wSecond;
tm.tm_min = st.wMinute;
tm.tm_hour = st.wHour;
tm.tm_mday = st.wDay;
tm.tm_mon = st.wMonth - 1;
tm.tm_year = st.wYear - 1900;
tm.tm_isdst = -1;
return mktime(&tm);
#else
const time_t current_local = time(nullptr);
return mktime(gmtime(&current_local));
#endif
}
}; // namespace Utils
}; // namespace Slic3r

View File

@@ -0,0 +1,25 @@
#ifndef slic3r_Utils_Time_hpp_
#define slic3r_Utils_Time_hpp_
#include <string>
#include <time.h>
namespace Slic3r {
namespace Utils {
// Utilities to convert an UTC time_t to/from an ISO8601 time format,
// useful for putting timestamps into file and directory names.
// Returns (time_t)-1 on error.
extern time_t parse_time_ISO8601Z(const std::string &s);
extern std::string format_time_ISO8601Z(time_t time);
// Format the date and time from an UTC time according to the active locales and a local time zone.
extern std::string format_local_date_time(time_t time);
// There is no gmtime() on windows.
extern time_t get_current_time_utc();
}; // namespace Utils
}; // namespace Slic3r
#endif /* slic3r_Utils_Time_hpp_ */