mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-11-02 20:51:23 -07:00
Merged branch 'dev_native' into lm_sla_supports_auto
Added igl library files
This commit is contained in:
commit
7681d00ee5
2865 changed files with 142806 additions and 22325 deletions
364
src/slic3r/AppController.cpp
Normal file
364
src/slic3r/AppController.cpp
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
#include "AppController.hpp"
|
||||
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <cstdarg>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <PrintConfig.hpp>
|
||||
#include <Print.hpp>
|
||||
#include <PrintExport.hpp>
|
||||
#include <Geometry.hpp>
|
||||
#include <Model.hpp>
|
||||
#include <ModelArrange.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppControllerGui::PriData {
|
||||
public:
|
||||
std::mutex m;
|
||||
std::thread::id ui_thread;
|
||||
|
||||
inline explicit PriData(std::thread::id uit): ui_thread(uit) {}
|
||||
};
|
||||
|
||||
AppControllerGui::AppControllerGui()
|
||||
:m_pri_data(new PriData(std::this_thread::get_id())) {}
|
||||
|
||||
AppControllerGui::~AppControllerGui() {
|
||||
m_pri_data.reset();
|
||||
}
|
||||
|
||||
bool AppControllerGui::is_main_thread() const
|
||||
{
|
||||
return m_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;
|
||||
|
||||
ProgresIndicatorPtr AppControllerGui::global_progress_indicator() {
|
||||
ProgresIndicatorPtr ret;
|
||||
|
||||
m_pri_data->m.lock();
|
||||
ret = m_global_progressind;
|
||||
m_pri_data->m.unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AppControllerGui::global_progress_indicator(ProgresIndicatorPtr gpri)
|
||||
{
|
||||
m_pri_data->m.lock();
|
||||
m_global_progressind = gpri;
|
||||
m_pri_data->m.unlock();
|
||||
}
|
||||
|
||||
PrintController::PngExportData
|
||||
PrintController::query_png_export_data(const DynamicPrintConfig& conf)
|
||||
{
|
||||
PngExportData ret;
|
||||
|
||||
auto c = GUI::get_appctl();
|
||||
auto zippath = c->query_destination_path("Output zip file", "*.zip",
|
||||
"export-png",
|
||||
"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(ProgresIndicatorPtr pri)
|
||||
{
|
||||
m_print->set_status_callback([pri](int st, const std::string& msg){
|
||||
pri->update(unsigned(st), msg);
|
||||
});
|
||||
|
||||
m_print->process();
|
||||
}
|
||||
|
||||
void PrintController::slice()
|
||||
{
|
||||
auto ctl = GUI::get_appctl();
|
||||
auto pri = ctl->global_progress_indicator();
|
||||
if(!pri) pri = ctl->create_progress_indicator(100, L("Slicing"));
|
||||
slice(pri);
|
||||
}
|
||||
|
||||
template<> class LayerWriter<Zipper> {
|
||||
Zipper m_zip;
|
||||
public:
|
||||
|
||||
inline LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {}
|
||||
|
||||
inline void next_entry(const std::string& fname) { m_zip.next_entry(fname); }
|
||||
|
||||
inline std::string get_name() const { return m_zip.get_name(); }
|
||||
|
||||
template<class T> inline LayerWriter& operator<<(const T& arg) {
|
||||
m_zip.stream() << arg; return *this;
|
||||
}
|
||||
|
||||
inline void close() { m_zip.close(); }
|
||||
};
|
||||
|
||||
void PrintController::slice_to_png()
|
||||
{
|
||||
// using Pointf3 = Vec3d;
|
||||
|
||||
// auto ctl = GUI::get_appctl();
|
||||
// auto presetbundle = GUI::wxGetApp().preset_bundle;
|
||||
|
||||
// assert(presetbundle);
|
||||
|
||||
// // FIXME: this crashes in command line mode
|
||||
// auto pt = presetbundle->printers.get_selected_preset().printer_technology();
|
||||
// if(pt != ptSLA) {
|
||||
// ctl->report_issue(IssueType::ERR, L("Printer technology is not SLA!"),
|
||||
// L("Error"));
|
||||
// return;
|
||||
// }
|
||||
|
||||
// auto conf = presetbundle->full_config();
|
||||
// conf.validate();
|
||||
|
||||
// auto exd = query_png_export_data(conf);
|
||||
// if(exd.zippath.empty()) return;
|
||||
|
||||
// Print *print = m_print;
|
||||
|
||||
// try {
|
||||
// print->apply_config(conf);
|
||||
// print->validate();
|
||||
// } catch(std::exception& e) {
|
||||
// ctl->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, print, 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(!ctl->report_issue(IssueType::WARN_Q, ss.str(), L("Warning"))) {
|
||||
// scale_back();
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// auto pri = ctl->create_progress_indicator(
|
||||
// 200, L("Slicing to zipped png files..."));
|
||||
|
||||
// pri->on_cancel([&print](){ print->cancel(); });
|
||||
|
||||
// try {
|
||||
// pri->update(0, L("Slicing..."));
|
||||
// slice(pri);
|
||||
// } catch (std::exception& e) {
|
||||
// ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred"));
|
||||
// scale_back();
|
||||
// if(print->canceled()) print->restart();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// auto initstate = unsigned(pri->state());
|
||||
// print->set_status_callback([pri, initstate](int st, const std::string& msg)
|
||||
// {
|
||||
// pri->update(initstate + unsigned(st), msg);
|
||||
// });
|
||||
|
||||
// try {
|
||||
// print_to<FilePrinterFormat::PNG, Zipper>( *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) {
|
||||
// ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred"));
|
||||
// }
|
||||
|
||||
// scale_back();
|
||||
// if(print->canceled()) print->restart();
|
||||
// print->set_status_default();
|
||||
}
|
||||
|
||||
const PrintConfig &PrintController::config() const
|
||||
{
|
||||
return m_print->config();
|
||||
}
|
||||
|
||||
void ProgressIndicator::message_fmt(
|
||||
const std::string &fmtstr, ...) {
|
||||
std::stringstream ss;
|
||||
va_list args;
|
||||
va_start(args, fmtstr);
|
||||
|
||||
auto fmt = fmtstr.begin();
|
||||
|
||||
while (*fmt != '\0') {
|
||||
if (*fmt == 'd') {
|
||||
int i = va_arg(args, int);
|
||||
ss << i << '\n';
|
||||
} else if (*fmt == 'c') {
|
||||
// note automatic conversion to integral type
|
||||
int c = va_arg(args, int);
|
||||
ss << static_cast<char>(c) << '\n';
|
||||
} else if (*fmt == 'f') {
|
||||
double d = va_arg(args, double);
|
||||
ss << d << '\n';
|
||||
}
|
||||
++fmt;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
message(ss.str());
|
||||
}
|
||||
|
||||
void AppController::arrange_model()
|
||||
{
|
||||
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
|
||||
|
||||
auto ctl = GUI::get_appctl();
|
||||
|
||||
if(m_arranging.load()) return;
|
||||
|
||||
// to prevent UI reentrancies
|
||||
m_arranging.store(true);
|
||||
|
||||
unsigned count = 0;
|
||||
for(auto obj : m_model->objects) count += obj->instances.size();
|
||||
|
||||
auto pind = ctl->global_progress_indicator();
|
||||
|
||||
float pmax = 1.0;
|
||||
|
||||
if(pind) {
|
||||
pmax = pind->max();
|
||||
|
||||
// Set the range of the progress to the object count
|
||||
pind->max(count);
|
||||
|
||||
pind->on_cancel([this](){
|
||||
m_arranging.store(false);
|
||||
});
|
||||
}
|
||||
|
||||
auto dist = print_ctl()->config().min_object_distance();
|
||||
|
||||
// Create the arranger config
|
||||
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
||||
|
||||
auto& bedpoints = print_ctl()->config().bed_shape.values;
|
||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||
for(auto& v : bedpoints)
|
||||
bed.append(Point::new_scale(v(0), v(1)));
|
||||
|
||||
if(pind) pind->update(0, L("Arranging objects..."));
|
||||
|
||||
try {
|
||||
arr::BedShapeHint hint;
|
||||
// TODO: from Sasha from GUI
|
||||
hint.type = arr::BedShapeType::WHO_KNOWS;
|
||||
|
||||
arr::arrange(*m_model,
|
||||
min_obj_distance,
|
||||
bed,
|
||||
hint,
|
||||
false, // create many piles not just one pile
|
||||
[this, pind, &ctl, count](unsigned rem) {
|
||||
if(pind)
|
||||
pind->update(count - rem, L("Arranging objects..."));
|
||||
|
||||
ctl->process_events();
|
||||
}, [this] () { return !m_arranging.load(); });
|
||||
} catch(std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
ctl->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, m_arranging.load() ? L("Arranging done.") :
|
||||
L("Arranging canceled."));
|
||||
|
||||
pind->on_cancel(/*remove cancel function*/);
|
||||
}
|
||||
|
||||
m_arranging.store(false);
|
||||
}
|
||||
|
||||
}
|
||||
414
src/slic3r/AppController.hpp
Normal file
414
src/slic3r/AppController.hpp
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
#ifndef APPCONTROLLER_HPP
|
||||
#define APPCONTROLLER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
|
||||
#include "GUI/ProgressIndicator.hpp"
|
||||
|
||||
#include <PrintConfig.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class PrintConfig;
|
||||
class ProgressStatusBar;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
/// A Progress indicator object smart pointer
|
||||
using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>;
|
||||
|
||||
using FilePath = std::string;
|
||||
using FilePathList = std::vector<FilePath>;
|
||||
|
||||
/// Common runtime issue types
|
||||
enum class IssueType {
|
||||
INFO,
|
||||
WARN,
|
||||
WARN_Q, // Warning with a question to continue
|
||||
ERR,
|
||||
FATAL
|
||||
};
|
||||
|
||||
/**
|
||||
* @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 AppControllerBase {
|
||||
public:
|
||||
|
||||
using Ptr = std::shared_ptr<AppControllerBase>;
|
||||
|
||||
inline virtual ~AppControllerBase() {}
|
||||
|
||||
/**
|
||||
* @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 chosen by the user.
|
||||
*/
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Same as query_destination_paths but works for directories only.
|
||||
*/
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& title,
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Same as query_destination_paths but returns only one path.
|
||||
*/
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
virtual bool report_issue(IssueType issuetype,
|
||||
const std::string& description,
|
||||
const std::string& brief) = 0;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() = 0;
|
||||
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr gpri) = 0;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
virtual bool is_main_thread() const = 0;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
virtual bool supports_asynch() const = 0;
|
||||
|
||||
virtual void process_events() = 0;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title,
|
||||
const std::string& firstmsg = "") const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation of AppControllerBase for the GUI app
|
||||
*/
|
||||
class AppControllerGui: public AppControllerBase {
|
||||
private:
|
||||
class PriData; // Some structure to store progress indication data
|
||||
|
||||
// Pimpl data for thread safe progress indication features
|
||||
std::unique_ptr<PriData> m_pri_data;
|
||||
|
||||
public:
|
||||
|
||||
AppControllerGui();
|
||||
|
||||
virtual ~AppControllerGui();
|
||||
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid,
|
||||
const std::string& hint) const override;
|
||||
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid,
|
||||
const std::string& hint) const override;
|
||||
|
||||
virtual bool report_issue(IssueType issuetype,
|
||||
const std::string& description,
|
||||
const std::string& brief = std::string()) override;
|
||||
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() override;
|
||||
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr gpri) override;
|
||||
|
||||
virtual bool is_main_thread() const override;
|
||||
|
||||
virtual bool supports_asynch() const override;
|
||||
|
||||
virtual void process_events() override;
|
||||
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title,
|
||||
const std::string& firstmsg) const override;
|
||||
|
||||
protected:
|
||||
|
||||
// This is a global progress indicator placeholder. In the Slic3r UI it can
|
||||
// contain the progress indicator on the statusbar.
|
||||
ProgresIndicatorPtr m_global_progressind;
|
||||
};
|
||||
|
||||
class AppControllerCli: public AppControllerBase {
|
||||
|
||||
class CliProgress : public ProgressIndicator {
|
||||
std::string m_msg, m_title;
|
||||
public:
|
||||
virtual void message(const std::string& msg) override {
|
||||
m_msg = msg;
|
||||
}
|
||||
|
||||
virtual void title(const std::string& title) override {
|
||||
m_title = title;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
AppControllerCli() {
|
||||
std::cout << "Cli AppController ready..." << std::endl;
|
||||
m_global_progressind = std::make_shared<CliProgress>();
|
||||
}
|
||||
|
||||
virtual ~AppControllerCli() {}
|
||||
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*extensions*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*extensions*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return "out.zip"; }
|
||||
|
||||
virtual bool report_issue(IssueType /*issuetype*/,
|
||||
const std::string& description,
|
||||
const std::string& brief) override {
|
||||
std::cerr << brief << ": " << description << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() override {
|
||||
return m_global_progressind;
|
||||
}
|
||||
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr) override {}
|
||||
|
||||
virtual bool is_main_thread() const override { return true; }
|
||||
|
||||
virtual bool supports_asynch() const override { return false; }
|
||||
|
||||
virtual void process_events() override {}
|
||||
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned /*statenum*/,
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*firstmsg*/) const override {
|
||||
return std::make_shared<CliProgress>();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// This is a global progress indicator placeholder. In the Slic3r UI it can
|
||||
// contain the progress indicator on the statusbar.
|
||||
ProgresIndicatorPtr m_global_progressind;
|
||||
};
|
||||
|
||||
class Zipper {
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
public:
|
||||
|
||||
Zipper(const std::string& zipfilepath);
|
||||
~Zipper();
|
||||
|
||||
void next_entry(const std::string& fname);
|
||||
|
||||
std::string get_name() const;
|
||||
|
||||
std::ostream& stream();
|
||||
|
||||
void close();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation of the printing logic.
|
||||
*/
|
||||
class PrintController {
|
||||
Print *m_print = nullptr;
|
||||
std::function<void()> m_rempools;
|
||||
protected:
|
||||
|
||||
// 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 m_prev_expdata;
|
||||
|
||||
void slice(ProgresIndicatorPtr pri);
|
||||
|
||||
public:
|
||||
|
||||
// Must be public for perl to use it
|
||||
explicit inline PrintController(Print *print): m_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;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Top level controller.
|
||||
*/
|
||||
class AppController {
|
||||
Model *m_model = nullptr;
|
||||
PrintController::Ptr printctl;
|
||||
std::atomic<bool> m_arranging;
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Get the print controller object.
|
||||
*
|
||||
* @return Return a raw pointer instead of a smart one for perl to be able
|
||||
* to use this function and access the print controller.
|
||||
*/
|
||||
PrintController * print_ctl() { return printctl.get(); }
|
||||
|
||||
/**
|
||||
* @brief Set a model object.
|
||||
*
|
||||
* @param model A raw pointer to the model object. This can be used from
|
||||
* perl.
|
||||
*/
|
||||
void set_model(Model *model) { m_model = model; }
|
||||
|
||||
/**
|
||||
* @brief Set the print object from perl.
|
||||
*
|
||||
* This will create a print controller that will then be accessible from
|
||||
* perl.
|
||||
* @param print A print object which can be a perl-ish extension as well.
|
||||
*/
|
||||
void set_print(Print *print) {
|
||||
printctl = PrintController::create(print);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set up a global progress indicator.
|
||||
*
|
||||
* In perl we have a progress indicating status bar on the bottom of the
|
||||
* window which is defined and created in perl. We can pass the ID-s of the
|
||||
* gauge and the statusbar id and make a wrapper implementation of the
|
||||
* ProgressIndicator interface so we can use this GUI widget from C++.
|
||||
*
|
||||
* This function should be called from perl.
|
||||
*
|
||||
* @param gauge_id The ID of the gague widget of the status bar.
|
||||
* @param statusbar_id The ID of the status bar.
|
||||
*/
|
||||
void set_global_progress_indicator(ProgressStatusBar *prs);
|
||||
|
||||
void arrange_model();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // APPCONTROLLER_HPP
|
||||
340
src/slic3r/AppControllerWx.cpp
Normal file
340
src/slic3r/AppControllerWx.cpp
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
#include "AppController.hpp"
|
||||
|
||||
#include <wx/stdstream.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/zipstrm.h>
|
||||
|
||||
#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 AppControllerGui::supports_asynch() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppControllerGui::process_events()
|
||||
{
|
||||
wxYieldIfNeeded();
|
||||
}
|
||||
|
||||
FilePathList AppControllerGui::query_destination_paths(
|
||||
const std::string &title,
|
||||
const std::string &extensions,
|
||||
const std::string &/*functionid*/,
|
||||
const std::string& hint) const
|
||||
{
|
||||
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
|
||||
dlg.SetWildcard(extensions);
|
||||
|
||||
dlg.SetFilename(hint);
|
||||
|
||||
FilePathList ret;
|
||||
|
||||
if(dlg.ShowModal() == wxID_OK) {
|
||||
wxArrayString paths;
|
||||
dlg.GetPaths(paths);
|
||||
for(auto& p : paths) ret.push_back(p.ToStdString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FilePath AppControllerGui::query_destination_path(
|
||||
const std::string &title,
|
||||
const std::string &extensions,
|
||||
const std::string &/*functionid*/,
|
||||
const std::string& hint) const
|
||||
{
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
|
||||
dlg.SetWildcard(extensions);
|
||||
|
||||
dlg.SetFilename(hint);
|
||||
|
||||
FilePath ret;
|
||||
|
||||
if(dlg.ShowModal() == wxID_OK) {
|
||||
ret = FilePath(dlg.GetPath());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AppControllerGui::report_issue(IssueType issuetype,
|
||||
const std::string &description,
|
||||
const std::string &brief)
|
||||
{
|
||||
auto icon = wxICON_INFORMATION;
|
||||
auto style = wxOK|wxCENTRE;
|
||||
switch(issuetype) {
|
||||
case IssueType::INFO: break;
|
||||
case IssueType::WARN: icon = wxICON_WARNING; break;
|
||||
case IssueType::WARN_Q: icon = wxICON_WARNING; style |= wxCANCEL; break;
|
||||
case IssueType::ERR:
|
||||
case IssueType::FATAL: icon = wxICON_ERROR;
|
||||
}
|
||||
|
||||
auto ret = wxMessageBox(_(description), _(brief), icon | style);
|
||||
return ret != wxCANCEL;
|
||||
}
|
||||
|
||||
wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
|
||||
|
||||
struct Zipper::Impl {
|
||||
wxFileName fpath;
|
||||
wxFFileOutputStream zipfile;
|
||||
wxZipOutputStream zipstream;
|
||||
wxStdOutputStream pngstream;
|
||||
|
||||
Impl(const std::string& zipfile_path):
|
||||
fpath(zipfile_path),
|
||||
zipfile(zipfile_path),
|
||||
zipstream(zipfile),
|
||||
pngstream(zipstream)
|
||||
{
|
||||
if(!zipfile.IsOk())
|
||||
throw std::runtime_error(L("Cannot create zip file."));
|
||||
}
|
||||
};
|
||||
|
||||
Zipper::Zipper(const std::string &zipfilepath)
|
||||
{
|
||||
m_impl.reset(new Impl(zipfilepath));
|
||||
}
|
||||
|
||||
Zipper::~Zipper() {}
|
||||
|
||||
void Zipper::next_entry(const std::string &fname)
|
||||
{
|
||||
m_impl->zipstream.PutNextEntry(fname);
|
||||
}
|
||||
|
||||
std::string Zipper::get_name() const
|
||||
{
|
||||
return m_impl->fpath.GetName().ToStdString();
|
||||
}
|
||||
|
||||
std::ostream &Zipper::stream()
|
||||
{
|
||||
return m_impl->pngstream;
|
||||
}
|
||||
|
||||
void Zipper::close()
|
||||
{
|
||||
m_impl->zipstream.Close();
|
||||
m_impl->zipfile.Close();
|
||||
}
|
||||
|
||||
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 m_gauge;
|
||||
using Base = ProgressIndicator;
|
||||
wxString m_message;
|
||||
int m_range; wxString m_title;
|
||||
bool m_is_asynch = false;
|
||||
|
||||
const int m_id = wxWindow::NewControlId();
|
||||
|
||||
// status update handler
|
||||
void _state( wxCommandEvent& evt) {
|
||||
unsigned st = evt.GetInt();
|
||||
m_message = evt.GetString();
|
||||
_state(st);
|
||||
}
|
||||
|
||||
// Status update implementation
|
||||
void _state( unsigned st) {
|
||||
if(!m_gauge.IsShown()) m_gauge.ShowModal();
|
||||
Base::state(st);
|
||||
if(!m_gauge.Update(static_cast<int>(st), m_message)) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// Setting whether it will be used from the UI thread or some worker thread
|
||||
inline void asynch(bool is) { m_is_asynch = is; }
|
||||
|
||||
/// Get the mode of parallel operation.
|
||||
inline bool asynch() const { return m_is_asynch; }
|
||||
|
||||
inline GuiProgressIndicator(int range, const wxString& title,
|
||||
const wxString& firstmsg) :
|
||||
m_gauge(title, firstmsg, range, wxTheApp->GetTopWindow(),
|
||||
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT),
|
||||
|
||||
m_message(firstmsg),
|
||||
m_range(range), m_title(title)
|
||||
{
|
||||
Base::max(static_cast<float>(range));
|
||||
Base::states(static_cast<unsigned>(range));
|
||||
|
||||
Bind(PROGRESS_STATUS_UPDATE_EVENT,
|
||||
&GuiProgressIndicator::_state,
|
||||
this, m_id);
|
||||
}
|
||||
|
||||
virtual void state(float val) override {
|
||||
state(static_cast<unsigned>(val));
|
||||
}
|
||||
|
||||
void state(unsigned st) {
|
||||
// send status update event
|
||||
if(m_is_asynch) {
|
||||
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, m_id);
|
||||
evt->SetInt(st);
|
||||
evt->SetString(m_message);
|
||||
wxQueueEvent(this, evt);
|
||||
} else _state(st);
|
||||
}
|
||||
|
||||
virtual void message(const std::string & msg) override {
|
||||
m_message = _(msg);
|
||||
}
|
||||
|
||||
virtual void messageFmt(const std::string& fmt, ...) {
|
||||
va_list arglist;
|
||||
va_start(arglist, fmt);
|
||||
m_message = wxString::Format(_(fmt), arglist);
|
||||
va_end(arglist);
|
||||
}
|
||||
|
||||
virtual void title(const std::string & title) override {
|
||||
m_title = _(title);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ProgresIndicatorPtr AppControllerGui::create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title,
|
||||
const std::string& firstmsg) const
|
||||
{
|
||||
auto pri =
|
||||
std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg);
|
||||
|
||||
// We set up the mode of operation depending of the creator thread's
|
||||
// identity
|
||||
pri->asynch(!is_main_thread());
|
||||
|
||||
return pri;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class Wrapper: public ProgressIndicator, public wxEvtHandler {
|
||||
ProgressStatusBar *m_sbar;
|
||||
using Base = ProgressIndicator;
|
||||
wxString m_message;
|
||||
AppControllerBase& m_ctl;
|
||||
|
||||
void showProgress(bool show = true) {
|
||||
m_sbar->show_progress(show);
|
||||
}
|
||||
|
||||
void _state(unsigned st) {
|
||||
if( st <= ProgressIndicator::max() ) {
|
||||
Base::state(st);
|
||||
m_sbar->set_status_text(m_message);
|
||||
m_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,
|
||||
AppControllerBase& ctl):
|
||||
m_sbar(sbar), m_ctl(ctl)
|
||||
{
|
||||
Base::max(static_cast<float>(m_sbar->get_range()));
|
||||
Base::states(static_cast<unsigned>(m_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) {
|
||||
m_sbar->set_range(static_cast<int>(val));
|
||||
ProgressIndicator::max(val);
|
||||
}
|
||||
}
|
||||
|
||||
void state(unsigned st) {
|
||||
if(!m_ctl.is_main_thread()) {
|
||||
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_);
|
||||
evt->SetInt(st);
|
||||
wxQueueEvent(this, evt);
|
||||
} else {
|
||||
_state(st);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void message(const std::string & msg) override {
|
||||
m_message = _(msg);
|
||||
}
|
||||
|
||||
virtual void message_fmt(const std::string& fmt, ...) override {
|
||||
va_list arglist;
|
||||
va_start(arglist, fmt);
|
||||
m_message = wxString::Format(_(fmt), arglist);
|
||||
va_end(arglist);
|
||||
}
|
||||
|
||||
virtual void title(const std::string & /*title*/) override {}
|
||||
|
||||
virtual void on_cancel(CancelFn fn) override {
|
||||
m_sbar->set_cancel_callback(fn);
|
||||
Base::on_cancel(fn);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
void AppController::set_global_progress_indicator(ProgressStatusBar *prsb)
|
||||
{
|
||||
if(prsb) {
|
||||
auto ctl = GUI::get_appctl();
|
||||
ctl->global_progress_indicator(std::make_shared<Wrapper>(prsb, *ctl));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
122
src/slic3r/CMakeLists.txt
Normal file
122
src/slic3r/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
add_library(libslic3r_gui STATIC
|
||||
${LIBDIR}/slic3r/GUI/AboutDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/AboutDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/SysInfoDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/SysInfoDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/AppConfig.cpp
|
||||
${LIBDIR}/slic3r/GUI/AppConfig.hpp
|
||||
${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.cpp
|
||||
${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.hpp
|
||||
${LIBDIR}/slic3r/GUI/BitmapCache.cpp
|
||||
${LIBDIR}/slic3r/GUI/BitmapCache.hpp
|
||||
${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/3DScene.cpp
|
||||
${LIBDIR}/slic3r/GUI/3DScene.hpp
|
||||
${LIBDIR}/slic3r/GUI/GLShader.cpp
|
||||
${LIBDIR}/slic3r/GUI/GLShader.hpp
|
||||
${LIBDIR}/slic3r/GUI/GLCanvas3D.hpp
|
||||
${LIBDIR}/slic3r/GUI/GLCanvas3D.cpp
|
||||
${LIBDIR}/slic3r/GUI/GLCanvas3DManager.hpp
|
||||
${LIBDIR}/slic3r/GUI/GLCanvas3DManager.cpp
|
||||
${LIBDIR}/slic3r/GUI/GLGizmo.hpp
|
||||
${LIBDIR}/slic3r/GUI/GLGizmo.cpp
|
||||
${LIBDIR}/slic3r/GUI/GLTexture.hpp
|
||||
${LIBDIR}/slic3r/GUI/GLTexture.cpp
|
||||
${LIBDIR}/slic3r/GUI/GLToolbar.hpp
|
||||
${LIBDIR}/slic3r/GUI/GLToolbar.cpp
|
||||
${LIBDIR}/slic3r/GUI/Preferences.cpp
|
||||
${LIBDIR}/slic3r/GUI/Preferences.hpp
|
||||
${LIBDIR}/slic3r/GUI/Preset.cpp
|
||||
${LIBDIR}/slic3r/GUI/Preset.hpp
|
||||
${LIBDIR}/slic3r/GUI/PresetBundle.cpp
|
||||
${LIBDIR}/slic3r/GUI/PresetBundle.hpp
|
||||
${LIBDIR}/slic3r/GUI/PresetHints.cpp
|
||||
${LIBDIR}/slic3r/GUI/PresetHints.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_Preview.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_Preview.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_PreviewIface.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_PreviewIface.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_App.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_App.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_Utils.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_Utils.hpp
|
||||
${LIBDIR}/slic3r/GUI/MainFrame.cpp
|
||||
${LIBDIR}/slic3r/GUI/MainFrame.hpp
|
||||
${LIBDIR}/slic3r/GUI/Plater.cpp
|
||||
${LIBDIR}/slic3r/GUI/Plater.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_ObjectList.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_ObjectList.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_ObjectManipulation.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_ObjectManipulation.hpp
|
||||
${LIBDIR}/slic3r/GUI/LambdaObjectDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/LambdaObjectDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/Tab.cpp
|
||||
${LIBDIR}/slic3r/GUI/Tab.hpp
|
||||
${LIBDIR}/slic3r/GUI/TabIface.cpp
|
||||
${LIBDIR}/slic3r/GUI/TabIface.hpp
|
||||
${LIBDIR}/slic3r/GUI/Field.cpp
|
||||
${LIBDIR}/slic3r/GUI/Field.hpp
|
||||
${LIBDIR}/slic3r/GUI/OptionsGroup.cpp
|
||||
${LIBDIR}/slic3r/GUI/OptionsGroup.hpp
|
||||
${LIBDIR}/slic3r/GUI/BedShapeDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/BedShapeDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/2DBed.cpp
|
||||
${LIBDIR}/slic3r/GUI/2DBed.hpp
|
||||
${LIBDIR}/slic3r/GUI/wxExtensions.cpp
|
||||
${LIBDIR}/slic3r/GUI/wxExtensions.hpp
|
||||
${LIBDIR}/slic3r/GUI/WipeTowerDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/WipeTowerDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/RammingChart.cpp
|
||||
${LIBDIR}/slic3r/GUI/RammingChart.hpp
|
||||
${LIBDIR}/slic3r/GUI/BonjourDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/BonjourDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/ButtonsDescription.cpp
|
||||
${LIBDIR}/slic3r/GUI/ButtonsDescription.hpp
|
||||
${LIBDIR}/slic3r/Config/Snapshot.cpp
|
||||
${LIBDIR}/slic3r/Config/Snapshot.hpp
|
||||
${LIBDIR}/slic3r/Config/Version.cpp
|
||||
${LIBDIR}/slic3r/Config/Version.hpp
|
||||
${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp
|
||||
${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp
|
||||
${LIBDIR}/slic3r/Utils/Serial.cpp
|
||||
${LIBDIR}/slic3r/Utils/Serial.hpp
|
||||
${LIBDIR}/slic3r/GUI/ConfigWizard.cpp
|
||||
${LIBDIR}/slic3r/GUI/ConfigWizard.hpp
|
||||
${LIBDIR}/slic3r/GUI/MsgDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/MsgDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/UpdateDialogs.cpp
|
||||
${LIBDIR}/slic3r/GUI/UpdateDialogs.hpp
|
||||
${LIBDIR}/slic3r/GUI/FirmwareDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/FirmwareDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/ProgressIndicator.hpp
|
||||
${LIBDIR}/slic3r/GUI/ProgressStatusBar.hpp
|
||||
${LIBDIR}/slic3r/GUI/ProgressStatusBar.cpp
|
||||
${LIBDIR}/slic3r/Utils/Http.cpp
|
||||
${LIBDIR}/slic3r/Utils/Http.hpp
|
||||
${LIBDIR}/slic3r/Utils/FixModelByWin10.cpp
|
||||
${LIBDIR}/slic3r/Utils/FixModelByWin10.hpp
|
||||
${LIBDIR}/slic3r/Utils/PrintHostSendDialog.cpp
|
||||
${LIBDIR}/slic3r/Utils/PrintHostSendDialog.hpp
|
||||
${LIBDIR}/slic3r/Utils/OctoPrint.cpp
|
||||
${LIBDIR}/slic3r/Utils/OctoPrint.hpp
|
||||
${LIBDIR}/slic3r/Utils/Duet.cpp
|
||||
${LIBDIR}/slic3r/Utils/Duet.hpp
|
||||
${LIBDIR}/slic3r/Utils/PrintHost.cpp
|
||||
${LIBDIR}/slic3r/Utils/PrintHost.hpp
|
||||
${LIBDIR}/slic3r/Utils/Bonjour.cpp
|
||||
${LIBDIR}/slic3r/Utils/Bonjour.hpp
|
||||
${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
|
||||
${LIBDIR}/slic3r/Utils/PresetUpdater.hpp
|
||||
${LIBDIR}/slic3r/Utils/Time.cpp
|
||||
${LIBDIR}/slic3r/Utils/Time.hpp
|
||||
${LIBDIR}/slic3r/Utils/HexFile.cpp
|
||||
${LIBDIR}/slic3r/Utils/HexFile.hpp
|
||||
${LIBDIR}/slic3r/AppController.hpp
|
||||
${LIBDIR}/slic3r/AppController.cpp
|
||||
${LIBDIR}/slic3r/AppControllerWx.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude)
|
||||
532
src/slic3r/Config/Snapshot.cpp
Normal file
532
src/slic3r/Config/Snapshot.cpp
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
#include "Snapshot.hpp"
|
||||
#include "../GUI/AppConfig.hpp"
|
||||
#include "../GUI/PresetBundle.hpp"
|
||||
#include "../Utils/Time.hpp"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Config.hpp"
|
||||
#include "../../libslic3r/FileParserError.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
|
||||
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
|
||||
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
namespace Config {
|
||||
|
||||
void Snapshot::clear()
|
||||
{
|
||||
this->id.clear();
|
||||
this->time_captured = 0;
|
||||
this->slic3r_version_captured = Semver::invalid();
|
||||
this->comment.clear();
|
||||
this->reason = SNAPSHOT_UNKNOWN;
|
||||
this->print.clear();
|
||||
this->filaments.clear();
|
||||
this->printer.clear();
|
||||
}
|
||||
|
||||
void Snapshot::load_ini(const std::string &path)
|
||||
{
|
||||
this->clear();
|
||||
|
||||
auto throw_on_parse_error = [&path](const std::string &msg) {
|
||||
throw file_parser_error(std::string("Failed loading the snapshot file. Reason: ") + msg, path);
|
||||
};
|
||||
|
||||
// Load the snapshot.ini file.
|
||||
boost::property_tree::ptree tree;
|
||||
try {
|
||||
boost::nowide::ifstream ifs(path);
|
||||
boost::property_tree::read_ini(ifs, tree);
|
||||
} catch (const std::ifstream::failure &err) {
|
||||
throw file_parser_error(std::string("The snapshot file cannot be loaded. Reason: ") + err.what(), path);
|
||||
} catch (const std::runtime_error &err) {
|
||||
throw_on_parse_error(err.what());
|
||||
}
|
||||
|
||||
// Parse snapshot.ini
|
||||
std::string group_name_vendor = "Vendor:";
|
||||
std::string key_filament = "filament";
|
||||
std::string key_prefix_model = "model_";
|
||||
for (auto §ion : tree) {
|
||||
if (section.first == "snapshot") {
|
||||
// Parse the common section.
|
||||
for (auto &kvp : section.second) {
|
||||
if (kvp.first == "id")
|
||||
this->id = kvp.second.data();
|
||||
else if (kvp.first == "time_captured") {
|
||||
this->time_captured = Slic3r::Utils::parse_time_ISO8601Z(kvp.second.data());
|
||||
if (this->time_captured == (time_t)-1)
|
||||
throw_on_parse_error("invalid timestamp");
|
||||
} else if (kvp.first == "slic3r_version_captured") {
|
||||
auto semver = Semver::parse(kvp.second.data());
|
||||
if (! semver)
|
||||
throw_on_parse_error("invalid slic3r_version_captured semver");
|
||||
this->slic3r_version_captured = *semver;
|
||||
} else if (kvp.first == "comment") {
|
||||
this->comment = kvp.second.data();
|
||||
} else if (kvp.first == "reason") {
|
||||
std::string rsn = kvp.second.data();
|
||||
if (rsn == "upgrade")
|
||||
this->reason = SNAPSHOT_UPGRADE;
|
||||
else if (rsn == "downgrade")
|
||||
this->reason = SNAPSHOT_DOWNGRADE;
|
||||
else if (rsn == "before_rollback")
|
||||
this->reason = SNAPSHOT_BEFORE_ROLLBACK;
|
||||
else if (rsn == "user")
|
||||
this->reason = SNAPSHOT_USER;
|
||||
else
|
||||
this->reason = SNAPSHOT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
} else if (section.first == "presets") {
|
||||
// Load the names of the active presets.
|
||||
for (auto &kvp : section.second) {
|
||||
if (kvp.first == "print") {
|
||||
this->print = kvp.second.data();
|
||||
} else if (boost::starts_with(kvp.first, "filament")) {
|
||||
int idx = 0;
|
||||
if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) {
|
||||
if (int(this->filaments.size()) <= idx)
|
||||
this->filaments.resize(idx + 1, std::string());
|
||||
this->filaments[idx] = kvp.second.data();
|
||||
}
|
||||
} else if (kvp.first == "printer") {
|
||||
this->printer = kvp.second.data();
|
||||
}
|
||||
}
|
||||
} else if (boost::starts_with(section.first, group_name_vendor) && section.first.size() > group_name_vendor.size()) {
|
||||
// Vendor specific section.
|
||||
VendorConfig vc;
|
||||
vc.name = section.first.substr(group_name_vendor.size());
|
||||
for (auto &kvp : section.second) {
|
||||
if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") {
|
||||
// Version of the vendor specific config bundle bundled with this snapshot.
|
||||
auto semver = Semver::parse(kvp.second.data());
|
||||
if (! semver)
|
||||
throw_on_parse_error("invalid " + kvp.first + " format for " + section.first);
|
||||
if (kvp.first == "version")
|
||||
vc.version.config_version = *semver;
|
||||
else if (kvp.first == "min_slic3r_version")
|
||||
vc.version.min_slic3r_version = *semver;
|
||||
else
|
||||
vc.version.max_slic3r_version = *semver;
|
||||
} else if (boost::starts_with(kvp.first, key_prefix_model) && kvp.first.size() > key_prefix_model.size()) {
|
||||
// Parse the printer variants installed for the current model.
|
||||
auto &set_variants = vc.models_variants_installed[kvp.first.substr(key_prefix_model.size())];
|
||||
std::vector<std::string> variants;
|
||||
if (unescape_strings_cstyle(kvp.second.data(), variants))
|
||||
for (auto &variant : variants)
|
||||
set_variants.insert(std::move(variant));
|
||||
}
|
||||
}
|
||||
this->vendor_configs.emplace_back(std::move(vc));
|
||||
}
|
||||
}
|
||||
// Sort the vendors lexicographically.
|
||||
std::sort(this->vendor_configs.begin(), this->vendor_configs.begin(),
|
||||
[](const VendorConfig &cfg1, const VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
|
||||
}
|
||||
|
||||
static std::string reason_string(const Snapshot::Reason reason)
|
||||
{
|
||||
switch (reason) {
|
||||
case Snapshot::SNAPSHOT_UPGRADE:
|
||||
return "upgrade";
|
||||
case Snapshot::SNAPSHOT_DOWNGRADE:
|
||||
return "downgrade";
|
||||
case Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
|
||||
return "before_rollback";
|
||||
case Snapshot::SNAPSHOT_USER:
|
||||
return "user";
|
||||
case Snapshot::SNAPSHOT_UNKNOWN:
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void Snapshot::save_ini(const std::string &path)
|
||||
{
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path, std::ios::out | std::ios::trunc);
|
||||
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
|
||||
// Export the common "snapshot".
|
||||
c << std::endl << "[snapshot]" << std::endl;
|
||||
c << "id = " << this->id << std::endl;
|
||||
c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl;
|
||||
c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
|
||||
c << "comment = " << this->comment << std::endl;
|
||||
c << "reason = " << reason_string(this->reason) << std::endl;
|
||||
|
||||
// Export the active presets at the time of the snapshot.
|
||||
c << std::endl << "[presets]" << std::endl;
|
||||
c << "print = " << this->print << std::endl;
|
||||
c << "filament = " << this->filaments.front() << std::endl;
|
||||
for (size_t i = 1; i < this->filaments.size(); ++ i)
|
||||
c << "filament_" << std::to_string(i) << " = " << this->filaments[i] << std::endl;
|
||||
c << "printer = " << this->printer << std::endl;
|
||||
|
||||
// Export the vendor configs.
|
||||
for (const VendorConfig &vc : this->vendor_configs) {
|
||||
c << std::endl << "[Vendor:" << vc.name << "]" << std::endl;
|
||||
c << "version = " << vc.version.config_version.to_string() << std::endl;
|
||||
c << "min_slic3r_version = " << vc.version.min_slic3r_version.to_string() << std::endl;
|
||||
c << "max_slic3r_version = " << vc.version.max_slic3r_version.to_string() << std::endl;
|
||||
// Export installed printer models and their variants.
|
||||
for (const auto &model : vc.models_variants_installed) {
|
||||
if (model.second.size() == 0)
|
||||
continue;
|
||||
const std::vector<std::string> variants(model.second.begin(), model.second.end());
|
||||
const auto escaped = escape_strings_cstyle(variants);
|
||||
c << "model_" << model.first << " = " << escaped << std::endl;
|
||||
}
|
||||
}
|
||||
c.close();
|
||||
}
|
||||
|
||||
void Snapshot::export_selections(AppConfig &config) const
|
||||
{
|
||||
assert(filaments.size() >= 1);
|
||||
config.clear_section("presets");
|
||||
config.set("presets", "print", print);
|
||||
config.set("presets", "filament", filaments.front());
|
||||
for (int i = 1; i < filaments.size(); ++i) {
|
||||
char name[64];
|
||||
sprintf(name, "filament_%d", i);
|
||||
config.set("presets", name, filaments[i]);
|
||||
}
|
||||
config.set("presets", "printer", printer);
|
||||
}
|
||||
|
||||
void Snapshot::export_vendor_configs(AppConfig &config) const
|
||||
{
|
||||
std::map<std::string, std::map<std::string, std::set<std::string>>> vendors;
|
||||
for (const VendorConfig &vc : vendor_configs)
|
||||
vendors[vc.name] = vc.models_variants_installed;
|
||||
config.set_vendors(std::move(vendors));
|
||||
}
|
||||
|
||||
// Perform a deep compare of the active print / filament / printer / vendor directories.
|
||||
// Return true if the content of the current print / filament / printer / vendor directories
|
||||
// matches the state stored in this snapshot.
|
||||
bool Snapshot::equal_to_active(const AppConfig &app_config) const
|
||||
{
|
||||
// 1) Check, whether this snapshot contains the same set of active vendors, printer models and variants
|
||||
// as app_config.
|
||||
{
|
||||
std::set<std::string> matched;
|
||||
for (const VendorConfig &vc : this->vendor_configs) {
|
||||
auto it_vendor_models_variants = app_config.vendors().find(vc.name);
|
||||
if (it_vendor_models_variants == app_config.vendors().end() ||
|
||||
it_vendor_models_variants->second != vc.models_variants_installed)
|
||||
// There are more vendors enabled in the snapshot than currently installed.
|
||||
return false;
|
||||
matched.insert(vc.name);
|
||||
}
|
||||
for (const std::pair<std::string, std::map<std::string, std::set<std::string>>> &v : app_config.vendors())
|
||||
if (matched.find(v.first) == matched.end() && ! v.second.empty())
|
||||
// There are more vendors currently installed than enabled in the snapshot.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2) Check, whether this snapshot references the same set of ini files as the current state.
|
||||
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||
boost::filesystem::path snapshot_dir = boost::filesystem::path(Slic3r::data_dir()) / SLIC3R_SNAPSHOTS_DIR / this->id;
|
||||
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
|
||||
boost::filesystem::path path1 = data_dir / subdir;
|
||||
boost::filesystem::path path2 = snapshot_dir / subdir;
|
||||
std::vector<std::string> files1, files2;
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(path1))
|
||||
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
|
||||
files1.emplace_back(dir_entry.path().filename().string());
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(path2))
|
||||
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
|
||||
files2.emplace_back(dir_entry.path().filename().string());
|
||||
std::sort(files1.begin(), files1.end());
|
||||
std::sort(files2.begin(), files2.end());
|
||||
if (files1 != files2)
|
||||
return false;
|
||||
for (const std::string &filename : files1) {
|
||||
FILE *f1 = boost::nowide::fopen((path1 / filename).string().c_str(), "rb");
|
||||
FILE *f2 = boost::nowide::fopen((path2 / filename).string().c_str(), "rb");
|
||||
bool same = true;
|
||||
if (f1 && f2) {
|
||||
char buf1[4096];
|
||||
char buf2[4096];
|
||||
do {
|
||||
size_t r1 = fread(buf1, 1, 4096, f1);
|
||||
size_t r2 = fread(buf2, 1, 4096, f2);
|
||||
if (r1 != r2 || memcmp(buf1, buf2, r1)) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
} while (! feof(f1) || ! feof(f2));
|
||||
} else
|
||||
same = false;
|
||||
if (f1)
|
||||
fclose(f1);
|
||||
if (f2)
|
||||
fclose(f2);
|
||||
if (! same)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t SnapshotDB::load_db()
|
||||
{
|
||||
boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir();
|
||||
|
||||
m_snapshots.clear();
|
||||
|
||||
// Walk over the snapshot directories and load their index.
|
||||
std::string errors_cummulative;
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(snapshots_dir))
|
||||
if (boost::filesystem::is_directory(dir_entry.status())) {
|
||||
// Try to read "snapshot.ini".
|
||||
boost::filesystem::path path_ini = dir_entry.path() / SLIC3R_SNAPSHOT_FILE;
|
||||
Snapshot snapshot;
|
||||
try {
|
||||
snapshot.load_ini(path_ini.string());
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
errors_cummulative += "\n";
|
||||
continue;
|
||||
}
|
||||
// Check that the name of the snapshot directory matches the snapshot id stored in the snapshot.ini file.
|
||||
if (dir_entry.path().filename().string() != snapshot.id) {
|
||||
errors_cummulative += std::string("Snapshot ID ") + snapshot.id + " does not match the snapshot directory " + dir_entry.path().filename().string() + "\n";
|
||||
continue;
|
||||
}
|
||||
m_snapshots.emplace_back(std::move(snapshot));
|
||||
}
|
||||
// Sort the snapshots by their date/time.
|
||||
std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; });
|
||||
if (! errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
return m_snapshots.size();
|
||||
}
|
||||
|
||||
void SnapshotDB::update_slic3r_versions(std::vector<Index> &index_db)
|
||||
{
|
||||
for (Snapshot &snapshot : m_snapshots) {
|
||||
for (Snapshot::VendorConfig &vendor_config : snapshot.vendor_configs) {
|
||||
auto it = std::find_if(index_db.begin(), index_db.end(), [&vendor_config](const Index &idx) { return idx.vendor() == vendor_config.name; });
|
||||
if (it != index_db.end()) {
|
||||
Index::const_iterator it_version = it->find(vendor_config.version.config_version);
|
||||
if (it_version != it->end()) {
|
||||
vendor_config.version.min_slic3r_version = it_version->min_slic3r_version;
|
||||
vendor_config.version.max_slic3r_version = it_version->max_slic3r_version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst)
|
||||
{
|
||||
if (! boost::filesystem::is_directory(path_dst) &&
|
||||
! boost::filesystem::create_directory(path_dst))
|
||||
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
|
||||
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
|
||||
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
|
||||
boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists);
|
||||
}
|
||||
|
||||
static void delete_existing_ini_files(const boost::filesystem::path &path)
|
||||
{
|
||||
if (! boost::filesystem::is_directory(path))
|
||||
return;
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(path))
|
||||
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
|
||||
boost::filesystem::remove(dir_entry.path());
|
||||
}
|
||||
|
||||
const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
|
||||
{
|
||||
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
|
||||
|
||||
// 1) Prepare the snapshot structure.
|
||||
Snapshot snapshot;
|
||||
// Snapshot header.
|
||||
snapshot.time_captured = Slic3r::Utils::get_current_time_utc();
|
||||
snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured);
|
||||
snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); // XXX: have Semver Slic3r version
|
||||
snapshot.comment = comment;
|
||||
snapshot.reason = reason;
|
||||
// Active presets at the time of the snapshot.
|
||||
snapshot.print = app_config.get("presets", "print");
|
||||
snapshot.filaments.emplace_back(app_config.get("presets", "filament"));
|
||||
snapshot.printer = app_config.get("presets", "printer");
|
||||
for (unsigned int i = 1; i < 1000; ++ i) {
|
||||
char name[64];
|
||||
sprintf(name, "filament_%d", i);
|
||||
if (! app_config.has("presets", name))
|
||||
break;
|
||||
snapshot.filaments.emplace_back(app_config.get("presets", name));
|
||||
}
|
||||
// Vendor specific config bundles and installed printers.
|
||||
for (const std::pair<std::string, std::map<std::string, std::set<std::string>>> &vendor : app_config.vendors()) {
|
||||
Snapshot::VendorConfig cfg;
|
||||
cfg.name = vendor.first;
|
||||
cfg.models_variants_installed = vendor.second;
|
||||
for (auto it = cfg.models_variants_installed.begin(); it != cfg.models_variants_installed.end();)
|
||||
if (it->second.empty())
|
||||
cfg.models_variants_installed.erase(it ++);
|
||||
else
|
||||
++ it;
|
||||
// Read the active config bundle, parse the config version.
|
||||
PresetBundle bundle;
|
||||
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
|
||||
for (const VendorProfile &vp : bundle.vendors)
|
||||
if (vp.id == cfg.name)
|
||||
cfg.version.config_version = vp.config_version;
|
||||
// Fill-in the min/max slic3r version from the config index, if possible.
|
||||
try {
|
||||
// Load the config index for the vendor.
|
||||
Index index;
|
||||
index.load(data_dir / "vendor" / (cfg.name + ".idx"));
|
||||
auto it = index.find(cfg.version.config_version);
|
||||
if (it != index.end()) {
|
||||
cfg.version.min_slic3r_version = it->min_slic3r_version;
|
||||
cfg.version.max_slic3r_version = it->max_slic3r_version;
|
||||
}
|
||||
} catch (const std::runtime_error &err) {
|
||||
}
|
||||
snapshot.vendor_configs.emplace_back(std::move(cfg));
|
||||
}
|
||||
|
||||
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
|
||||
boost::filesystem::create_directory(snapshot_dir);
|
||||
|
||||
// Backup the presets.
|
||||
for (const char *subdir : { "print", "filament", "printer", "vendor" })
|
||||
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
|
||||
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
|
||||
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
|
||||
m_snapshots.emplace_back(std::move(snapshot));
|
||||
return m_snapshots.back();
|
||||
}
|
||||
|
||||
const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config)
|
||||
{
|
||||
for (const Snapshot &snapshot : m_snapshots)
|
||||
if (snapshot.id == id) {
|
||||
this->restore_snapshot(snapshot, app_config);
|
||||
return snapshot;
|
||||
}
|
||||
throw std::runtime_error(std::string("Snapshot with id " + id + " was not found."));
|
||||
}
|
||||
|
||||
void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config)
|
||||
{
|
||||
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
|
||||
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
|
||||
// Remove existing ini files and restore the ini files from the snapshot.
|
||||
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
|
||||
delete_existing_ini_files(data_dir / subdir);
|
||||
copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir);
|
||||
}
|
||||
// Update AppConfig with the selections of the print / filament / printer profiles
|
||||
// and about the installed printer types and variants.
|
||||
snapshot.export_selections(app_config);
|
||||
snapshot.export_vendor_configs(app_config);
|
||||
}
|
||||
|
||||
bool SnapshotDB::is_on_snapshot(AppConfig &app_config) const
|
||||
{
|
||||
// Is the "on_snapshot" configuration value set?
|
||||
std::string on_snapshot = app_config.get("on_snapshot");
|
||||
if (on_snapshot.empty())
|
||||
// No, we are not on a snapshot.
|
||||
return false;
|
||||
// Is the "on_snapshot" equal to the current configuration state?
|
||||
auto it_snapshot = this->snapshot(on_snapshot);
|
||||
if (it_snapshot != this->end() && it_snapshot->equal_to_active(app_config))
|
||||
// Yes, we are on the snapshot.
|
||||
return true;
|
||||
// No, we are no more on a snapshot. Reset the state.
|
||||
app_config.set("on_snapshot", "");
|
||||
return false;
|
||||
}
|
||||
|
||||
SnapshotDB::const_iterator SnapshotDB::snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version)
|
||||
{
|
||||
auto it_found = m_snapshots.end();
|
||||
Snapshot::VendorConfig key;
|
||||
key.name = vendor_name;
|
||||
for (auto it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) {
|
||||
const Snapshot &snapshot = *it;
|
||||
auto it_vendor_config = std::lower_bound(snapshot.vendor_configs.begin(), snapshot.vendor_configs.end(),
|
||||
key, [](const Snapshot::VendorConfig &cfg1, const Snapshot::VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
|
||||
if (it_vendor_config != snapshot.vendor_configs.end() && it_vendor_config->name == vendor_name &&
|
||||
config_version == it_vendor_config->version.config_version) {
|
||||
// Vendor config found with the correct version.
|
||||
// Save it, but continue searching, as we want the newest snapshot.
|
||||
it_found = it;
|
||||
}
|
||||
}
|
||||
return it_found;
|
||||
}
|
||||
|
||||
SnapshotDB::const_iterator SnapshotDB::snapshot(const std::string &id) const
|
||||
{
|
||||
for (const_iterator it = m_snapshots.begin(); it != m_snapshots.end(); ++ it)
|
||||
if (it->id == id)
|
||||
return it;
|
||||
return m_snapshots.end();
|
||||
}
|
||||
|
||||
boost::filesystem::path SnapshotDB::create_db_dir()
|
||||
{
|
||||
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||
boost::filesystem::path snapshots_dir = data_dir / SLIC3R_SNAPSHOTS_DIR;
|
||||
for (const boost::filesystem::path &path : { data_dir, snapshots_dir }) {
|
||||
boost::filesystem::path subdir = path;
|
||||
subdir.make_preferred();
|
||||
if (! boost::filesystem::is_directory(subdir) &&
|
||||
! boost::filesystem::create_directory(subdir))
|
||||
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + subdir.string());
|
||||
}
|
||||
return snapshots_dir;
|
||||
}
|
||||
|
||||
SnapshotDB& SnapshotDB::singleton()
|
||||
{
|
||||
static SnapshotDB instance;
|
||||
static bool loaded = false;
|
||||
if (! loaded) {
|
||||
try {
|
||||
loaded = true;
|
||||
// Load the snapshot database.
|
||||
instance.load_db();
|
||||
// Load the vendor specific configuration indices.
|
||||
std::vector<Index> index_db = Index::load_db();
|
||||
// Update the min / max slic3r versions compatible with the configurations stored inside the snapshots
|
||||
// based on the min / max slic3r versions defined by the vendor specific config indices.
|
||||
instance.update_slic3r_versions(index_db);
|
||||
} catch (std::exception &ex) {
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
129
src/slic3r/Config/Snapshot.hpp
Normal file
129
src/slic3r/Config/Snapshot.hpp
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
#ifndef slic3r_GUI_Snapshot_
|
||||
#define slic3r_GUI_Snapshot_
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "Version.hpp"
|
||||
#include "../Utils/Semver.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig;
|
||||
|
||||
namespace GUI {
|
||||
namespace Config {
|
||||
|
||||
class Index;
|
||||
|
||||
// A snapshot contains:
|
||||
// Slic3r.ini
|
||||
// vendor/
|
||||
// print/
|
||||
// filament/
|
||||
// printer/
|
||||
class Snapshot
|
||||
{
|
||||
public:
|
||||
enum Reason {
|
||||
SNAPSHOT_UNKNOWN,
|
||||
SNAPSHOT_UPGRADE,
|
||||
SNAPSHOT_DOWNGRADE,
|
||||
SNAPSHOT_BEFORE_ROLLBACK,
|
||||
SNAPSHOT_USER,
|
||||
};
|
||||
|
||||
Snapshot() { clear(); }
|
||||
|
||||
void clear();
|
||||
void load_ini(const std::string &path);
|
||||
void save_ini(const std::string &path);
|
||||
|
||||
// Export the print / filament / printer selections to be activated into the AppConfig.
|
||||
void export_selections(AppConfig &config) const;
|
||||
void export_vendor_configs(AppConfig &config) const;
|
||||
|
||||
// Perform a deep compare of the active print / filament / printer / vendor directories.
|
||||
// Return true if the content of the current print / filament / printer / vendor directories
|
||||
// matches the state stored in this snapshot.
|
||||
bool equal_to_active(const AppConfig &app_config) const;
|
||||
|
||||
// ID of a snapshot should equal to the name of the snapshot directory.
|
||||
// The ID contains the date/time, reason and comment to be human readable.
|
||||
std::string id;
|
||||
std::time_t time_captured;
|
||||
// Which Slic3r version captured this snapshot?
|
||||
Semver slic3r_version_captured = Semver::invalid();
|
||||
// Comment entered by the user at the start of the snapshot capture.
|
||||
std::string comment;
|
||||
Reason reason;
|
||||
|
||||
std::string format_reason() const;
|
||||
|
||||
// Active presets at the time of the snapshot.
|
||||
std::string print;
|
||||
std::vector<std::string> filaments;
|
||||
std::string printer;
|
||||
|
||||
// Annotation of the vendor configuration stored in the snapshot.
|
||||
// This information is displayed to the user and used to decide compatibility
|
||||
// of the configuration stored in the snapshot with the running Slic3r version.
|
||||
struct VendorConfig {
|
||||
// Name of the vendor contained in this snapshot.
|
||||
std::string name;
|
||||
// Version of the vendor config contained in this snapshot, along with compatibility data.
|
||||
Version version;
|
||||
// Which printer models of this vendor were installed, and which variants of the models?
|
||||
std::map<std::string, std::set<std::string>> models_variants_installed;
|
||||
};
|
||||
// List of vendor configs contained in this snapshot, sorted lexicographically.
|
||||
std::vector<VendorConfig> vendor_configs;
|
||||
};
|
||||
|
||||
class SnapshotDB
|
||||
{
|
||||
public:
|
||||
// Initialize the SnapshotDB singleton instance. Load the database if it has not been loaded yet.
|
||||
static SnapshotDB& singleton();
|
||||
|
||||
typedef std::vector<Snapshot>::const_iterator const_iterator;
|
||||
|
||||
// Load the snapshot database from the snapshots directory.
|
||||
// If the snapshot directory or its parent does not exist yet, it will be created.
|
||||
// Returns a number of snapshots loaded.
|
||||
size_t load_db();
|
||||
void update_slic3r_versions(std::vector<Index> &index_db);
|
||||
|
||||
// Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles,
|
||||
// create an index.
|
||||
const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment = "");
|
||||
const Snapshot& restore_snapshot(const std::string &id, AppConfig &app_config);
|
||||
void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config);
|
||||
// Test whether the AppConfig's on_snapshot variable points to an existing snapshot, and the existing snapshot
|
||||
// matches the current state. If it does not match the current state, the AppConfig's "on_snapshot" ID is reset.
|
||||
bool is_on_snapshot(AppConfig &app_config) const;
|
||||
// Finds the newest snapshot, which contains a config bundle for vendor_name with config_version.
|
||||
const_iterator snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version);
|
||||
|
||||
const_iterator begin() const { return m_snapshots.begin(); }
|
||||
const_iterator end() const { return m_snapshots.end(); }
|
||||
const_iterator snapshot(const std::string &id) const;
|
||||
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
|
||||
|
||||
private:
|
||||
// Create the snapshots directory if it does not exist yet.
|
||||
static boost::filesystem::path create_db_dir();
|
||||
|
||||
// Snapshots are sorted by their date/time, oldest first.
|
||||
std::vector<Snapshot> m_snapshots;
|
||||
};
|
||||
|
||||
} // namespace Config
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_Snapshot_ */
|
||||
319
src/slic3r/Config/Version.cpp
Normal file
319
src/slic3r/Config/Version.cpp
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
#include "Version.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Config.hpp"
|
||||
#include "../../libslic3r/FileParserError.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
namespace Config {
|
||||
|
||||
static const Semver s_current_slic3r_semver(SLIC3R_VERSION);
|
||||
|
||||
// Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix.
|
||||
static int compare_prerelease(const char *p1, const char *p2)
|
||||
{
|
||||
for (;;) {
|
||||
char c1 = *p1 ++;
|
||||
char c2 = *p2 ++;
|
||||
bool a1 = std::isalpha(c1) && c1 != 0;
|
||||
bool a2 = std::isalpha(c2) && c2 != 0;
|
||||
if (a1) {
|
||||
if (a2) {
|
||||
if (c1 != c2)
|
||||
return (c1 < c2) ? -1 : 1;
|
||||
} else
|
||||
return 1;
|
||||
} else {
|
||||
if (a2)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// This shall never happen.
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Version::is_slic3r_supported(const Semver &slic3r_version) const
|
||||
{
|
||||
if (! slic3r_version.in_range(min_slic3r_version, max_slic3r_version))
|
||||
return false;
|
||||
// Now verify, whether the configuration pre-release status is compatible with the Slic3r's pre-release status.
|
||||
// Alpha Slic3r will happily load any configuration, while beta Slic3r will ignore alpha configurations etc.
|
||||
const char *prerelease_slic3r = slic3r_version.prerelease();
|
||||
const char *prerelease_config = this->config_version.prerelease();
|
||||
if (prerelease_config == nullptr)
|
||||
// Released config is always supported.
|
||||
return true;
|
||||
else if (prerelease_slic3r == nullptr)
|
||||
// Released slic3r only supports released configs.
|
||||
return false;
|
||||
// Compare the pre-release status of Slic3r against the config.
|
||||
// If the prerelease status of slic3r is lexicographically lower or equal
|
||||
// to the prerelease status of the config, accept it.
|
||||
return compare_prerelease(prerelease_slic3r, prerelease_config) != 1;
|
||||
}
|
||||
|
||||
bool Version::is_current_slic3r_supported() const
|
||||
{
|
||||
return this->is_slic3r_supported(s_current_slic3r_semver);
|
||||
}
|
||||
|
||||
#if 0
|
||||
//TODO: This test should be moved to a unit test, once we have C++ unit tests in place.
|
||||
static int version_test()
|
||||
{
|
||||
Version v;
|
||||
v.config_version = *Semver::parse("1.1.2");
|
||||
v.min_slic3r_version = *Semver::parse("1.38.0");
|
||||
v.max_slic3r_version = Semver::inf();
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.38.0")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha")));
|
||||
// Test the prerelease status.
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0")));
|
||||
v.config_version = *Semver::parse("1.1.2-alpha");
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
|
||||
v.config_version = *Semver::parse("1.1.2-alpha1");
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
|
||||
v.config_version = *Semver::parse("1.1.2-beta");
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
|
||||
v.config_version = *Semver::parse("1.1.2-rc");
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
|
||||
v.config_version = *Semver::parse("1.1.2-rc2");
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
|
||||
// Test the upper boundary.
|
||||
v.config_version = *Semver::parse("1.1.2");
|
||||
v.max_slic3r_version = *Semver::parse("1.39.3-beta1");
|
||||
assert(v.is_slic3r_supported(*Semver::parse("1.38.0")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha1")));
|
||||
assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha")));
|
||||
return 0;
|
||||
}
|
||||
static int version_test_run = version_test();
|
||||
#endif
|
||||
|
||||
inline char* left_trim(char *c)
|
||||
{
|
||||
for (; *c == ' ' || *c == '\t'; ++ c);
|
||||
return c;
|
||||
}
|
||||
|
||||
inline char* right_trim(char *start)
|
||||
{
|
||||
char *end = start + strlen(start) - 1;
|
||||
for (; end >= start && (*end == ' ' || *end == '\t'); -- end);
|
||||
*(++ end) = 0;
|
||||
return end;
|
||||
}
|
||||
|
||||
inline std::string unquote_value(char *value, char *end, const std::string &path, int idx_line)
|
||||
{
|
||||
std::string svalue;
|
||||
if (value == end) {
|
||||
// Empty string is a valid string.
|
||||
} else if (*value == '"') {
|
||||
if (++ value > -- end || *end != '"')
|
||||
throw file_parser_error("String not enquoted correctly", path, idx_line);
|
||||
*end = 0;
|
||||
if (! unescape_string_cstyle(value, svalue))
|
||||
throw file_parser_error("Invalid escape sequence inside a quoted string", path, idx_line);
|
||||
} else
|
||||
svalue.assign(value, end);
|
||||
return svalue;
|
||||
}
|
||||
|
||||
inline std::string unquote_version_comment(char *value, char *end, const std::string &path, int idx_line)
|
||||
{
|
||||
std::string svalue;
|
||||
if (value == end) {
|
||||
// Empty string is a valid string.
|
||||
} else if (*value == '"') {
|
||||
if (++ value > -- end || *end != '"')
|
||||
throw file_parser_error("Version comment not enquoted correctly", path, idx_line);
|
||||
*end = 0;
|
||||
if (! unescape_string_cstyle(value, svalue))
|
||||
throw file_parser_error("Invalid escape sequence inside a quoted version comment", path, idx_line);
|
||||
} else
|
||||
svalue.assign(value, end);
|
||||
return svalue;
|
||||
}
|
||||
|
||||
size_t Index::load(const boost::filesystem::path &path)
|
||||
{
|
||||
m_configs.clear();
|
||||
m_vendor = path.stem().string();
|
||||
|
||||
boost::nowide::ifstream ifs(path.string());
|
||||
std::string line;
|
||||
size_t idx_line = 0;
|
||||
Version ver;
|
||||
while (std::getline(ifs, line)) {
|
||||
++ idx_line;
|
||||
// Skip the initial white spaces.
|
||||
char *key = left_trim(const_cast<char*>(line.data()));
|
||||
if (*key == '#')
|
||||
// Skip a comment line.
|
||||
continue;
|
||||
// Right trim the line.
|
||||
char *end = right_trim(key);
|
||||
if (key == end)
|
||||
// Skip an empty line.
|
||||
continue;
|
||||
// Keyword may only contain alphanumeric characters. Semantic version may in addition contain "+.-".
|
||||
char *key_end = key;
|
||||
bool maybe_semver = true;
|
||||
for (; *key_end != 0; ++ key_end) {
|
||||
if (std::isalnum(*key_end) || strchr("+.-", *key_end) != nullptr) {
|
||||
// It may be a semver.
|
||||
} else if (*key_end == '_') {
|
||||
// Cannot be a semver, but it may be a key.
|
||||
maybe_semver = false;
|
||||
} else
|
||||
// End of semver or keyword.
|
||||
break;
|
||||
}
|
||||
if (*key_end != 0 && *key_end != ' ' && *key_end != '\t' && *key_end != '=')
|
||||
throw file_parser_error("Invalid keyword or semantic version", path, idx_line);
|
||||
char *value = left_trim(key_end);
|
||||
bool key_value_pair = *value == '=';
|
||||
if (key_value_pair)
|
||||
value = left_trim(value + 1);
|
||||
*key_end = 0;
|
||||
boost::optional<Semver> semver;
|
||||
if (maybe_semver)
|
||||
semver = Semver::parse(key);
|
||||
if (key_value_pair) {
|
||||
if (semver)
|
||||
throw file_parser_error("Key cannot be a semantic version", path, idx_line);\
|
||||
// Verify validity of the key / value pair.
|
||||
std::string svalue = unquote_value(value, end, path.string(), idx_line);
|
||||
if (strcmp(key, "min_slic3r_version") == 0 || strcmp(key, "max_slic3r_version") == 0) {
|
||||
if (! svalue.empty())
|
||||
semver = Semver::parse(svalue);
|
||||
if (! semver)
|
||||
throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line);
|
||||
if (strcmp(key, "min_slic3r_version") == 0)
|
||||
ver.min_slic3r_version = *semver;
|
||||
else
|
||||
ver.max_slic3r_version = *semver;
|
||||
} else {
|
||||
// Ignore unknown keys, as there may come new keys in the future.
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (! semver)
|
||||
throw file_parser_error("Invalid semantic version", path, idx_line);
|
||||
ver.config_version = *semver;
|
||||
ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path.string(), idx_line);
|
||||
m_configs.emplace_back(ver);
|
||||
}
|
||||
|
||||
// Sort the configs by their version.
|
||||
std::sort(m_configs.begin(), m_configs.end(), [](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; });
|
||||
return m_configs.size();
|
||||
}
|
||||
|
||||
Semver Index::version() const
|
||||
{
|
||||
Semver ver = Semver::zero();
|
||||
for (const Version &cv : m_configs)
|
||||
if (cv.config_version >= ver)
|
||||
ver = cv.config_version;
|
||||
return ver;
|
||||
}
|
||||
|
||||
Index::const_iterator Index::find(const Semver &ver) const
|
||||
{
|
||||
Version key;
|
||||
key.config_version = ver;
|
||||
auto it = std::lower_bound(m_configs.begin(), m_configs.end(), key,
|
||||
[](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; });
|
||||
return (it == m_configs.end() || it->config_version == ver) ? it : m_configs.end();
|
||||
}
|
||||
|
||||
Index::const_iterator Index::recommended() const
|
||||
{
|
||||
int idx = -1;
|
||||
const_iterator highest = this->end();
|
||||
for (const_iterator it = this->begin(); it != this->end(); ++ it)
|
||||
if (it->is_current_slic3r_supported() &&
|
||||
(highest == this->end() || highest->config_version < it->config_version))
|
||||
highest = it;
|
||||
return highest;
|
||||
}
|
||||
|
||||
std::vector<Index> Index::load_db()
|
||||
{
|
||||
boost::filesystem::path cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "cache";
|
||||
|
||||
std::vector<Index> index_db;
|
||||
std::string errors_cummulative;
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir))
|
||||
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) {
|
||||
Index idx;
|
||||
try {
|
||||
idx.load(dir_entry.path());
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
errors_cummulative += "\n";
|
||||
continue;
|
||||
}
|
||||
index_db.emplace_back(std::move(idx));
|
||||
}
|
||||
|
||||
if (! errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
return index_db;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
88
src/slic3r/Config/Version.hpp
Normal file
88
src/slic3r/Config/Version.hpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#ifndef slic3r_GUI_ConfigIndex_
|
||||
#define slic3r_GUI_ConfigIndex_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "../../libslic3r/FileParserError.hpp"
|
||||
#include "../Utils/Semver.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
namespace Config {
|
||||
|
||||
// Configuration bundle version.
|
||||
struct Version
|
||||
{
|
||||
// Version of this config.
|
||||
Semver config_version = Semver::invalid();
|
||||
// Minimum Slic3r version, for which this config is applicable.
|
||||
Semver min_slic3r_version = Semver::zero();
|
||||
// Maximum Slic3r version, for which this config is recommended.
|
||||
// Slic3r should read older configuration and upgrade to a newer format,
|
||||
// but likely there has been a better configuration published, using the new features.
|
||||
Semver max_slic3r_version = Semver::inf();
|
||||
// Single comment line.
|
||||
std::string comment;
|
||||
|
||||
bool is_slic3r_supported(const Semver &slicer_version) const;
|
||||
bool is_current_slic3r_supported() const;
|
||||
};
|
||||
|
||||
// Index of vendor specific config bundle versions and Slic3r compatibilities.
|
||||
// The index is being downloaded from the internet, also an initial version of the index
|
||||
// is contained in the Slic3r installation.
|
||||
//
|
||||
// The index has a simple format:
|
||||
//
|
||||
// min_sic3r_version =
|
||||
// max_slic3r_version =
|
||||
// config_version "comment"
|
||||
// config_version "comment"
|
||||
// ...
|
||||
// min_slic3r_version =
|
||||
// max_slic3r_version =
|
||||
// config_version comment
|
||||
// config_version comment
|
||||
// ...
|
||||
//
|
||||
// The min_slic3r_version, max_slic3r_version keys are applied to the config versions below,
|
||||
// empty slic3r version means an open interval.
|
||||
class Index
|
||||
{
|
||||
public:
|
||||
typedef std::vector<Version>::const_iterator const_iterator;
|
||||
// Read a config index file in the simple format described in the Index class comment.
|
||||
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
|
||||
size_t load(const boost::filesystem::path &path);
|
||||
|
||||
const std::string& vendor() const { return m_vendor; }
|
||||
// Returns version of the index as the highest version of all the configs.
|
||||
// If there is no config, Semver::zero() is returned.
|
||||
Semver version() const;
|
||||
|
||||
const_iterator begin() const { return m_configs.begin(); }
|
||||
const_iterator end() const { return m_configs.end(); }
|
||||
const_iterator find(const Semver &ver) const;
|
||||
const std::vector<Version>& configs() const { return m_configs; }
|
||||
// Finds a recommended config to be installed for the current Slic3r version.
|
||||
// Returns configs().end() if such version does not exist in the index. This shall never happen
|
||||
// if the index is valid.
|
||||
const_iterator recommended() const;
|
||||
|
||||
// Load all vendor specific indices.
|
||||
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
|
||||
static std::vector<Index> load_db();
|
||||
|
||||
private:
|
||||
std::string m_vendor;
|
||||
std::vector<Version> m_configs;
|
||||
};
|
||||
|
||||
} // namespace Config
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_ConfigIndex_ */
|
||||
183
src/slic3r/GUI/2DBed.cpp
Normal file
183
src/slic3r/GUI/2DBed.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#include "2DBed.hpp"
|
||||
|
||||
#include <wx/dcbuffer.h>
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
void Bed_2D::repaint()
|
||||
{
|
||||
wxAutoBufferedPaintDC dc(this);
|
||||
auto cw = GetSize().GetWidth();
|
||||
auto ch = GetSize().GetHeight();
|
||||
// when canvas is not rendered yet, size is 0, 0
|
||||
if (cw == 0) return ;
|
||||
|
||||
if (m_user_drawn_background) {
|
||||
// On all systems the AutoBufferedPaintDC() achieves double buffering.
|
||||
// On MacOS the background is erased, on Windows the background is not erased
|
||||
// and on Linux / GTK the background is erased to gray color.
|
||||
// Fill DC with the background on Windows & Linux / GTK.
|
||||
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
|
||||
dc.SetPen(*new wxPen(color, 1, wxPENSTYLE_SOLID));
|
||||
dc.SetBrush(*new wxBrush(color, wxBRUSHSTYLE_SOLID));
|
||||
auto rect = GetUpdateRegion().GetBox();
|
||||
dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight());
|
||||
}
|
||||
|
||||
// turn cw and ch from sizes to max coordinates
|
||||
cw--;
|
||||
ch--;
|
||||
|
||||
auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch));
|
||||
// leave space for origin point
|
||||
cbb.min(0) += 4;
|
||||
cbb.max -= Vec2d(4., 4.);
|
||||
|
||||
// leave space for origin label
|
||||
cbb.max(1) -= 13;
|
||||
|
||||
// read new size
|
||||
cw = cbb.size()(0);
|
||||
ch = cbb.size()(1);
|
||||
|
||||
auto ccenter = cbb.center();
|
||||
|
||||
// get bounding box of bed shape in G - code coordinates
|
||||
auto bed_shape = m_bed_shape;
|
||||
auto bed_polygon = Polygon::new_scale(m_bed_shape);
|
||||
auto bb = BoundingBoxf(m_bed_shape);
|
||||
bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area
|
||||
auto bw = bb.size()(0);
|
||||
auto bh = bb.size()(1);
|
||||
auto bcenter = bb.center();
|
||||
|
||||
// calculate the scaling factor for fitting bed shape in canvas area
|
||||
auto sfactor = std::min(cw/bw, ch/bh);
|
||||
auto shift = Vec2d(
|
||||
ccenter(0) - bcenter(0) * sfactor,
|
||||
ccenter(1) - bcenter(1) * sfactor
|
||||
);
|
||||
m_scale_factor = sfactor;
|
||||
m_shift = Vec2d(shift(0) + cbb.min(0),
|
||||
shift(1) - (cbb.max(1) - GetSize().GetHeight()));
|
||||
|
||||
// draw bed fill
|
||||
dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxSOLID));
|
||||
wxPointList pt_list;
|
||||
for (auto pt: m_bed_shape)
|
||||
{
|
||||
Point pt_pix = to_pixels(pt);
|
||||
pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1)));
|
||||
}
|
||||
dc.DrawPolygon(&pt_list, 0, 0);
|
||||
|
||||
// draw grid
|
||||
auto step = 10; // 1cm grid
|
||||
Polylines polylines;
|
||||
for (auto x = bb.min(0) - fmod(bb.min(0), step) + step; x < bb.max(0); x += step) {
|
||||
polylines.push_back(Polyline::new_scale({ Vec2d(x, bb.min(1)), Vec2d(x, bb.max(1)) }));
|
||||
}
|
||||
for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) {
|
||||
polylines.push_back(Polyline::new_scale({ Vec2d(bb.min(0), y), Vec2d(bb.max(0), y) }));
|
||||
}
|
||||
polylines = intersection_pl(polylines, bed_polygon);
|
||||
|
||||
dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxSOLID));
|
||||
for (auto pl : polylines)
|
||||
{
|
||||
for (size_t i = 0; i < pl.points.size()-1; i++){
|
||||
Point pt1 = to_pixels(unscale(pl.points[i]));
|
||||
Point pt2 = to_pixels(unscale(pl.points[i+1]));
|
||||
dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1));
|
||||
}
|
||||
}
|
||||
|
||||
// draw bed contour
|
||||
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID));
|
||||
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxTRANSPARENT));
|
||||
dc.DrawPolygon(&pt_list, 0, 0);
|
||||
|
||||
auto origin_px = to_pixels(Vec2d(0, 0));
|
||||
|
||||
// draw axes
|
||||
auto axes_len = 50;
|
||||
auto arrow_len = 6;
|
||||
auto arrow_angle = Geometry::deg2rad(45.0);
|
||||
dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxSOLID)); // red
|
||||
auto x_end = Vec2d(origin_px(0) + axes_len, origin_px(1));
|
||||
dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(x_end(0), x_end(1)));
|
||||
for (auto angle : { -arrow_angle, arrow_angle }){
|
||||
auto end = Eigen::Translation2d(x_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- x_end) * Eigen::Vector2d(x_end(0) - arrow_len, x_end(1));
|
||||
dc.DrawLine(wxPoint(x_end(0), x_end(1)), wxPoint(end(0), end(1)));
|
||||
}
|
||||
|
||||
dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxSOLID)); // green
|
||||
auto y_end = Vec2d(origin_px(0), origin_px(1) - axes_len);
|
||||
dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(y_end(0), y_end(1)));
|
||||
for (auto angle : { -arrow_angle, arrow_angle }) {
|
||||
auto end = Eigen::Translation2d(y_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- y_end) * Eigen::Vector2d(y_end(0), y_end(1) + arrow_len);
|
||||
dc.DrawLine(wxPoint(y_end(0), y_end(1)), wxPoint(end(0), end(1)));
|
||||
}
|
||||
|
||||
// draw origin
|
||||
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID));
|
||||
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxSOLID));
|
||||
dc.DrawCircle(origin_px(0), origin_px(1), 3);
|
||||
|
||||
static const auto origin_label = wxString("(0,0)");
|
||||
dc.SetTextForeground(wxColour(0, 0, 0));
|
||||
dc.SetFont(wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
auto extent = dc.GetTextExtent(origin_label);
|
||||
const auto origin_label_x = origin_px(0) <= cw / 2 ? origin_px(0) + 1 : origin_px(0) - 1 - extent.GetWidth();
|
||||
const auto origin_label_y = origin_px(1) <= ch / 2 ? origin_px(1) + 1 : origin_px(1) - 1 - extent.GetHeight();
|
||||
dc.DrawText(origin_label, origin_label_x, origin_label_y);
|
||||
|
||||
// draw current position
|
||||
if (m_pos!= Vec2d(0, 0)) {
|
||||
auto pos_px = to_pixels(m_pos);
|
||||
dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxSOLID));
|
||||
dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxTRANSPARENT));
|
||||
dc.DrawCircle(pos_px(0), pos_px(1), 5);
|
||||
|
||||
dc.DrawLine(pos_px(0) - 15, pos_px(1), pos_px(0) + 15, pos_px(1));
|
||||
dc.DrawLine(pos_px(0), pos_px(1) - 15, pos_px(0), pos_px(1) + 15);
|
||||
}
|
||||
|
||||
m_painted = true;
|
||||
}
|
||||
|
||||
// convert G - code coordinates into pixels
|
||||
Point Bed_2D::to_pixels(Vec2d point){
|
||||
auto p = point * m_scale_factor + m_shift;
|
||||
return Point(p(0), GetSize().GetHeight() - p(1));
|
||||
}
|
||||
|
||||
void Bed_2D::mouse_event(wxMouseEvent event){
|
||||
if (!m_interactive) return;
|
||||
if (!m_painted) return;
|
||||
|
||||
auto pos = event.GetPosition();
|
||||
auto point = to_units(Point(pos.x, pos.y));
|
||||
if (event.LeftDown() || event.Dragging()) {
|
||||
if (m_on_move)
|
||||
m_on_move(point) ;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// convert pixels into G - code coordinates
|
||||
Vec2d Bed_2D::to_units(Point point){
|
||||
return (Vec2d(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor);
|
||||
}
|
||||
|
||||
void Bed_2D::set_pos(Vec2d pos){
|
||||
m_pos = pos;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
52
src/slic3r/GUI/2DBed.hpp
Normal file
52
src/slic3r/GUI/2DBed.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef slic3r_2DBed_hpp_
|
||||
#define slic3r_2DBed_hpp_
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "Config.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class Bed_2D : public wxPanel
|
||||
{
|
||||
bool m_user_drawn_background = true;
|
||||
|
||||
bool m_painted = false;
|
||||
bool m_interactive = false;
|
||||
double m_scale_factor;
|
||||
Vec2d m_shift = Vec2d::Zero();
|
||||
Vec2d m_pos = Vec2d::Zero();
|
||||
std::function<void(Vec2d)> m_on_move = nullptr;
|
||||
|
||||
Point to_pixels(Vec2d point);
|
||||
Vec2d to_units(Point point);
|
||||
void repaint();
|
||||
void mouse_event(wxMouseEvent event);
|
||||
void set_pos(Vec2d pos);
|
||||
|
||||
public:
|
||||
Bed_2D(wxWindow* parent)
|
||||
{
|
||||
Create(parent, wxID_ANY, wxDefaultPosition, wxSize(250, -1), wxTAB_TRAVERSAL);
|
||||
// m_user_drawn_background = $^O ne 'darwin';
|
||||
#ifdef __APPLE__
|
||||
m_user_drawn_background = false;
|
||||
#endif /*__APPLE__*/
|
||||
Bind(wxEVT_PAINT, ([this](wxPaintEvent e) { repaint(); }));
|
||||
// EVT_ERASE_BACKGROUND($self, sub{}) if $self->{user_drawn_background};
|
||||
// Bind(EVT_MOUSE_EVENTS, ([this](wxMouseEvent event){/*mouse_event()*/; }));
|
||||
Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent event){ mouse_event(event); }));
|
||||
Bind(wxEVT_MOTION, ([this](wxMouseEvent event){ mouse_event(event); }));
|
||||
Bind(wxEVT_SIZE, ([this](wxSizeEvent e) { Refresh(); }));
|
||||
}
|
||||
~Bed_2D(){}
|
||||
|
||||
std::vector<Vec2d> m_bed_shape;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* slic3r_2DBed_hpp_ */
|
||||
2152
src/slic3r/GUI/3DScene.cpp
Normal file
2152
src/slic3r/GUI/3DScene.cpp
Normal file
File diff suppressed because it is too large
Load diff
622
src/slic3r/GUI/3DScene.hpp
Normal file
622
src/slic3r/GUI/3DScene.hpp
Normal file
|
|
@ -0,0 +1,622 @@
|
|||
#ifndef slic3r_3DScene_hpp_
|
||||
#define slic3r_3DScene_hpp_
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Point.hpp"
|
||||
#include "../../libslic3r/Line.hpp"
|
||||
#include "../../libslic3r/TriangleMesh.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include "../../libslic3r/Model.hpp"
|
||||
#include "../../slic3r/GUI/GLCanvas3DManager.hpp"
|
||||
|
||||
class wxBitmap;
|
||||
class wxWindow;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class Model;
|
||||
class ModelObject;
|
||||
class GCodePreviewData;
|
||||
class DynamicPrintConfig;
|
||||
class ExtrusionPath;
|
||||
class ExtrusionMultiPath;
|
||||
class ExtrusionLoop;
|
||||
class ExtrusionEntity;
|
||||
class ExtrusionEntityCollection;
|
||||
|
||||
// A container for interleaved arrays of 3D vertices and normals,
|
||||
// possibly indexed by triangles and / or quads.
|
||||
class GLIndexedVertexArray {
|
||||
public:
|
||||
GLIndexedVertexArray() :
|
||||
vertices_and_normals_interleaved_VBO_id(0),
|
||||
triangle_indices_VBO_id(0),
|
||||
quad_indices_VBO_id(0)
|
||||
{ this->setup_sizes(); }
|
||||
GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
|
||||
vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
|
||||
triangle_indices(rhs.triangle_indices),
|
||||
quad_indices(rhs.quad_indices),
|
||||
vertices_and_normals_interleaved_VBO_id(0),
|
||||
triangle_indices_VBO_id(0),
|
||||
quad_indices_VBO_id(0)
|
||||
{ this->setup_sizes(); }
|
||||
GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
|
||||
vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
|
||||
triangle_indices(std::move(rhs.triangle_indices)),
|
||||
quad_indices(std::move(rhs.quad_indices)),
|
||||
vertices_and_normals_interleaved_VBO_id(0),
|
||||
triangle_indices_VBO_id(0),
|
||||
quad_indices_VBO_id(0)
|
||||
{ this->setup_sizes(); }
|
||||
|
||||
GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs)
|
||||
{
|
||||
assert(vertices_and_normals_interleaved_VBO_id == 0);
|
||||
assert(triangle_indices_VBO_id == 0);
|
||||
assert(triangle_indices_VBO_id == 0);
|
||||
this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved;
|
||||
this->triangle_indices = rhs.triangle_indices;
|
||||
this->quad_indices = rhs.quad_indices;
|
||||
this->setup_sizes();
|
||||
return *this;
|
||||
}
|
||||
|
||||
GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs)
|
||||
{
|
||||
assert(vertices_and_normals_interleaved_VBO_id == 0);
|
||||
assert(triangle_indices_VBO_id == 0);
|
||||
assert(triangle_indices_VBO_id == 0);
|
||||
this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
|
||||
this->triangle_indices = std::move(rhs.triangle_indices);
|
||||
this->quad_indices = std::move(rhs.quad_indices);
|
||||
this->setup_sizes();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x)
|
||||
std::vector<float> vertices_and_normals_interleaved;
|
||||
std::vector<int> triangle_indices;
|
||||
std::vector<int> quad_indices;
|
||||
|
||||
// When the geometry data is loaded into the graphics card as Vertex Buffer Objects,
|
||||
// the above mentioned std::vectors are cleared and the following variables keep their original length.
|
||||
size_t vertices_and_normals_interleaved_size;
|
||||
size_t triangle_indices_size;
|
||||
size_t quad_indices_size;
|
||||
|
||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||
// Zero if the VBOs are not used.
|
||||
unsigned int vertices_and_normals_interleaved_VBO_id;
|
||||
unsigned int triangle_indices_VBO_id;
|
||||
unsigned int quad_indices_VBO_id;
|
||||
|
||||
void load_mesh_flat_shading(const TriangleMesh &mesh);
|
||||
void load_mesh_full_shading(const TriangleMesh &mesh);
|
||||
|
||||
inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; }
|
||||
|
||||
inline void reserve(size_t sz) {
|
||||
this->vertices_and_normals_interleaved.reserve(sz * 6);
|
||||
this->triangle_indices.reserve(sz * 3);
|
||||
this->quad_indices.reserve(sz * 4);
|
||||
}
|
||||
|
||||
inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) {
|
||||
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
|
||||
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
|
||||
this->vertices_and_normals_interleaved.push_back(nx);
|
||||
this->vertices_and_normals_interleaved.push_back(ny);
|
||||
this->vertices_and_normals_interleaved.push_back(nz);
|
||||
this->vertices_and_normals_interleaved.push_back(x);
|
||||
this->vertices_and_normals_interleaved.push_back(y);
|
||||
this->vertices_and_normals_interleaved.push_back(z);
|
||||
};
|
||||
|
||||
inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
|
||||
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
|
||||
}
|
||||
|
||||
inline void push_geometry(const Vec3d& p, const Vec3d& n) {
|
||||
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
|
||||
}
|
||||
|
||||
inline void push_triangle(int idx1, int idx2, int idx3) {
|
||||
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
|
||||
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
|
||||
this->triangle_indices.push_back(idx1);
|
||||
this->triangle_indices.push_back(idx2);
|
||||
this->triangle_indices.push_back(idx3);
|
||||
};
|
||||
|
||||
inline void push_quad(int idx1, int idx2, int idx3, int idx4) {
|
||||
if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity())
|
||||
this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4));
|
||||
this->quad_indices.push_back(idx1);
|
||||
this->quad_indices.push_back(idx2);
|
||||
this->quad_indices.push_back(idx3);
|
||||
this->quad_indices.push_back(idx4);
|
||||
};
|
||||
|
||||
// Finalize the initialization of the geometry & indices,
|
||||
// upload the geometry and indices to OpenGL VBO objects
|
||||
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
|
||||
void finalize_geometry(bool use_VBOs);
|
||||
// Release the geometry data, release OpenGL VBOs.
|
||||
void release_geometry();
|
||||
// Render either using an immediate mode, or the VBOs.
|
||||
void render() const;
|
||||
void render(const std::pair<size_t, size_t> &tverts_range, const std::pair<size_t, size_t> &qverts_range) const;
|
||||
|
||||
// Is there any geometry data stored?
|
||||
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
|
||||
|
||||
// Is this object indexed, or is it just a set of triangles?
|
||||
bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; }
|
||||
|
||||
void clear() {
|
||||
this->vertices_and_normals_interleaved.clear();
|
||||
this->triangle_indices.clear();
|
||||
this->quad_indices.clear();
|
||||
this->setup_sizes();
|
||||
}
|
||||
|
||||
// Shrink the internal storage to tighly fit the data stored.
|
||||
void shrink_to_fit() {
|
||||
if (! this->has_VBOs())
|
||||
this->setup_sizes();
|
||||
this->vertices_and_normals_interleaved.shrink_to_fit();
|
||||
this->triangle_indices.shrink_to_fit();
|
||||
this->quad_indices.shrink_to_fit();
|
||||
}
|
||||
|
||||
BoundingBoxf3 bounding_box() const {
|
||||
BoundingBoxf3 bbox;
|
||||
if (! this->vertices_and_normals_interleaved.empty()) {
|
||||
bbox.defined = true;
|
||||
bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3];
|
||||
bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4];
|
||||
bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5];
|
||||
for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) {
|
||||
const float *verts = this->vertices_and_normals_interleaved.data() + i;
|
||||
bbox.min(0) = std::min<coordf_t>(bbox.min(0), verts[0]);
|
||||
bbox.min(1) = std::min<coordf_t>(bbox.min(1), verts[1]);
|
||||
bbox.min(2) = std::min<coordf_t>(bbox.min(2), verts[2]);
|
||||
bbox.max(0) = std::max<coordf_t>(bbox.max(0), verts[0]);
|
||||
bbox.max(1) = std::max<coordf_t>(bbox.max(1), verts[1]);
|
||||
bbox.max(2) = std::max<coordf_t>(bbox.max(2), verts[2]);
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
private:
|
||||
inline void setup_sizes() {
|
||||
vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
|
||||
triangle_indices_size = this->triangle_indices.size();
|
||||
quad_indices_size = this->quad_indices.size();
|
||||
}
|
||||
};
|
||||
|
||||
class LayersTexture
|
||||
{
|
||||
public:
|
||||
LayersTexture() : width(0), height(0), levels(0), cells(0) {}
|
||||
|
||||
// Texture data
|
||||
std::vector<char> data;
|
||||
// Width of the texture, top level.
|
||||
size_t width;
|
||||
// Height of the texture, top level.
|
||||
size_t height;
|
||||
// For how many levels of detail is the data allocated?
|
||||
size_t levels;
|
||||
// Number of texture cells allocated for the height texture.
|
||||
size_t cells;
|
||||
};
|
||||
|
||||
class GLVolume {
|
||||
struct LayerHeightTextureData
|
||||
{
|
||||
// ID of the layer height texture
|
||||
unsigned int texture_id;
|
||||
// ID of the shader used to render with the layer height texture
|
||||
unsigned int shader_id;
|
||||
// The print object to update when generating the layer height texture
|
||||
const PrintObject* print_object;
|
||||
|
||||
float z_cursor_relative;
|
||||
float edit_band_width;
|
||||
|
||||
LayerHeightTextureData() { reset(); }
|
||||
|
||||
void reset()
|
||||
{
|
||||
texture_id = 0;
|
||||
shader_id = 0;
|
||||
print_object = nullptr;
|
||||
z_cursor_relative = 0.0f;
|
||||
edit_band_width = 0.0f;
|
||||
}
|
||||
|
||||
bool can_use() const { return (texture_id > 0) && (shader_id > 0) && (print_object != nullptr); }
|
||||
};
|
||||
|
||||
public:
|
||||
static const float SELECTED_COLOR[4];
|
||||
static const float HOVER_COLOR[4];
|
||||
static const float OUTSIDE_COLOR[4];
|
||||
static const float SELECTED_OUTSIDE_COLOR[4];
|
||||
|
||||
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
|
||||
GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
|
||||
|
||||
private:
|
||||
// Offset of the volume to be rendered.
|
||||
Vec3d m_offset;
|
||||
// Rotation around three axes of the volume to be rendered.
|
||||
Vec3d m_rotation;
|
||||
// Scale factor along the three axes of the volume to be rendered.
|
||||
Vec3d m_scaling_factor;
|
||||
#if ENABLE_MIRROR
|
||||
// Mirroring along the three axes of the volume to be rendered.
|
||||
Vec3d m_mirror;
|
||||
#endif // ENABLE_MIRROR
|
||||
// World matrix of the volume to be rendered.
|
||||
mutable Transform3f m_world_matrix;
|
||||
// Whether or not is needed to recalculate the world matrix.
|
||||
mutable bool m_world_matrix_dirty;
|
||||
// Bounding box of this volume, in unscaled coordinates.
|
||||
mutable BoundingBoxf3 m_transformed_bounding_box;
|
||||
// Whether or not is needed to recalculate the transformed bounding box.
|
||||
mutable bool m_transformed_bounding_box_dirty;
|
||||
// Pointer to convex hull of the original mesh, if any.
|
||||
const TriangleMesh* m_convex_hull;
|
||||
// Bounding box of this volume, in unscaled coordinates.
|
||||
mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
|
||||
// Whether or not is needed to recalculate the transformed convex hull bounding box.
|
||||
mutable bool m_transformed_convex_hull_bounding_box_dirty;
|
||||
|
||||
public:
|
||||
|
||||
// Bounding box of this volume, in unscaled coordinates.
|
||||
BoundingBoxf3 bounding_box;
|
||||
// Color of the triangles / quads held by this volume.
|
||||
float color[4];
|
||||
// Color used to render this volume.
|
||||
float render_color[4];
|
||||
// An ID containing the object ID, volume ID and instance ID.
|
||||
int composite_id;
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
// An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance.
|
||||
int select_group_id;
|
||||
// An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance.
|
||||
int drag_group_id;
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
// An ID containing the extruder ID (used to select color).
|
||||
int extruder_id;
|
||||
// Is this object selected?
|
||||
bool selected;
|
||||
// Whether or not this volume is active for rendering
|
||||
bool is_active;
|
||||
// Whether or not to use this volume when applying zoom_to_volumes()
|
||||
bool zoom_to_volumes;
|
||||
// Wheter or not this volume is enabled for outside print volume detection in shader.
|
||||
bool shader_outside_printer_detection_enabled;
|
||||
// Wheter or not this volume is outside print volume.
|
||||
bool is_outside;
|
||||
// Boolean: Is mouse over this object?
|
||||
bool hover;
|
||||
// Wheter or not this volume has been generated from a modifier
|
||||
bool is_modifier;
|
||||
// Wheter or not this volume has been generated from the wipe tower
|
||||
bool is_wipe_tower;
|
||||
// Wheter or not this volume has been generated from an extrusion path
|
||||
bool is_extrusion_path;
|
||||
|
||||
// Interleaved triangles & normals with indexed triangles & quads.
|
||||
GLIndexedVertexArray indexed_vertex_array;
|
||||
// Ranges of triangle and quad indices to be rendered.
|
||||
std::pair<size_t, size_t> tverts_range;
|
||||
std::pair<size_t, size_t> qverts_range;
|
||||
|
||||
// If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts
|
||||
// of the extrusions per layer.
|
||||
std::vector<coordf_t> print_zs;
|
||||
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
|
||||
std::vector<size_t> offsets;
|
||||
|
||||
void set_render_color(float r, float g, float b, float a);
|
||||
void set_render_color(const float* rgba, unsigned int size);
|
||||
// Sets render color in dependence of current state
|
||||
void set_render_color();
|
||||
|
||||
const Vec3d& get_rotation() const;
|
||||
void set_rotation(const Vec3d& rotation);
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
const Vec3d& get_scaling_factor() const;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
void set_scaling_factor(const Vec3d& scaling_factor);
|
||||
|
||||
#if ENABLE_MIRROR
|
||||
const Vec3d& get_mirror() const;
|
||||
double get_mirror(Axis axis) const;
|
||||
void set_mirror(const Vec3d& mirror);
|
||||
void set_mirror(Axis axis, double mirror);
|
||||
#endif // ENABLE_MIRROR
|
||||
|
||||
const Vec3d& get_offset() const;
|
||||
void set_offset(const Vec3d& offset);
|
||||
|
||||
void set_convex_hull(const TriangleMesh& convex_hull);
|
||||
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
void set_select_group_id(const std::string& select_by);
|
||||
void set_drag_group_id(const std::string& drag_by);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
int object_idx() const { return this->composite_id / 1000000; }
|
||||
int volume_idx() const { return (this->composite_id / 1000) % 1000; }
|
||||
int instance_idx() const { return this->composite_id % 1000; }
|
||||
|
||||
const Transform3f& world_matrix() const;
|
||||
const BoundingBoxf3& transformed_bounding_box() const;
|
||||
const BoundingBoxf3& transformed_convex_hull_bounding_box() const;
|
||||
|
||||
bool empty() const { return this->indexed_vertex_array.empty(); }
|
||||
bool indexed() const { return this->indexed_vertex_array.indexed(); }
|
||||
|
||||
void set_range(coordf_t low, coordf_t high);
|
||||
void render() const;
|
||||
void render_using_layer_height() const;
|
||||
void render_VBOs(int color_id, int detection_id, int worldmatrix_id) const;
|
||||
void render_legacy() const;
|
||||
|
||||
void finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); }
|
||||
void release_geometry() { this->indexed_vertex_array.release_geometry(); }
|
||||
|
||||
/************************************************ Layer height texture ****************************************************/
|
||||
std::shared_ptr<LayersTexture> layer_height_texture;
|
||||
// Data to render this volume using the layer height texture
|
||||
LayerHeightTextureData layer_height_texture_data;
|
||||
|
||||
bool has_layer_height_texture() const
|
||||
{ return this->layer_height_texture.get() != nullptr; }
|
||||
size_t layer_height_texture_width() const
|
||||
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->width; }
|
||||
size_t layer_height_texture_height() const
|
||||
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->height; }
|
||||
size_t layer_height_texture_cells() const
|
||||
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->cells; }
|
||||
void* layer_height_texture_data_ptr_level0() const {
|
||||
return (layer_height_texture.get() == nullptr) ? 0 :
|
||||
(void*)layer_height_texture->data.data();
|
||||
}
|
||||
void* layer_height_texture_data_ptr_level1() const {
|
||||
return (layer_height_texture.get() == nullptr) ? 0 :
|
||||
(void*)(layer_height_texture->data.data() + layer_height_texture->width * layer_height_texture->height * 4);
|
||||
}
|
||||
double layer_height_texture_z_to_row_id() const;
|
||||
void generate_layer_height_texture(const PrintObject *print_object, bool force);
|
||||
|
||||
void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, const PrintObject* print_object, float z_cursor_relative, float edit_band_width)
|
||||
{
|
||||
layer_height_texture_data.texture_id = texture_id;
|
||||
layer_height_texture_data.shader_id = shader_id;
|
||||
layer_height_texture_data.print_object = print_object;
|
||||
layer_height_texture_data.z_cursor_relative = z_cursor_relative;
|
||||
layer_height_texture_data.edit_band_width = edit_band_width;
|
||||
}
|
||||
|
||||
void reset_layer_height_texture_data() { layer_height_texture_data.reset(); }
|
||||
};
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
typedef std::vector<GLVolume*> GLVolumePtrs;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
class GLVolumeCollection
|
||||
{
|
||||
// min and max vertex of the print box volume
|
||||
float print_box_min[3];
|
||||
float print_box_max[3];
|
||||
|
||||
public:
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
GLVolumePtrs volumes;
|
||||
#else
|
||||
std::vector<GLVolume*> volumes;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
GLVolumeCollection() {};
|
||||
~GLVolumeCollection() { clear(); };
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
std::vector<int> load_object(
|
||||
const ModelObject *model_object,
|
||||
int obj_idx,
|
||||
const std::vector<int> &instance_idxs,
|
||||
const std::string &color_by,
|
||||
bool use_VBOs);
|
||||
#else
|
||||
std::vector<int> load_object(
|
||||
const ModelObject *model_object,
|
||||
int obj_idx,
|
||||
const std::vector<int> &instance_idxs,
|
||||
const std::string &color_by,
|
||||
const std::string &select_by,
|
||||
const std::string &drag_by,
|
||||
bool use_VBOs);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
int load_wipe_tower_preview(
|
||||
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);
|
||||
|
||||
// Render the volumes by OpenGL.
|
||||
void render_VBOs() const;
|
||||
void render_legacy() const;
|
||||
|
||||
// Finalize the initialization of the geometry & indices,
|
||||
// upload the geometry and indices to OpenGL VBO objects
|
||||
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
|
||||
void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); }
|
||||
// Release the geometry data assigned to the volumes.
|
||||
// If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
|
||||
void release_geometry() { for (auto *v : volumes) v->release_geometry(); }
|
||||
// Clear the geometry
|
||||
void clear() { for (auto *v : volumes) delete v; volumes.clear(); }
|
||||
|
||||
bool empty() const { return volumes.empty(); }
|
||||
void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); }
|
||||
|
||||
void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) {
|
||||
print_box_min[0] = min_x; print_box_min[1] = min_y; print_box_min[2] = min_z;
|
||||
print_box_max[0] = max_x; print_box_max[1] = max_y; print_box_max[2] = max_z;
|
||||
}
|
||||
|
||||
// returns true if all the volumes are completely contained in the print volume
|
||||
// returns the containment state in the given out_state, if non-null
|
||||
bool check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state);
|
||||
void reset_outside_state();
|
||||
|
||||
void update_colors_by_extruder(const DynamicPrintConfig* config);
|
||||
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
void set_select_by(const std::string& select_by);
|
||||
void set_drag_by(const std::string& drag_by);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
// Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
|
||||
std::vector<double> get_current_print_zs(bool active_only) const;
|
||||
|
||||
private:
|
||||
GLVolumeCollection(const GLVolumeCollection &other);
|
||||
GLVolumeCollection& operator=(const GLVolumeCollection &);
|
||||
};
|
||||
|
||||
class _3DScene
|
||||
{
|
||||
static GUI::GLCanvas3DManager s_canvas_mgr;
|
||||
|
||||
public:
|
||||
static void init_gl();
|
||||
static std::string get_gl_info(bool format_as_html, bool extensions);
|
||||
static bool use_VBOs();
|
||||
|
||||
static bool add_canvas(wxGLCanvas* canvas);
|
||||
static bool remove_canvas(wxGLCanvas* canvas);
|
||||
static void remove_all_canvases();
|
||||
|
||||
static bool init(wxGLCanvas* canvas);
|
||||
|
||||
static void set_as_dirty(wxGLCanvas* canvas);
|
||||
|
||||
static unsigned int get_volumes_count(wxGLCanvas* canvas);
|
||||
static void reset_volumes(wxGLCanvas* canvas);
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
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);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
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);
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
static GUI::GLCanvas3D* get_canvas(wxGLCanvas* canvas);
|
||||
#else
|
||||
static void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
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);
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
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);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
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 std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
|
||||
static std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
|
||||
|
||||
static int get_first_volume_id(wxGLCanvas* canvas, int obj_idx);
|
||||
static int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx);
|
||||
|
||||
#if ENABLE_MIRROR
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
static void mirror_selection(wxGLCanvas* canvas, Axis axis);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
#endif // ENABLE_MIRROR
|
||||
|
||||
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(wxGLCanvas* canvas);
|
||||
|
||||
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
|
||||
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
|
||||
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
|
||||
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
|
||||
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
|
||||
static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume);
|
||||
static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
|
||||
static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
|
||||
static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
|
||||
static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
136
src/slic3r/GUI/AboutDialog.cpp
Normal file
136
src/slic3r/GUI/AboutDialog.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#include "AboutDialog.hpp"
|
||||
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
AboutDialogLogo::AboutDialogLogo(wxWindow* parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||
{
|
||||
this->SetBackgroundColour(*wxWHITE);
|
||||
this->logo = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG);
|
||||
this->SetMinSize(this->logo.GetSize());
|
||||
|
||||
this->Bind(wxEVT_PAINT, &AboutDialogLogo::onRepaint, this);
|
||||
}
|
||||
|
||||
void AboutDialogLogo::onRepaint(wxEvent &event)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
dc.SetBackgroundMode(wxTRANSPARENT);
|
||||
|
||||
wxSize size = this->GetSize();
|
||||
int logo_w = this->logo.GetWidth();
|
||||
int logo_h = this->logo.GetHeight();
|
||||
dc.DrawBitmap(this->logo, (size.GetWidth() - logo_w)/2, (size.GetHeight() - logo_h)/2, true);
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
AboutDialog::AboutDialog()
|
||||
: wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxDefaultSize, wxCAPTION)
|
||||
{
|
||||
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
SetBackgroundColour(bgr_clr);
|
||||
wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20);
|
||||
|
||||
// logo
|
||||
wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG);
|
||||
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp));
|
||||
hsizer->Add(logo, 1, wxEXPAND | wxTOP | wxBOTTOM, 35);
|
||||
|
||||
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
#ifdef __WXMSW__
|
||||
int proportion = 2;
|
||||
#else
|
||||
int proportion = 3;
|
||||
#endif
|
||||
hsizer->Add(vsizer, proportion, wxEXPAND|wxLEFT, 20);
|
||||
|
||||
// title
|
||||
{
|
||||
wxStaticText* title = new wxStaticText(this, wxID_ANY, "Slic3r Prusa Edition", wxDefaultPosition, wxDefaultSize);
|
||||
wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
title_font.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
title_font.SetFamily(wxFONTFAMILY_ROMAN);
|
||||
title_font.SetPointSize(24);
|
||||
title->SetFont(title_font);
|
||||
vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 10);
|
||||
}
|
||||
|
||||
// version
|
||||
{
|
||||
auto version_string = _(L("Version"))+ " " + std::string(SLIC3R_VERSION);
|
||||
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
|
||||
wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
#ifdef __WXMSW__
|
||||
version_font.SetPointSize(9);
|
||||
#else
|
||||
version_font.SetPointSize(11);
|
||||
#endif
|
||||
version->SetFont(version_font);
|
||||
vsizer->Add(version, 0, wxALIGN_LEFT | wxBOTTOM, 10);
|
||||
}
|
||||
|
||||
// text
|
||||
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/);
|
||||
{
|
||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
|
||||
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
|
||||
|
||||
const int fs = font.GetPointSize()-1;
|
||||
int size[] = {fs,fs,fs,fs,fs,fs,fs};
|
||||
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
html->SetBorders(2);
|
||||
const auto text = wxString::Format(
|
||||
"<html>"
|
||||
"<body bgcolor= %s link= %s>"
|
||||
"<font color=%s>"
|
||||
"Copyright © 2016-2018 Prusa Research. <br />"
|
||||
"Copyright © 2011-2017 Alessandro Ranellucci. <br />"
|
||||
"<a href=\"http://slic3r.org/\">Slic3r</a> is licensed under the "
|
||||
"<a href=\"http://www.gnu.org/licenses/agpl-3.0.html\">GNU Affero General Public License, version 3</a>."
|
||||
"<br /><br />"
|
||||
"Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. "
|
||||
"Manual by Gary Hodgson. Inspired by the RepRap community. <br />"
|
||||
"Slic3r logo designed by Corey Daniels, <a href=\"http://www.famfamfam.com/lab/icons/silk/\">Silk Icon Set</a> designed by Mark James. "
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>", bgr_clr_str, text_clr_str, text_clr_str);
|
||||
html->SetPage(text);
|
||||
vsizer->Add(html, 1, wxEXPAND | wxBOTTOM, 10);
|
||||
html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this);
|
||||
}
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE);
|
||||
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
|
||||
this->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
|
||||
logo->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||
{
|
||||
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
|
||||
event.Skip(false);
|
||||
}
|
||||
|
||||
void AboutDialog::onCloseDialog(wxEvent &)
|
||||
{
|
||||
this->EndModal(wxID_CLOSE);
|
||||
this->Close();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
36
src/slic3r/GUI/AboutDialog.hpp
Normal file
36
src/slic3r/GUI/AboutDialog.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef slic3r_GUI_AboutDialog_hpp_
|
||||
#define slic3r_GUI_AboutDialog_hpp_
|
||||
|
||||
#include "GUI.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class AboutDialogLogo : public wxPanel
|
||||
{
|
||||
public:
|
||||
AboutDialogLogo(wxWindow* parent);
|
||||
|
||||
private:
|
||||
wxBitmap logo;
|
||||
void onRepaint(wxEvent &event);
|
||||
};
|
||||
|
||||
class AboutDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
AboutDialog();
|
||||
|
||||
private:
|
||||
void onLinkClicked(wxHtmlLinkEvent &event);
|
||||
void onCloseDialog(wxEvent &);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
266
src/slic3r/GUI/AppConfig.cpp
Normal file
266
src/slic3r/GUI/AppConfig.cpp
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <utility>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/cenv.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static const std::string VENDOR_PREFIX = "vendor:";
|
||||
static const std::string MODEL_PREFIX = "model:";
|
||||
static const std::string VERSION_CHECK_URL = "https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/Slic3rPE.version";
|
||||
|
||||
void AppConfig::reset()
|
||||
{
|
||||
m_storage.clear();
|
||||
set_defaults();
|
||||
};
|
||||
|
||||
// Override missing or keys with their defaults.
|
||||
void AppConfig::set_defaults()
|
||||
{
|
||||
// Reset the empty fields to defaults.
|
||||
if (get("autocenter").empty())
|
||||
set("autocenter", "0");
|
||||
// Disable background processing by default as it is not stable.
|
||||
if (get("background_processing").empty())
|
||||
set("background_processing", "0");
|
||||
// If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
// By default, Prusa has the controller hidden.
|
||||
if (get("no_controller").empty())
|
||||
set("no_controller", "1");
|
||||
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
|
||||
if (get("no_defaults").empty())
|
||||
set("no_defaults", "1");
|
||||
if (get("show_incompatible_presets").empty())
|
||||
set("show_incompatible_presets", "0");
|
||||
|
||||
if (get("version_check").empty())
|
||||
set("version_check", "1");
|
||||
if (get("preset_update").empty())
|
||||
set("preset_update", "1");
|
||||
|
||||
// Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers.
|
||||
// https://github.com/prusa3d/Slic3r/issues/233
|
||||
if (get("use_legacy_opengl").empty())
|
||||
set("use_legacy_opengl", "0");
|
||||
|
||||
if (get("remember_output_path").empty())
|
||||
set("remember_output_path", "1");
|
||||
|
||||
// Remove legacy window positions/sizes
|
||||
erase("", "main_frame_maximized");
|
||||
erase("", "main_frame_pos");
|
||||
erase("", "main_frame_size");
|
||||
erase("", "object_settings_maximized");
|
||||
erase("", "object_settings_pos");
|
||||
erase("", "object_settings_size");
|
||||
}
|
||||
|
||||
void AppConfig::load()
|
||||
{
|
||||
// 1) Read the complete config file into a boost::property_tree.
|
||||
namespace pt = boost::property_tree;
|
||||
pt::ptree tree;
|
||||
boost::nowide::ifstream ifs(AppConfig::config_path());
|
||||
pt::read_ini(ifs, tree);
|
||||
|
||||
// 2) Parse the property_tree, extract the sections and key / value pairs.
|
||||
for (const auto §ion : tree) {
|
||||
if (section.second.empty()) {
|
||||
// This may be a top level (no section) entry, or an empty section.
|
||||
std::string data = section.second.data();
|
||||
if (! data.empty())
|
||||
// If there is a non-empty data, then it must be a top-level (without a section) config entry.
|
||||
m_storage[""][section.first] = data;
|
||||
} else if (boost::starts_with(section.first, VENDOR_PREFIX)) {
|
||||
// This is a vendor section listing enabled model / variants
|
||||
const auto vendor_name = section.first.substr(VENDOR_PREFIX.size());
|
||||
auto &vendor = m_vendors[vendor_name];
|
||||
for (const auto &kvp : section.second) {
|
||||
if (! boost::starts_with(kvp.first, MODEL_PREFIX)) { continue; }
|
||||
const auto model_name = kvp.first.substr(MODEL_PREFIX.size());
|
||||
std::vector<std::string> variants;
|
||||
if (! unescape_strings_cstyle(kvp.second.data(), variants)) { continue; }
|
||||
for (const auto &variant : variants) {
|
||||
vendor[model_name].insert(variant);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This must be a section name. Read the entries of a section.
|
||||
std::map<std::string, std::string> &storage = m_storage[section.first];
|
||||
for (auto &kvp : section.second)
|
||||
storage[kvp.first] = kvp.second.data();
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out if datadir has legacy presets
|
||||
auto ini_ver = Semver::parse(get("version"));
|
||||
m_legacy_datadir = false;
|
||||
if (ini_ver) {
|
||||
m_orig_version = *ini_ver;
|
||||
// Make 1.40.0 alphas compare well
|
||||
ini_ver->set_metadata(boost::none);
|
||||
ini_ver->set_prerelease(boost::none);
|
||||
m_legacy_datadir = ini_ver < Semver(1, 40, 0);
|
||||
}
|
||||
|
||||
// Override missing or keys with their defaults.
|
||||
this->set_defaults();
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
void AppConfig::save()
|
||||
{
|
||||
boost::nowide::ofstream c;
|
||||
c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc);
|
||||
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
// Make sure the "no" category is written first.
|
||||
for (const std::pair<std::string, std::string> &kvp : m_storage[""])
|
||||
c << kvp.first << " = " << kvp.second << std::endl;
|
||||
// Write the other categories.
|
||||
for (const auto category : m_storage) {
|
||||
if (category.first.empty())
|
||||
continue;
|
||||
c << std::endl << "[" << category.first << "]" << std::endl;
|
||||
for (const std::pair<std::string, std::string> &kvp : category.second)
|
||||
c << kvp.first << " = " << kvp.second << std::endl;
|
||||
}
|
||||
// Write vendor sections
|
||||
for (const auto &vendor : m_vendors) {
|
||||
size_t size_sum = 0;
|
||||
for (const auto &model : vendor.second) { size_sum += model.second.size(); }
|
||||
if (size_sum == 0) { continue; }
|
||||
|
||||
c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
|
||||
|
||||
for (const auto &model : vendor.second) {
|
||||
if (model.second.size() == 0) { continue; }
|
||||
const std::vector<std::string> variants(model.second.begin(), model.second.end());
|
||||
const auto escaped = escape_strings_cstyle(variants);
|
||||
c << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
|
||||
}
|
||||
}
|
||||
c.close();
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const
|
||||
{
|
||||
const auto it_v = m_vendors.find(vendor);
|
||||
if (it_v == m_vendors.end()) { return false; }
|
||||
const auto it_m = it_v->second.find(model);
|
||||
return it_m == it_v->second.end() ? false : it_m->second.find(variant) != it_m->second.end();
|
||||
}
|
||||
|
||||
void AppConfig::set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
if (get_variant(vendor, model, variant)) { return; }
|
||||
m_vendors[vendor][model].insert(variant);
|
||||
} else {
|
||||
auto it_v = m_vendors.find(vendor);
|
||||
if (it_v == m_vendors.end()) { return; }
|
||||
auto it_m = it_v->second.find(model);
|
||||
if (it_m == it_v->second.end()) { return; }
|
||||
auto it_var = it_m->second.find(variant);
|
||||
if (it_var == it_m->second.end()) { return; }
|
||||
it_m->second.erase(it_var);
|
||||
}
|
||||
// If we got here, there was an update
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void AppConfig::set_vendors(const AppConfig &from)
|
||||
{
|
||||
m_vendors = from.m_vendors;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
std::string AppConfig::get_last_dir() const
|
||||
{
|
||||
const auto it = m_storage.find("recent");
|
||||
if (it != m_storage.end()) {
|
||||
{
|
||||
const auto it2 = it->second.find("skein_directory");
|
||||
if (it2 != it->second.end() && ! it2->second.empty())
|
||||
return it2->second;
|
||||
}
|
||||
{
|
||||
const auto it2 = it->second.find("config_directory");
|
||||
if (it2 != it->second.end() && ! it2->second.empty())
|
||||
return it2->second;
|
||||
}
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
void AppConfig::update_config_dir(const std::string &dir)
|
||||
{
|
||||
this->set("recent", "config_directory", dir);
|
||||
}
|
||||
|
||||
void AppConfig::update_skein_dir(const std::string &dir)
|
||||
{
|
||||
this->set("recent", "skein_directory", dir);
|
||||
}
|
||||
|
||||
std::string AppConfig::get_last_output_dir(const std::string &alt) const
|
||||
{
|
||||
const auto it = m_storage.find("");
|
||||
if (it != m_storage.end()) {
|
||||
const auto it2 = it->second.find("last_output_path");
|
||||
const auto it3 = it->second.find("remember_output_path");
|
||||
if (it2 != it->second.end() && it3 != it->second.end() && ! it2->second.empty() && it3->second == "1")
|
||||
return it2->second;
|
||||
}
|
||||
return alt;
|
||||
}
|
||||
|
||||
void AppConfig::update_last_output_dir(const std::string &dir)
|
||||
{
|
||||
this->set("", "last_output_path", dir);
|
||||
}
|
||||
|
||||
void AppConfig::reset_selections()
|
||||
{
|
||||
auto it = m_storage.find("presets");
|
||||
if (it != m_storage.end()) {
|
||||
it->second.erase("print");
|
||||
it->second.erase("filament");
|
||||
it->second.erase("sla_material");
|
||||
it->second.erase("printer");
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string AppConfig::config_path()
|
||||
{
|
||||
return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string();
|
||||
}
|
||||
|
||||
std::string AppConfig::version_check_url() const
|
||||
{
|
||||
auto from_settings = get("version_check_url");
|
||||
return from_settings.empty() ? VERSION_CHECK_URL : from_settings;
|
||||
}
|
||||
|
||||
bool AppConfig::exists()
|
||||
{
|
||||
return boost::filesystem::exists(AppConfig::config_path());
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
140
src/slic3r/GUI/AppConfig.hpp
Normal file
140
src/slic3r/GUI/AppConfig.hpp
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#ifndef slic3r_AppConfig_hpp_
|
||||
#define slic3r_AppConfig_hpp_
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "slic3r/Utils/Semver.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig
|
||||
{
|
||||
public:
|
||||
AppConfig() :
|
||||
m_dirty(false),
|
||||
m_orig_version(Semver::invalid()),
|
||||
m_legacy_datadir(false)
|
||||
{
|
||||
this->reset();
|
||||
}
|
||||
|
||||
// Clear and reset to defaults.
|
||||
void reset();
|
||||
// Override missing or keys with their defaults.
|
||||
void set_defaults();
|
||||
|
||||
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
|
||||
void load();
|
||||
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
|
||||
void save();
|
||||
|
||||
// Does this config need to be saved?
|
||||
bool dirty() const { return m_dirty; }
|
||||
|
||||
// Const accessor, it will return false if a section or a key does not exist.
|
||||
bool get(const std::string §ion, const std::string &key, std::string &value) const
|
||||
{
|
||||
value.clear();
|
||||
auto it = m_storage.find(section);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it2 = it->second.find(key);
|
||||
if (it2 == it->second.end())
|
||||
return false;
|
||||
value = it2->second;
|
||||
return true;
|
||||
}
|
||||
std::string get(const std::string §ion, const std::string &key) const
|
||||
{ std::string value; this->get(section, key, value); return value; }
|
||||
std::string get(const std::string &key) const
|
||||
{ std::string value; this->get("", key, value); return value; }
|
||||
void set(const std::string §ion, const std::string &key, const std::string &value)
|
||||
{
|
||||
std::string &old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
old = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
void set(const std::string &key, const std::string &value)
|
||||
{ this->set("", key, value); }
|
||||
bool has(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
auto it = m_storage.find(section);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it2 = it->second.find(key);
|
||||
return it2 != it->second.end() && ! it2->second.empty();
|
||||
}
|
||||
bool has(const std::string &key) const
|
||||
{ return this->has("", key); }
|
||||
|
||||
void erase(const std::string §ion, const std::string &key)
|
||||
{
|
||||
auto it = m_storage.find(section);
|
||||
if (it != m_storage.end()) {
|
||||
it->second.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_section(const std::string §ion)
|
||||
{ m_storage[section].clear(); }
|
||||
|
||||
typedef std::map<std::string, std::map<std::string, std::set<std::string>>> VendorMap;
|
||||
bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const;
|
||||
void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable);
|
||||
void set_vendors(const AppConfig &from);
|
||||
void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; }
|
||||
void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
|
||||
const VendorMap& vendors() const { return m_vendors; }
|
||||
|
||||
// return recent/skein_directory or recent/config_directory or empty string.
|
||||
std::string get_last_dir() const;
|
||||
void update_config_dir(const std::string &dir);
|
||||
void update_skein_dir(const std::string &dir);
|
||||
|
||||
std::string get_last_output_dir(const std::string &alt) const;
|
||||
void update_last_output_dir(const std::string &dir);
|
||||
|
||||
// reset the current print / filament / printer selections, so that
|
||||
// the PresetBundle::load_selections(const AppConfig &config) call will select
|
||||
// the first non-default preset when called.
|
||||
void reset_selections();
|
||||
|
||||
// Get the default config path from Slic3r::data_dir().
|
||||
static std::string config_path();
|
||||
|
||||
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
|
||||
bool legacy_datadir() const { return m_legacy_datadir; }
|
||||
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
|
||||
|
||||
// Get the Slic3r version check url.
|
||||
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
|
||||
std::string version_check_url() const;
|
||||
|
||||
// Returns the original Slic3r version found in the ini file before it was overwritten
|
||||
// by the current version
|
||||
Semver orig_version() const { return m_orig_version; }
|
||||
|
||||
// Does the config file exist?
|
||||
static bool exists();
|
||||
|
||||
private:
|
||||
// Map of section, name -> value
|
||||
std::map<std::string, std::map<std::string, std::string>> m_storage;
|
||||
// Map of enabled vendors / models / variants
|
||||
VendorMap m_vendors;
|
||||
// Has any value been modified since the config.ini has been last saved or loaded?
|
||||
bool m_dirty;
|
||||
// Original version found in the ini file before it was overwritten
|
||||
Semver m_orig_version;
|
||||
// Whether the existing version is before system profiles & configuration updating
|
||||
bool m_legacy_datadir;
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_AppConfig_hpp_ */
|
||||
234
src/slic3r/GUI/BackgroundSlicingProcess.cpp
Normal file
234
src/slic3r/GUI/BackgroundSlicingProcess.cpp
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
|
||||
#include "../../libslic3r/Print.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include "../../libslic3r/GCode/PostProcessor.hpp"
|
||||
|
||||
//#undef NDEBUG
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
BackgroundSlicingProcess::BackgroundSlicingProcess()
|
||||
{
|
||||
boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data());
|
||||
temp_path /= (boost::format(".%1%.gcode") % get_current_pid()).str();
|
||||
m_temp_output_path = temp_path.string();
|
||||
}
|
||||
|
||||
BackgroundSlicingProcess::~BackgroundSlicingProcess()
|
||||
{
|
||||
this->stop();
|
||||
this->join_background_thread();
|
||||
boost::nowide::remove(m_temp_output_path.c_str());
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::thread_proc()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
// Let the caller know we are ready to run the background processing task.
|
||||
m_state = STATE_IDLE;
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
for (;;) {
|
||||
assert(m_state == STATE_IDLE || m_state == STATE_CANCELED || m_state == STATE_FINISHED);
|
||||
// Wait until a new task is ready to be executed, or this thread should be finished.
|
||||
lck.lock();
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_STARTED || m_state == STATE_EXIT; });
|
||||
if (m_state == STATE_EXIT)
|
||||
// Exiting this thread.
|
||||
break;
|
||||
// Process the background slicing task.
|
||||
m_state = STATE_RUNNING;
|
||||
lck.unlock();
|
||||
std::string error;
|
||||
try {
|
||||
assert(m_print != nullptr);
|
||||
m_print->process();
|
||||
if (! m_print->canceled()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_sliced_id));
|
||||
m_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
|
||||
if (! m_print->canceled() && ! this->is_step_done(bspsGCodeFinalize)) {
|
||||
this->set_step_started(bspsGCodeFinalize);
|
||||
if (! m_export_path.empty()) {
|
||||
//FIXME localize the messages
|
||||
if (copy_file(m_temp_output_path, m_export_path) != 0)
|
||||
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
|
||||
m_print->set_status(95, "Running post-processing scripts");
|
||||
run_post_process_scripts(m_export_path, m_print->config());
|
||||
m_print->set_status(100, "G-code file exported to " + m_export_path);
|
||||
} else {
|
||||
m_print->set_status(100, "Slicing complete");
|
||||
}
|
||||
this->set_step_done(bspsGCodeFinalize);
|
||||
}
|
||||
}
|
||||
} catch (CanceledException &ex) {
|
||||
// Canceled, this is all right.
|
||||
assert(m_print->canceled());
|
||||
} catch (std::exception &ex) {
|
||||
error = ex.what();
|
||||
} catch (...) {
|
||||
error = "Unknown C++ exception.";
|
||||
}
|
||||
lck.lock();
|
||||
m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED;
|
||||
wxCommandEvent evt(m_event_finished_id);
|
||||
evt.SetString(error);
|
||||
evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0));
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
|
||||
m_print->restart();
|
||||
lck.unlock();
|
||||
// Let the UI thread wake up if it is waiting for the background task to finish.
|
||||
m_condition.notify_one();
|
||||
// Let the UI thread see the result.
|
||||
}
|
||||
m_state = STATE_EXITED;
|
||||
lck.unlock();
|
||||
// End of the background processing thread. The UI thread should join m_thread now.
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::join_background_thread()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
// Worker thread has not been started yet.
|
||||
assert(! m_thread.joinable());
|
||||
} else {
|
||||
assert(m_state == STATE_IDLE);
|
||||
assert(m_thread.joinable());
|
||||
// Notify the worker thread to exit.
|
||||
m_state = STATE_EXIT;
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
// Wait until the worker thread exits.
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::start()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
// The worker thread is not running yet. Start it.
|
||||
assert(! m_thread.joinable());
|
||||
m_thread = std::thread([this]{this->thread_proc();});
|
||||
// Wait until the worker thread is ready to execute the background processing task.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
|
||||
}
|
||||
assert(m_state == STATE_IDLE || this->running());
|
||||
if (this->running())
|
||||
// The background processing thread is already running.
|
||||
return false;
|
||||
if (! this->idle())
|
||||
throw std::runtime_error("Cannot start a background task, the worker thread is not idle.");
|
||||
m_state = STATE_STARTED;
|
||||
m_print->set_cancel_callback([this](){ this->stop(); });
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::stop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
// this->m_export_path.clear();
|
||||
return false;
|
||||
}
|
||||
// assert(this->running());
|
||||
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
|
||||
m_print->cancel();
|
||||
// Wait until the background processing stops by being canceled.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
|
||||
// In the "Canceled" state. Reset the state to "Idle".
|
||||
m_state = STATE_IDLE;
|
||||
m_print->set_cancel_callback([](){});
|
||||
} else if (m_state == STATE_FINISHED || m_state == STATE_CANCELED) {
|
||||
// In the "Finished" or "Canceled" state. Reset the state to "Idle".
|
||||
m_state = STATE_IDLE;
|
||||
m_print->set_cancel_callback([](){});
|
||||
}
|
||||
// this->m_export_path.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
// processed steps to be invalidated, therefore the task will need to be restarted.
|
||||
bool BackgroundSlicingProcess::apply_config(const DynamicPrintConfig &config)
|
||||
{
|
||||
this->stop();
|
||||
bool invalidated = m_print->apply_config(config);
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
// processed steps to be invalidated, therefore the task will need to be restarted.
|
||||
Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const DynamicPrintConfig &config)
|
||||
{
|
||||
// this->stop();
|
||||
Print::ApplyStatus invalidated = m_print->apply(model, config);
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
// Set the output path of the G-code.
|
||||
void BackgroundSlicingProcess::schedule_export(const std::string &path)
|
||||
{
|
||||
assert(m_export_path.empty());
|
||||
if (! m_export_path.empty())
|
||||
return;
|
||||
|
||||
// Guard against entering the export step before changing the export path.
|
||||
tbb::mutex::scoped_lock lock(m_step_state_mutex);
|
||||
this->invalidate_step(bspsGCodeFinalize);
|
||||
m_export_path = path;
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::reset_export()
|
||||
{
|
||||
assert(! this->running());
|
||||
if (! this->running()) {
|
||||
m_export_path.clear();
|
||||
// invalidate_step expects the mutex to be locked.
|
||||
tbb::mutex::scoped_lock lock(m_step_state_mutex);
|
||||
this->invalidate_step(bspsGCodeFinalize);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step)
|
||||
{
|
||||
m_step_state.set_started(step, m_step_state_mutex);
|
||||
if (m_print->canceled())
|
||||
throw CanceledException();
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::set_step_done(BackgroundSlicingProcessStep step)
|
||||
{
|
||||
m_step_state.set_done(step, m_step_state_mutex);
|
||||
if (m_print->canceled())
|
||||
throw CanceledException();
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::invalidate_step(BackgroundSlicingProcessStep step)
|
||||
{
|
||||
bool invalidated = m_step_state.invalidate(step, m_step_state_mutex, [this](){ this->stop(); });
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::invalidate_all_steps()
|
||||
{
|
||||
return m_step_state.invalidate_all(m_step_state_mutex, [this](){ this->stop(); });
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
117
src/slic3r/GUI/BackgroundSlicingProcess.hpp
Normal file
117
src/slic3r/GUI/BackgroundSlicingProcess.hpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_
|
||||
#define slic3r_GUI_BackgroundSlicingProcess_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "Print.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class GCodePreviewData;
|
||||
class Model;
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
enum BackgroundSlicingProcessStep {
|
||||
bspsGCodeFinalize, bspsCount,
|
||||
};
|
||||
|
||||
// Support for the GUI background processing (Slicing and G-code generation).
|
||||
// As of now this class is not declared in Slic3r::GUI due to the Perl bindings limits.
|
||||
class BackgroundSlicingProcess
|
||||
{
|
||||
public:
|
||||
BackgroundSlicingProcess();
|
||||
// Stop the background processing and finalize the bacgkround processing thread, remove temp files.
|
||||
~BackgroundSlicingProcess();
|
||||
|
||||
void set_print(Print *print) { m_print = print; }
|
||||
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
|
||||
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
|
||||
// and the background processing will transition into G-code export.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_sliced_event(int event_id) { m_event_sliced_id = event_id; }
|
||||
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the G-code export is finished.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
|
||||
|
||||
// Start the background processing. Returns false if the background processing was already running.
|
||||
bool start();
|
||||
// Cancel the background processing. Returns false if the background processing was not running.
|
||||
// A stopped background processing may be restarted with start().
|
||||
bool stop();
|
||||
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
// processed steps to be invalidated, therefore the task will need to be restarted.
|
||||
bool apply_config(const DynamicPrintConfig &config);
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
// processed steps to be invalidated, therefore the task will need to be restarted.
|
||||
Print::ApplyStatus apply(const Model &model, const DynamicPrintConfig &config);
|
||||
// Set the export path of the G-code.
|
||||
// Once the path is set, the G-code
|
||||
void schedule_export(const std::string &path);
|
||||
// Clear m_export_path.
|
||||
void reset_export();
|
||||
// Once the G-code export is scheduled, the apply() methods will do nothing.
|
||||
bool is_export_scheduled() const { return ! m_export_path.empty(); }
|
||||
|
||||
enum State {
|
||||
// m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
|
||||
STATE_INITIAL = 0,
|
||||
// m_thread is waiting for the task to execute.
|
||||
STATE_IDLE,
|
||||
STATE_STARTED,
|
||||
// m_thread is executing a task.
|
||||
STATE_RUNNING,
|
||||
// m_thread finished executing a task, and it is waiting until the UI thread picks up the results.
|
||||
STATE_FINISHED,
|
||||
// m_thread finished executing a task, the task has been canceled by the UI thread, therefore the UI thread will not be notified.
|
||||
STATE_CANCELED,
|
||||
// m_thread exited the loop and it is going to finish. The UI thread should join on m_thread.
|
||||
STATE_EXIT,
|
||||
STATE_EXITED,
|
||||
};
|
||||
State state() const { return m_state; }
|
||||
bool idle() const { return m_state == STATE_IDLE; }
|
||||
bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; }
|
||||
|
||||
private:
|
||||
void thread_proc();
|
||||
void join_background_thread();
|
||||
|
||||
Print *m_print = nullptr;
|
||||
// Data structure, to which the G-code export writes its annotations.
|
||||
GCodePreviewData *m_gcode_preview_data = nullptr;
|
||||
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
|
||||
std::string m_temp_output_path;
|
||||
// Output path provided by the user. The output path may be set even if the slicing is running,
|
||||
// but once set, it cannot be re-set.
|
||||
std::string m_export_path;
|
||||
// Thread, on which the background processing is executed. The thread will always be present
|
||||
// and ready to execute the slicing process.
|
||||
std::thread m_thread;
|
||||
// Mutex and condition variable to synchronize m_thread with the UI thread.
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
State m_state = STATE_INITIAL;
|
||||
|
||||
PrintState<BackgroundSlicingProcessStep, bspsCount> m_step_state;
|
||||
mutable tbb::mutex m_step_state_mutex;
|
||||
void set_step_started(BackgroundSlicingProcessStep step);
|
||||
void set_step_done(BackgroundSlicingProcessStep step);
|
||||
bool is_step_done(BackgroundSlicingProcessStep step) const { return m_step_state.is_done(step); }
|
||||
bool invalidate_step(BackgroundSlicingProcessStep step);
|
||||
bool invalidate_all_steps();
|
||||
|
||||
// wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue.
|
||||
int m_event_sliced_id = 0;
|
||||
// wxWidgets command ID to be sent to the platter to inform that the task finished.
|
||||
int m_event_finished_id = 0;
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_BackgroundSlicingProcess_hpp_ */
|
||||
337
src/slic3r/GUI/BedShapeDialog.cpp
Normal file
337
src/slic3r/GUI/BedShapeDialog.cpp
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
#include "BedShapeDialog.hpp"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/wx.h>
|
||||
#include "Polygon.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include <wx/numformatter.h>
|
||||
#include "Model.hpp"
|
||||
#include "boost/nowide/iostream.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt)
|
||||
{
|
||||
m_panel = new BedShapePanel(this);
|
||||
m_panel->build_panel(default_pt);
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(m_panel, 1, wxEXPAND);
|
||||
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetMinSize(GetSize());
|
||||
main_sizer->SetSizeHints(this);
|
||||
|
||||
// needed to actually free memory
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent e){
|
||||
EndModal(wxID_OK);
|
||||
Destroy();
|
||||
}));
|
||||
}
|
||||
|
||||
void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
|
||||
{
|
||||
// on_change(nullptr);
|
||||
|
||||
auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape")));
|
||||
auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
// shape options
|
||||
m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(300, -1), wxCHB_TOP);
|
||||
sbsizer->Add(m_shape_options_book);
|
||||
|
||||
auto optgroup = init_shape_options_page(_(L("Rectangular")));
|
||||
ConfigOptionDef def;
|
||||
def.type = coPoints;
|
||||
def.default_value = new ConfigOptionPoints{ Vec2d(200, 200) };
|
||||
def.label = L("Size");
|
||||
def.tooltip = L("Size in X and Y of the rectangular plate.");
|
||||
Option option(def, "rect_size");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
def.type = coPoints;
|
||||
def.default_value = new ConfigOptionPoints{ Vec2d(0, 0) };
|
||||
def.label = L("Origin");
|
||||
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
|
||||
option = Option(def, "rect_origin");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
optgroup = init_shape_options_page(_(L("Circular")));
|
||||
def.type = coFloat;
|
||||
def.default_value = new ConfigOptionFloat(200);
|
||||
def.sidetext = L("mm");
|
||||
def.label = L("Diameter");
|
||||
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
|
||||
option = Option(def, "diameter");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
optgroup = init_shape_options_page(_(L("Custom")));
|
||||
Line line{ "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn);
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e)
|
||||
{
|
||||
load_stl();
|
||||
}));
|
||||
|
||||
return sizer;
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
|
||||
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e)
|
||||
{
|
||||
update_shape();
|
||||
}));
|
||||
|
||||
// right pane with preview canvas
|
||||
m_canvas = new Bed_2D(this);
|
||||
m_canvas->m_bed_shape = default_pt->values;
|
||||
|
||||
// main sizer
|
||||
auto top_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10);
|
||||
if (m_canvas)
|
||||
top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ;
|
||||
|
||||
SetSizerAndFit(top_sizer);
|
||||
|
||||
set_shape(default_pt);
|
||||
update_preview();
|
||||
}
|
||||
|
||||
#define SHAPE_RECTANGULAR 0
|
||||
#define SHAPE_CIRCULAR 1
|
||||
#define SHAPE_CUSTOM 2
|
||||
|
||||
// Called from the constructor.
|
||||
// Create a panel for a rectangular / circular / custom bed shape.
|
||||
ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title){
|
||||
|
||||
auto panel = new wxPanel(m_shape_options_book);
|
||||
ConfigOptionsGroupShp optgroup;
|
||||
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
|
||||
|
||||
optgroup->label_width = 100;
|
||||
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
|
||||
update_shape();
|
||||
};
|
||||
|
||||
m_optgroups.push_back(optgroup);
|
||||
panel->SetSizerAndFit(optgroup->sizer);
|
||||
m_shape_options_book->AddPage(panel, title);
|
||||
|
||||
return optgroup;
|
||||
}
|
||||
|
||||
// Called from the constructor.
|
||||
// Set the initial bed shape from a list of points.
|
||||
// Deduce the bed shape type(rect, circle, custom)
|
||||
// This routine shall be smart enough if the user messes up
|
||||
// with the list of points in the ini file directly.
|
||||
void BedShapePanel::set_shape(ConfigOptionPoints* points)
|
||||
{
|
||||
auto polygon = Polygon::new_scale(points->values);
|
||||
|
||||
// is this a rectangle ?
|
||||
if (points->size() == 4) {
|
||||
auto lines = polygon.lines();
|
||||
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
|
||||
// okay, it's a rectangle
|
||||
// find origin
|
||||
coordf_t x_min, x_max, y_min, y_max;
|
||||
x_max = x_min = points->values[0](0);
|
||||
y_max = y_min = points->values[0](1);
|
||||
for (auto pt : points->values)
|
||||
{
|
||||
x_min = std::min(x_min, pt(0));
|
||||
x_max = std::max(x_max, pt(0));
|
||||
y_min = std::min(y_min, pt(1));
|
||||
y_max = std::max(y_max, pt(1));
|
||||
}
|
||||
|
||||
auto origin = new ConfigOptionPoints{ Vec2d(-x_min, -y_min) };
|
||||
|
||||
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
|
||||
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
|
||||
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
|
||||
optgroup->set_value("rect_origin", origin);
|
||||
update_shape();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// is this a circle ?
|
||||
{
|
||||
// Analyze the array of points.Do they reside on a circle ?
|
||||
auto center = polygon.bounding_box().center();
|
||||
std::vector<double> vertex_distances;
|
||||
double avg_dist = 0;
|
||||
for (auto pt: polygon.points)
|
||||
{
|
||||
double distance = (pt - center).cast<double>().norm();
|
||||
vertex_distances.push_back(distance);
|
||||
avg_dist += distance;
|
||||
}
|
||||
|
||||
avg_dist /= vertex_distances.size();
|
||||
bool defined_value = true;
|
||||
for (auto el: vertex_distances)
|
||||
{
|
||||
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
|
||||
defined_value = false;
|
||||
break;
|
||||
}
|
||||
if (defined_value) {
|
||||
// all vertices are equidistant to center
|
||||
m_shape_options_book->SetSelection(SHAPE_CIRCULAR);
|
||||
auto optgroup = m_optgroups[SHAPE_CIRCULAR];
|
||||
boost::any ret = wxNumberFormatter::ToString(unscale<double>(avg_dist * 2), 0);
|
||||
optgroup->set_value("diameter", ret);
|
||||
update_shape();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (points->size() < 3) {
|
||||
// Invalid polygon.Revert to default bed dimensions.
|
||||
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
|
||||
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
|
||||
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) });
|
||||
optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) });
|
||||
update_shape();
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a custom bed shape, use the polygon provided.
|
||||
m_shape_options_book->SetSelection(SHAPE_CUSTOM);
|
||||
// Copy the polygon to the canvas, make a copy of the array.
|
||||
m_canvas->m_bed_shape = points->values;
|
||||
update_shape();
|
||||
}
|
||||
|
||||
void BedShapePanel::update_preview()
|
||||
{
|
||||
if (m_canvas) m_canvas->Refresh();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// Update the bed shape from the dialog fields.
|
||||
void BedShapePanel::update_shape()
|
||||
{
|
||||
auto page_idx = m_shape_options_book->GetSelection();
|
||||
if (page_idx == SHAPE_RECTANGULAR) {
|
||||
Vec2d rect_size(Vec2d::Zero());
|
||||
Vec2d rect_origin(Vec2d::Zero());
|
||||
try{
|
||||
rect_size = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
|
||||
catch (const std::exception &e){
|
||||
return;
|
||||
}
|
||||
try{
|
||||
rect_origin = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
|
||||
}
|
||||
catch (const std::exception &e){
|
||||
return;}
|
||||
|
||||
auto x = rect_size(0);
|
||||
auto y = rect_size(1);
|
||||
// empty strings or '-' or other things
|
||||
if (x == 0 || y == 0) return;
|
||||
double x0 = 0.0;
|
||||
double y0 = 0.0;
|
||||
double x1 = x;
|
||||
double y1 = y;
|
||||
|
||||
auto dx = rect_origin(0);
|
||||
auto dy = rect_origin(1);
|
||||
|
||||
x0 -= dx;
|
||||
x1 -= dx;
|
||||
y0 -= dy;
|
||||
y1 -= dy;
|
||||
m_canvas->m_bed_shape = { Vec2d(x0, y0),
|
||||
Vec2d(x1, y0),
|
||||
Vec2d(x1, y1),
|
||||
Vec2d(x0, y1)};
|
||||
}
|
||||
else if(page_idx == SHAPE_CIRCULAR) {
|
||||
double diameter;
|
||||
try{
|
||||
diameter = boost::any_cast<double>(m_optgroups[SHAPE_CIRCULAR]->get_value("diameter"));
|
||||
}
|
||||
catch (const std::exception &e){
|
||||
return;
|
||||
}
|
||||
if (diameter == 0.0) return ;
|
||||
auto r = diameter / 2;
|
||||
auto twopi = 2 * PI;
|
||||
auto edges = 60;
|
||||
std::vector<Vec2d> points;
|
||||
for (size_t i = 1; i <= 60; ++i){
|
||||
auto angle = i * twopi / edges;
|
||||
points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
|
||||
}
|
||||
m_canvas->m_bed_shape = points;
|
||||
}
|
||||
|
||||
// $self->{on_change}->();
|
||||
update_preview();
|
||||
}
|
||||
|
||||
// Loads an stl file, projects it to the XY plane and calculates a polygon.
|
||||
void BedShapePanel::load_stl()
|
||||
{
|
||||
auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "",
|
||||
file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog->ShowModal() != wxID_OK) {
|
||||
dialog->Destroy();
|
||||
return;
|
||||
}
|
||||
wxArrayString input_file;
|
||||
dialog->GetPaths(input_file);
|
||||
dialog->Destroy();
|
||||
|
||||
std::string file_name = input_file[0].ToStdString();
|
||||
|
||||
Model model;
|
||||
try {
|
||||
model = Model::read_from_file(file_name);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
auto msg = _(L("Error! ")) + file_name + " : " + e.what() + ".";
|
||||
show_error(this, msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
auto mesh = model.mesh();
|
||||
auto expolygons = mesh.horizontal_projection();
|
||||
|
||||
if (expolygons.size() == 0) {
|
||||
show_error(this, _(L("The selected file contains no geometry.")));
|
||||
return;
|
||||
}
|
||||
if (expolygons.size() > 1) {
|
||||
show_error(this, _(L("The selected file contains several disjoint areas. This is not supported.")));
|
||||
return;
|
||||
}
|
||||
|
||||
auto polygon = expolygons[0].contour;
|
||||
std::vector<Vec2d> points;
|
||||
for (auto pt : polygon.points)
|
||||
points.push_back(unscale(pt));
|
||||
m_canvas->m_bed_shape = points;
|
||||
update_preview();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
56
src/slic3r/GUI/BedShapeDialog.hpp
Normal file
56
src/slic3r/GUI/BedShapeDialog.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#ifndef slic3r_BedShapeDialog_hpp_
|
||||
#define slic3r_BedShapeDialog_hpp_
|
||||
// The bed shape dialog.
|
||||
// The dialog opens from Print Settins tab->Bed Shape : Set...
|
||||
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "2DBed.hpp"
|
||||
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/choicebk.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
|
||||
class BedShapePanel : public wxPanel
|
||||
{
|
||||
wxChoicebook* m_shape_options_book;
|
||||
Bed_2D* m_canvas;
|
||||
|
||||
std::vector <ConfigOptionsGroupShp> m_optgroups;
|
||||
|
||||
public:
|
||||
BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY){}
|
||||
~BedShapePanel(){}
|
||||
|
||||
void build_panel(ConfigOptionPoints* default_pt);
|
||||
|
||||
ConfigOptionsGroupShp init_shape_options_page(wxString title);
|
||||
void set_shape(ConfigOptionPoints* points);
|
||||
void update_preview();
|
||||
void update_shape();
|
||||
void load_stl();
|
||||
|
||||
// Returns the resulting bed shape polygon. This value will be stored to the ini file.
|
||||
std::vector<Vec2d> GetValue() { return m_canvas->m_bed_shape; }
|
||||
};
|
||||
|
||||
class BedShapeDialog : public wxDialog
|
||||
{
|
||||
BedShapePanel* m_panel;
|
||||
public:
|
||||
BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _(L("Bed Shape")),
|
||||
wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER){}
|
||||
~BedShapeDialog(){ }
|
||||
|
||||
void build_dialog(ConfigOptionPoints* default_pt);
|
||||
std::vector<Vec2d> GetValue() { return m_panel->GetValue(); }
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
|
||||
#endif /* slic3r_BedShapeDialog_hpp_ */
|
||||
174
src/slic3r/GUI/BitmapCache.cpp
Normal file
174
src/slic3r/GUI/BitmapCache.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
#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));
|
||||
//FIXME The following method is missing from wxWidgets 3.1.1.
|
||||
// It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha().
|
||||
//data.UseAlpha();
|
||||
if (data) {
|
||||
for (int r = 0; r < bmp->GetHeight(); ++ r) {
|
||||
wxAlphaPixelData::Iterator src(data);
|
||||
src.Offset(data, 0, r);
|
||||
unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3;
|
||||
unsigned char *dst_alpha = image.GetAlpha() + x + r * width;
|
||||
for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) {
|
||||
*dst_pixels ++ = src.Red();
|
||||
*dst_pixels ++ = src.Green();
|
||||
*dst_pixels ++ = src.Blue();
|
||||
*dst_alpha ++ = src.Alpha();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (bmp->GetDepth() == 24) {
|
||||
wxNativePixelData data(*const_cast<wxBitmap*>(bmp));
|
||||
if (data) {
|
||||
for (int r = 0; r < bmp->GetHeight(); ++ r) {
|
||||
wxNativePixelData::Iterator src(data);
|
||||
src.Offset(data, 0, r);
|
||||
unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3;
|
||||
unsigned char *dst_alpha = image.GetAlpha() + x + r * width;
|
||||
for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) {
|
||||
*dst_pixels ++ = src.Red();
|
||||
*dst_pixels ++ = src.Green();
|
||||
*dst_pixels ++ = src.Blue();
|
||||
*dst_alpha ++ = wxALPHA_OPAQUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x += bmp->GetWidth();
|
||||
}
|
||||
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
|
||||
|
||||
#else
|
||||
|
||||
wxBitmap *bitmap = this->insert(bitmap_key, width, height);
|
||||
wxMemoryDC memDC;
|
||||
memDC.SelectObject(*bitmap);
|
||||
memDC.SetBackground(*wxTRANSPARENT_BRUSH);
|
||||
memDC.Clear();
|
||||
size_t x = 0;
|
||||
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
|
||||
if (bmp->GetWidth() > 0)
|
||||
memDC.DrawBitmap(*bmp, x, 0, true);
|
||||
x += bmp->GetWidth();
|
||||
}
|
||||
memDC.SelectObject(wxNullBitmap);
|
||||
return bitmap;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency)
|
||||
{
|
||||
wxImage image(width, height);
|
||||
image.InitAlpha();
|
||||
unsigned char* imgdata = image.GetData();
|
||||
unsigned char* imgalpha = image.GetAlpha();
|
||||
for (size_t i = 0; i < width * height; ++ i) {
|
||||
*imgdata ++ = r;
|
||||
*imgdata ++ = g;
|
||||
*imgdata ++ = b;
|
||||
*imgalpha ++ = transparency;
|
||||
}
|
||||
return wxImage_to_wxBitmap_with_alpha(std::move(image));
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
44
src/slic3r/GUI/BitmapCache.hpp
Normal file
44
src/slic3r/GUI/BitmapCache.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP
|
||||
#define SLIC3R_GUI_BITMAP_CACHE_HPP
|
||||
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Config.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class BitmapCache
|
||||
{
|
||||
public:
|
||||
BitmapCache() {}
|
||||
~BitmapCache() { clear(); }
|
||||
void clear();
|
||||
|
||||
wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; }
|
||||
const wxBitmap* find(const std::string &name) const { return const_cast<BitmapCache*>(this)->find(name); }
|
||||
|
||||
wxBitmap* insert(const std::string &name, size_t width, size_t height);
|
||||
wxBitmap* insert(const std::string &name, const wxBitmap &bmp);
|
||||
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2);
|
||||
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3);
|
||||
wxBitmap* insert(const std::string &name, const std::vector<wxBitmap> &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); }
|
||||
wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end);
|
||||
|
||||
static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency);
|
||||
static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }
|
||||
static wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); }
|
||||
|
||||
private:
|
||||
std::map<std::string, wxBitmap*> m_map;
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* SLIC3R_GUI_BITMAP_CACHE_HPP */
|
||||
200
src/slic3r/GUI/BonjourDialog.cpp
Normal file
200
src/slic3r/GUI/BonjourDialog.cpp
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
|
||||
|
||||
#include "BonjourDialog.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <mutex>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/listctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/timer.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/Utils/Bonjour.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
struct BonjourReplyEvent : public wxEvent
|
||||
{
|
||||
BonjourReply reply;
|
||||
|
||||
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
|
||||
wxEvent(winid, eventType),
|
||||
reply(std::move(reply))
|
||||
{}
|
||||
|
||||
virtual wxEvent *Clone() const
|
||||
{
|
||||
return new BonjourReplyEvent(*this);
|
||||
}
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
|
||||
|
||||
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||
|
||||
class ReplySet: public std::set<BonjourReply> {};
|
||||
|
||||
struct LifetimeGuard
|
||||
{
|
||||
std::mutex mutex;
|
||||
BonjourDialog *dialog;
|
||||
|
||||
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
|
||||
};
|
||||
|
||||
|
||||
BonjourDialog::BonjourDialog(wxWindow *parent) :
|
||||
wxDialog(parent, wxID_ANY, _(L("Network lookup"))),
|
||||
list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxSize(800, 300))),
|
||||
replies(new ReplySet),
|
||||
label(new wxStaticText(this, wxID_ANY, "")),
|
||||
timer(new wxTimer()),
|
||||
timer_state(0)
|
||||
{
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
list->SetSingleStyle(wxLC_SINGLE_SEL);
|
||||
list->SetSingleStyle(wxLC_SORT_DESCENDING);
|
||||
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 50);
|
||||
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 100);
|
||||
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 200);
|
||||
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 50);
|
||||
|
||||
vsizer->Add(list, 1, wxEXPAND | wxALL, 10);
|
||||
|
||||
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, 10);
|
||||
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10);
|
||||
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
|
||||
|
||||
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
|
||||
|
||||
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
|
||||
this->timer_state = 0;
|
||||
});
|
||||
|
||||
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
|
||||
}
|
||||
|
||||
BonjourDialog::~BonjourDialog()
|
||||
{
|
||||
// Needed bacuse of forward defs
|
||||
}
|
||||
|
||||
bool BonjourDialog::show_and_lookup()
|
||||
{
|
||||
Show(); // Because we need GetId() to work before ShowModal()
|
||||
|
||||
timer->Stop();
|
||||
timer->SetOwner(this);
|
||||
timer_state = 1;
|
||||
timer->Start(1000);
|
||||
wxTimerEvent evt_dummy;
|
||||
on_timer(evt_dummy);
|
||||
|
||||
// The background thread needs to queue messages for this dialog
|
||||
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
|
||||
// Here we put the pointer under a shared_ptr and protect it by a mutex,
|
||||
// so that both threads can access it safely.
|
||||
auto dguard = std::make_shared<LifetimeGuard>(this);
|
||||
|
||||
bonjour = std::move(Bonjour("octoprint")
|
||||
.set_retries(3)
|
||||
.set_timeout(4)
|
||||
.on_reply([dguard](BonjourReply &&reply) {
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
auto dialog = dguard->dialog;
|
||||
if (dialog != nullptr) {
|
||||
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
})
|
||||
.on_complete([dguard]() {
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
auto dialog = dguard->dialog;
|
||||
if (dialog != nullptr) {
|
||||
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
})
|
||||
.lookup()
|
||||
);
|
||||
|
||||
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
|
||||
{
|
||||
// Tell the background thread the dialog is going away...
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
dguard->dialog = nullptr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString BonjourDialog::get_selected() const
|
||||
{
|
||||
auto sel = list->GetFirstSelected();
|
||||
return sel >= 0 ? list->GetItemText(sel) : wxString();
|
||||
}
|
||||
|
||||
|
||||
// Private
|
||||
|
||||
void BonjourDialog::on_reply(BonjourReplyEvent &e)
|
||||
{
|
||||
if (replies->find(e.reply) != replies->end()) {
|
||||
// We already have this reply
|
||||
return;
|
||||
}
|
||||
|
||||
replies->insert(std::move(e.reply));
|
||||
|
||||
auto selected = get_selected();
|
||||
list->DeleteAllItems();
|
||||
|
||||
// The whole list is recreated so that we benefit from it already being sorted in the set.
|
||||
// (And also because wxListView's sorting API is bananas.)
|
||||
for (const auto &reply : *replies) {
|
||||
auto item = list->InsertItem(0, reply.full_address);
|
||||
list->SetItem(item, 1, reply.hostname);
|
||||
list->SetItem(item, 2, reply.service_name);
|
||||
list->SetItem(item, 3, reply.version);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
this->list->SetColumnWidth(i, wxLIST_AUTOSIZE);
|
||||
if (this->list->GetColumnWidth(i) < 100) { this->list->SetColumnWidth(i, 100); }
|
||||
}
|
||||
|
||||
if (!selected.IsEmpty()) {
|
||||
// Attempt to preserve selection
|
||||
auto hit = list->FindItem(-1, selected);
|
||||
if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); }
|
||||
}
|
||||
}
|
||||
|
||||
void BonjourDialog::on_timer(wxTimerEvent &)
|
||||
{
|
||||
const auto search_str = _(L("Searching for devices"));
|
||||
|
||||
if (timer_state > 0) {
|
||||
const std::string dots(timer_state, '.');
|
||||
label->SetLabel(wxString::Format("%s %s", search_str, dots));
|
||||
timer_state = (timer_state) % 3 + 1;
|
||||
} else {
|
||||
label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished"))+"."));
|
||||
timer->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
49
src/slic3r/GUI/BonjourDialog.hpp
Normal file
49
src/slic3r/GUI/BonjourDialog.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef slic3r_BonjourDialog_hpp_
|
||||
#define slic3r_BonjourDialog_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
class wxListView;
|
||||
class wxStaticText;
|
||||
class wxTimer;
|
||||
class wxTimerEvent;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Bonjour;
|
||||
class BonjourReplyEvent;
|
||||
class ReplySet;
|
||||
|
||||
|
||||
class BonjourDialog: public wxDialog
|
||||
{
|
||||
public:
|
||||
BonjourDialog(wxWindow *parent);
|
||||
BonjourDialog(BonjourDialog &&) = delete;
|
||||
BonjourDialog(const BonjourDialog &) = delete;
|
||||
BonjourDialog &operator=(BonjourDialog &&) = delete;
|
||||
BonjourDialog &operator=(const BonjourDialog &) = delete;
|
||||
~BonjourDialog();
|
||||
|
||||
bool show_and_lookup();
|
||||
wxString get_selected() const;
|
||||
private:
|
||||
wxListView *list;
|
||||
std::unique_ptr<ReplySet> replies;
|
||||
wxStaticText *label;
|
||||
std::shared_ptr<Bonjour> bonjour;
|
||||
std::unique_ptr<wxTimer> timer;
|
||||
unsigned timer_state;
|
||||
|
||||
void on_reply(BonjourReplyEvent &);
|
||||
void on_timer(wxTimerEvent &);
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
85
src/slic3r/GUI/ButtonsDescription.cpp
Normal file
85
src/slic3r/GUI/ButtonsDescription.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#include "ButtonsDescription.hpp"
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/clrpicker.h>
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.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(wxGetApp().get_label_clr_sys());
|
||||
auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().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(wxGetApp().get_label_clr_modified());
|
||||
auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().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&) {
|
||||
wxGetApp().set_label_clr_sys(sys_colour->GetColour());
|
||||
wxGetApp().set_label_clr_modified(mod_colour->GetColour());
|
||||
EndModal(wxID_OK);
|
||||
});
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
27
src/slic3r/GUI/ButtonsDescription.hpp
Normal file
27
src/slic3r/GUI/ButtonsDescription.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef slic3r_ButtonsDescription_hpp
|
||||
#define slic3r_ButtonsDescription_hpp
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
using t_icon_descriptions = std::vector<std::pair<wxBitmap*, std::string>>;
|
||||
|
||||
class ButtonsDescription : public wxDialog
|
||||
{
|
||||
t_icon_descriptions* m_icon_descriptions;
|
||||
public:
|
||||
ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions);
|
||||
~ButtonsDescription(){}
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
15
src/slic3r/GUI/ConfigExceptions.hpp
Normal file
15
src/slic3r/GUI/ConfigExceptions.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#include <exception>
|
||||
namespace Slic3r {
|
||||
|
||||
class ConfigError : public std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class ConfigGUITypeError : public ConfigError {
|
||||
using ConfigError::ConfigError;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
140
src/slic3r/GUI/ConfigSnapshotDialog.cpp
Normal file
140
src/slic3r/GUI/ConfigSnapshotDialog.cpp
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#include "ConfigSnapshotDialog.hpp"
|
||||
|
||||
#include "../Config/Snapshot.hpp"
|
||||
#include "../Utils/Time.hpp"
|
||||
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
static wxString format_reason(const Config::Snapshot::Reason reason)
|
||||
{
|
||||
switch (reason) {
|
||||
case Config::Snapshot::SNAPSHOT_UPGRADE:
|
||||
return wxString(_(L("Upgrade")));
|
||||
case Config::Snapshot::SNAPSHOT_DOWNGRADE:
|
||||
return wxString(_(L("Downgrade")));
|
||||
case Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
|
||||
return wxString(_(L("Before roll back")));
|
||||
case Config::Snapshot::SNAPSHOT_USER:
|
||||
return wxString(_(L("User")));
|
||||
case Config::Snapshot::SNAPSHOT_UNKNOWN:
|
||||
default:
|
||||
return wxString(_(L("Unknown")));
|
||||
}
|
||||
}
|
||||
|
||||
static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active)
|
||||
{
|
||||
// Start by declaring a row with an alternating background color.
|
||||
wxString text = "<tr bgcolor=\"";
|
||||
text += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5");
|
||||
text += "\">";
|
||||
text += "<td>";
|
||||
// Format the row header.
|
||||
text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active: ")) : "") +
|
||||
Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
|
||||
if (! snapshot.comment.empty())
|
||||
text += " (" + snapshot.comment + ")";
|
||||
text += "</b></font><br>";
|
||||
// End of row header.
|
||||
text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
|
||||
text += _(L("print")) + ": " + snapshot.print + "<br>";
|
||||
text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
|
||||
text += _(L("printer")) + ": " + snapshot.printer + "<br>";
|
||||
|
||||
bool compatible = true;
|
||||
for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) {
|
||||
text += _(L("vendor")) + ": " + vc.name +", " + _(L("version")) + ": " + vc.version.config_version.to_string() +
|
||||
", " + _(L("min slic3r version")) + ": " + vc.version.min_slic3r_version.to_string();
|
||||
if (vc.version.max_slic3r_version != Semver::inf())
|
||||
text += ", " + _(L("max slic3r version")) + ": " + vc.version.max_slic3r_version.to_string();
|
||||
text += "<br>";
|
||||
for (const std::pair<std::string, std::set<std::string>> &model : vc.models_variants_installed) {
|
||||
text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": ";
|
||||
for (const std::string &variant : model.second) {
|
||||
if (&variant != &*model.second.begin())
|
||||
text += ", ";
|
||||
text += variant;
|
||||
}
|
||||
text += "<br>";
|
||||
}
|
||||
if (! vc.version.is_current_slic3r_supported()) { compatible = false; }
|
||||
}
|
||||
|
||||
if (! compatible) {
|
||||
text += "<p align=\"right\">" + _(L("Incompatible with this Slic3r")) + "</p>";
|
||||
}
|
||||
else if (! snapshot_active)
|
||||
text += "<p align=\"right\"><a href=\"" + snapshot.id + "\">" + _(L("Activate")) + "</a></p>";
|
||||
text += "</td>";
|
||||
text += "</tr>";
|
||||
return text;
|
||||
}
|
||||
|
||||
static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot)
|
||||
{
|
||||
wxString text =
|
||||
"<html>"
|
||||
"<body bgcolor=\"#ffffff\" cellspacing=\"2\" cellpadding=\"0\" border=\"0\" link=\"#800000\">"
|
||||
"<font color=\"#000000\">";
|
||||
text += "<table style=\"width:100%\">";
|
||||
for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) {
|
||||
const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1];
|
||||
text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot);
|
||||
}
|
||||
text +=
|
||||
"</table>"
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
return text;
|
||||
}
|
||||
|
||||
ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot)
|
||||
: wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
|
||||
{
|
||||
this->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
this->SetSizer(vsizer);
|
||||
|
||||
// text
|
||||
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
|
||||
{
|
||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
#ifdef __WXMSW__
|
||||
int size[] = {8,8,8,8,11,11,11};
|
||||
#else
|
||||
int size[] = {11,11,11,11,14,14,14};
|
||||
#endif
|
||||
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
html->SetBorders(2);
|
||||
wxString text = generate_html_page(snapshot_db, on_snapshot);
|
||||
html->SetPage(text);
|
||||
vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0);
|
||||
html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this);
|
||||
}
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
this->Bind(wxEVT_BUTTON, &ConfigSnapshotDialog::onCloseDialog, this, wxID_CLOSE);
|
||||
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
}
|
||||
|
||||
void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||
{
|
||||
m_snapshot_to_activate = event.GetLinkInfo().GetHref();
|
||||
this->EndModal(wxID_CLOSE);
|
||||
this->Close();
|
||||
}
|
||||
|
||||
void ConfigSnapshotDialog::onCloseDialog(wxEvent &)
|
||||
{
|
||||
this->EndModal(wxID_CLOSE);
|
||||
this->Close();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
34
src/slic3r/GUI/ConfigSnapshotDialog.hpp
Normal file
34
src/slic3r/GUI/ConfigSnapshotDialog.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef slic3r_GUI_ConfigSnapshotDialog_hpp_
|
||||
#define slic3r_GUI_ConfigSnapshotDialog_hpp_
|
||||
|
||||
#include "GUI.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
namespace Config {
|
||||
class SnapshotDB;
|
||||
}
|
||||
|
||||
class ConfigSnapshotDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &id);
|
||||
const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; }
|
||||
|
||||
private:
|
||||
void onLinkClicked(wxHtmlLinkEvent &event);
|
||||
void onCloseDialog(wxEvent &);
|
||||
|
||||
// If set, it contains a snapshot ID to be restored after the dialog closes.
|
||||
std::string m_snapshot_to_activate;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_ConfigSnapshotDialog_hpp_ */
|
||||
915
src/slic3r/GUI/ConfigWizard.cpp
Normal file
915
src/slic3r/GUI/ConfigWizard.cpp
Normal file
|
|
@ -0,0 +1,915 @@
|
|||
#include "ConfigWizard_private.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/settings.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/statline.h>
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
// Printer model picker GUI control
|
||||
|
||||
struct PrinterPickerEvent : public wxEvent
|
||||
{
|
||||
std::string vendor_id;
|
||||
std::string model_id;
|
||||
std::string variant_name;
|
||||
bool enable;
|
||||
|
||||
PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) :
|
||||
wxEvent(winid, eventType),
|
||||
vendor_id(std::move(vendor_id)),
|
||||
model_id(std::move(model_id)),
|
||||
variant_name(std::move(variant_name)),
|
||||
enable(enable)
|
||||
{}
|
||||
|
||||
virtual wxEvent *Clone() const
|
||||
{
|
||||
return new PrinterPickerEvent(*this);
|
||||
}
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
|
||||
|
||||
PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors) :
|
||||
wxPanel(parent),
|
||||
vendor_id(vendor.id),
|
||||
variants_checked(0)
|
||||
{
|
||||
const auto &models = vendor.models;
|
||||
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20);
|
||||
printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
|
||||
sizer->Add(printer_grid);
|
||||
|
||||
auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
namefont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
|
||||
// wxGrid appends widgets by rows, but we need to construct them in columns.
|
||||
// These vectors are used to hold the elements so that they can be appended in the right order.
|
||||
std::vector<wxStaticText*> titles;
|
||||
std::vector<wxStaticBitmap*> bitmaps;
|
||||
std::vector<wxPanel*> variants_panels;
|
||||
|
||||
for (const auto &model : models) {
|
||||
auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model.id);
|
||||
wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG);
|
||||
|
||||
auto *title = new wxStaticText(this, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
title->SetFont(namefont);
|
||||
title->Wrap(std::max((int)MODEL_MIN_WRAP, bitmap.GetWidth()));
|
||||
titles.push_back(title);
|
||||
|
||||
auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
|
||||
bitmaps.push_back(bitmap_widget);
|
||||
|
||||
auto *variants_panel = new wxPanel(this);
|
||||
auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
variants_panel->SetSizer(variants_sizer);
|
||||
const auto model_id = model.id;
|
||||
|
||||
bool default_variant = true; // Mark the first variant as default in the GUI
|
||||
for (const auto &variant : model.variants) {
|
||||
const auto label = wxString::Format("%s %s %s %s", variant.name, _(L("mm")), _(L("nozzle")),
|
||||
(default_variant ? _(L("(default)")) : wxString()));
|
||||
default_variant = false;
|
||||
auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
|
||||
const size_t idx = cboxes.size();
|
||||
cboxes.push_back(cbox);
|
||||
bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant.name);
|
||||
variants_checked += enabled;
|
||||
cbox->SetValue(enabled);
|
||||
variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
|
||||
cbox->Bind(wxEVT_CHECKBOX, [this, idx](wxCommandEvent &event) {
|
||||
if (idx >= this->cboxes.size()) { return; }
|
||||
this->on_checkbox(this->cboxes[idx], event.IsChecked());
|
||||
});
|
||||
}
|
||||
|
||||
variants_panels.push_back(variants_panel);
|
||||
}
|
||||
|
||||
for (auto title : titles) { printer_grid->Add(title, 0, wxBOTTOM, 3); }
|
||||
for (auto bitmap : bitmaps) { printer_grid->Add(bitmap, 0, wxBOTTOM, 20); }
|
||||
for (auto vp : variants_panels) { printer_grid->Add(vp); }
|
||||
|
||||
auto *all_none_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *sel_all = new wxButton(this, wxID_ANY, _(L("Select all")));
|
||||
auto *sel_none = new wxButton(this, wxID_ANY, _(L("Select none")));
|
||||
sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true); });
|
||||
sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
|
||||
all_none_sizer->AddStretchSpacer();
|
||||
all_none_sizer->Add(sel_all);
|
||||
all_none_sizer->Add(sel_none);
|
||||
sizer->AddStretchSpacer();
|
||||
sizer->Add(all_none_sizer, 0, wxEXPAND);
|
||||
|
||||
SetSizer(sizer);
|
||||
}
|
||||
|
||||
void PrinterPicker::select_all(bool select)
|
||||
{
|
||||
for (const auto &cb : cboxes) {
|
||||
if (cb->GetValue() != select) {
|
||||
cb->SetValue(select);
|
||||
on_checkbox(cb, select);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrinterPicker::select_one(size_t i, bool select)
|
||||
{
|
||||
if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
|
||||
cboxes[i]->SetValue(select);
|
||||
on_checkbox(cboxes[i], select);
|
||||
}
|
||||
}
|
||||
|
||||
void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
|
||||
{
|
||||
variants_checked += checked ? 1 : -1;
|
||||
PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
|
||||
AddPendingEvent(evt);
|
||||
}
|
||||
|
||||
|
||||
// Wizard page base
|
||||
|
||||
ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname) :
|
||||
wxPanel(parent->p->hscroll),
|
||||
parent(parent),
|
||||
shortname(std::move(shortname)),
|
||||
p_prev(nullptr),
|
||||
p_next(nullptr)
|
||||
{
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
auto font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
font.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
font.SetPointSize(14);
|
||||
text->SetFont(font);
|
||||
sizer->Add(text, 0, wxALIGN_LEFT, 0);
|
||||
sizer->AddSpacer(10);
|
||||
|
||||
content = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(content, 1);
|
||||
|
||||
SetSizer(sizer);
|
||||
|
||||
this->Hide();
|
||||
|
||||
Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
|
||||
this->Layout();
|
||||
event.Skip();
|
||||
});
|
||||
}
|
||||
|
||||
ConfigWizardPage::~ConfigWizardPage() {}
|
||||
|
||||
ConfigWizardPage* ConfigWizardPage::chain(ConfigWizardPage *page)
|
||||
{
|
||||
if (p_next != nullptr) { p_next->p_prev = nullptr; }
|
||||
p_next = page;
|
||||
if (page != nullptr) {
|
||||
if (page->p_prev != nullptr) { page->p_prev->p_next = nullptr; }
|
||||
page->p_prev = this;
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
void ConfigWizardPage::append_text(wxString text)
|
||||
{
|
||||
auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
widget->Wrap(WRAP_WIDTH);
|
||||
widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
|
||||
append(widget);
|
||||
}
|
||||
|
||||
void ConfigWizardPage::append_spacer(int space)
|
||||
{
|
||||
content->AddSpacer(space);
|
||||
}
|
||||
|
||||
bool ConfigWizardPage::Show(bool show)
|
||||
{
|
||||
if (extra_buttons() != nullptr) { extra_buttons()->Show(show); }
|
||||
return wxPanel::Show(show);
|
||||
}
|
||||
|
||||
void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable); }
|
||||
|
||||
|
||||
// Wizard pages
|
||||
|
||||
PageWelcome::PageWelcome(ConfigWizard *parent, bool check_first_variant) :
|
||||
ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))),
|
||||
printer_picker(nullptr),
|
||||
others_buttons(new wxPanel(parent)),
|
||||
cbox_reset(nullptr)
|
||||
{
|
||||
if (wizard_p()->run_reason == ConfigWizard::RR_DATA_EMPTY) {
|
||||
wxString::Format(_(L("Run %s")), ConfigWizard::name());
|
||||
append_text(wxString::Format(
|
||||
_(L("Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")),
|
||||
ConfigWizard::name())
|
||||
);
|
||||
} else {
|
||||
cbox_reset = new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")));
|
||||
append(cbox_reset);
|
||||
}
|
||||
|
||||
const auto &vendors = wizard_p()->vendors;
|
||||
const auto vendor_prusa = vendors.find("PrusaResearch");
|
||||
|
||||
if (vendor_prusa != vendors.cend()) {
|
||||
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
|
||||
|
||||
printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors);
|
||||
if (check_first_variant) {
|
||||
// Select the default (first) model/variant on the Prusa vendor
|
||||
printer_picker->select_one(0, true);
|
||||
}
|
||||
printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
|
||||
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
|
||||
this->on_variant_checked();
|
||||
});
|
||||
|
||||
append(printer_picker);
|
||||
}
|
||||
|
||||
const size_t num_other_vendors = vendors.size() - (vendor_prusa != vendors.cend());
|
||||
auto *sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors")));
|
||||
other_vendors->Enable(num_other_vendors > 0);
|
||||
auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup")));
|
||||
|
||||
sizer->Add(other_vendors);
|
||||
sizer->AddSpacer(BTN_SPACING);
|
||||
sizer->Add(custom_setup);
|
||||
|
||||
other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); });
|
||||
custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); });
|
||||
|
||||
others_buttons->SetSizer(sizer);
|
||||
}
|
||||
|
||||
void PageWelcome::on_page_set()
|
||||
{
|
||||
chain(wizard_p()->page_update);
|
||||
on_variant_checked();
|
||||
}
|
||||
|
||||
void PageWelcome::on_variant_checked()
|
||||
{
|
||||
enable_next(printer_picker != nullptr ? printer_picker->variants_checked > 0 : false);
|
||||
}
|
||||
|
||||
PageUpdate::PageUpdate(ConfigWizard *parent) :
|
||||
ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))),
|
||||
version_check(true),
|
||||
preset_update(true)
|
||||
{
|
||||
const AppConfig *app_config = GUI::get_app_config();
|
||||
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
|
||||
auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for application updates")));
|
||||
box_slic3r->SetValue(app_config->get("version_check") == "1");
|
||||
append(box_slic3r);
|
||||
append_text(_(L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.")));
|
||||
|
||||
append_spacer(VERTICAL_SPACING);
|
||||
|
||||
auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically")));
|
||||
box_presets->SetValue(app_config->get("preset_update") == "1");
|
||||
append(box_presets);
|
||||
append_text(_(L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.")));
|
||||
const auto text_bold = _(L("Updates are never applied without user's consent and never overwrite user's customized settings."));
|
||||
auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
|
||||
label_bold->SetFont(boldfont);
|
||||
label_bold->Wrap(WRAP_WIDTH);
|
||||
append(label_bold);
|
||||
append_text(_(L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")));
|
||||
|
||||
box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
|
||||
box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
|
||||
}
|
||||
|
||||
PageVendors::PageVendors(ConfigWizard *parent) :
|
||||
ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors")))
|
||||
{
|
||||
append_text(_(L("Pick another vendor supported by Slic3r PE:")));
|
||||
|
||||
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
|
||||
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
|
||||
wxArrayString choices_vendors;
|
||||
|
||||
for (const auto vendor_pair : wizard_p()->vendors) {
|
||||
const auto &vendor = vendor_pair.second;
|
||||
if (vendor.id == "PrusaResearch") { continue; }
|
||||
|
||||
auto *picker = new PrinterPicker(this, vendor, appconfig_vendors);
|
||||
picker->Hide();
|
||||
pickers.push_back(picker);
|
||||
choices_vendors.Add(vendor.name);
|
||||
|
||||
picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
|
||||
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
|
||||
this->on_variant_checked();
|
||||
});
|
||||
}
|
||||
|
||||
auto *vendor_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices_vendors);
|
||||
if (choices_vendors.GetCount() > 0) {
|
||||
vendor_picker->SetSelection(0);
|
||||
on_vendor_pick(0);
|
||||
}
|
||||
|
||||
vendor_picker->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) {
|
||||
this->on_vendor_pick(evt.GetInt());
|
||||
});
|
||||
|
||||
append(vendor_picker);
|
||||
for (PrinterPicker *picker : pickers) { this->append(picker); }
|
||||
}
|
||||
|
||||
void PageVendors::on_page_set()
|
||||
{
|
||||
on_variant_checked();
|
||||
}
|
||||
|
||||
void PageVendors::on_vendor_pick(size_t i)
|
||||
{
|
||||
for (PrinterPicker *picker : pickers) { picker->Hide(); }
|
||||
if (i < pickers.size()) {
|
||||
pickers[i]->Show();
|
||||
wizard_p()->layout_fit();
|
||||
}
|
||||
}
|
||||
|
||||
void PageVendors::on_variant_checked()
|
||||
{
|
||||
size_t variants_checked = 0;
|
||||
for (const PrinterPicker *picker : pickers) { variants_checked += picker->variants_checked; }
|
||||
enable_next(variants_checked > 0);
|
||||
}
|
||||
|
||||
PageFirmware::PageFirmware(ConfigWizard *parent) :
|
||||
ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))),
|
||||
gcode_opt(*print_config_def.get("gcode_flavor")),
|
||||
gcode_picker(nullptr)
|
||||
{
|
||||
append_text(_(L("Choose the type of firmware used by your printer.")));
|
||||
append_text(gcode_opt.tooltip);
|
||||
|
||||
wxArrayString choices;
|
||||
choices.Alloc(gcode_opt.enum_labels.size());
|
||||
for (const auto &label : gcode_opt.enum_labels) {
|
||||
choices.Add(label);
|
||||
}
|
||||
|
||||
gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
|
||||
const auto &enum_values = gcode_opt.enum_values;
|
||||
auto needle = enum_values.cend();
|
||||
if (gcode_opt.default_value != nullptr) {
|
||||
needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
|
||||
}
|
||||
if (needle != enum_values.cend()) {
|
||||
gcode_picker->SetSelection(needle - enum_values.cbegin());
|
||||
} else {
|
||||
gcode_picker->SetSelection(0);
|
||||
}
|
||||
|
||||
append(gcode_picker);
|
||||
}
|
||||
|
||||
void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
|
||||
{
|
||||
auto sel = gcode_picker->GetSelection();
|
||||
if (sel >= 0 && sel < gcode_opt.enum_labels.size()) {
|
||||
auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
|
||||
config.set_key_value("gcode_flavor", opt);
|
||||
}
|
||||
}
|
||||
|
||||
PageBedShape::PageBedShape(ConfigWizard *parent) :
|
||||
ConfigWizardPage(parent, _(L("Bed Shape and Size")), _(L("Bed Shape"))),
|
||||
shape_panel(new BedShapePanel(this))
|
||||
{
|
||||
append_text(_(L("Set the shape of your printer's bed.")));
|
||||
|
||||
shape_panel->build_panel(wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape"));
|
||||
append(shape_panel);
|
||||
}
|
||||
|
||||
void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
|
||||
{
|
||||
const auto points(shape_panel->GetValue());
|
||||
auto *opt = new ConfigOptionPoints(points);
|
||||
config.set_key_value("bed_shape", opt);
|
||||
}
|
||||
|
||||
PageDiameters::PageDiameters(ConfigWizard *parent) :
|
||||
ConfigWizardPage(parent, _(L("Filament and Nozzle Diameters")), _(L("Print Diameters"))),
|
||||
spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)),
|
||||
spin_filam(new wxSpinCtrlDouble(this, wxID_ANY))
|
||||
{
|
||||
spin_nozzle->SetDigits(2);
|
||||
spin_nozzle->SetIncrement(0.1);
|
||||
const auto &def_nozzle = *print_config_def.get("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.get("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.get("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.get("bed_temperature");
|
||||
spin_bed->SetRange(def_bed.min, def_bed.max);
|
||||
auto *default_bed = dynamic_cast<const ConfigOptionInts*>(def_bed.default_value);
|
||||
spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
|
||||
|
||||
append_text(_(L("Enter the temperature needed for extruding your filament.")));
|
||||
append_text(_(L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")));
|
||||
|
||||
auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
|
||||
auto *text_extr = new wxStaticText(this, wxID_ANY, _(L("Extrusion Temperature:")));
|
||||
auto *unit_extr = new wxStaticText(this, wxID_ANY, _(L("°C")));
|
||||
sizer_extr->AddGrowableCol(0, 1);
|
||||
sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
sizer_extr->Add(spin_extr);
|
||||
sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
append(sizer_extr);
|
||||
|
||||
append_spacer(VERTICAL_SPACING);
|
||||
|
||||
append_text(_(L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")));
|
||||
append_text(_(L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")));
|
||||
|
||||
auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
|
||||
auto *text_bed = new wxStaticText(this, wxID_ANY, _(L("Bed Temperature:")));
|
||||
auto *unit_bed = new wxStaticText(this, wxID_ANY, _(L("°C")));
|
||||
sizer_bed->AddGrowableCol(0, 1);
|
||||
sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
sizer_bed->Add(spin_bed);
|
||||
sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
append(sizer_bed);
|
||||
}
|
||||
|
||||
void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
|
||||
{
|
||||
auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
|
||||
config.set_key_value("temperature", opt_extr);
|
||||
auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
|
||||
config.set_key_value("first_layer_temperature", opt_extr1st);
|
||||
auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
|
||||
config.set_key_value("bed_temperature", opt_bed);
|
||||
auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
|
||||
config.set_key_value("first_layer_bed_temperature", opt_bed1st);
|
||||
}
|
||||
|
||||
|
||||
// Index
|
||||
|
||||
ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) :
|
||||
wxPanel(parent),
|
||||
bg(GUI::from_u8(Slic3r::var("Slic3r_192px_transparent.png")), wxBITMAP_TYPE_PNG),
|
||||
bullet_black(GUI::from_u8(Slic3r::var("bullet_black.png")), wxBITMAP_TYPE_PNG),
|
||||
bullet_blue(GUI::from_u8(Slic3r::var("bullet_blue.png")), wxBITMAP_TYPE_PNG),
|
||||
bullet_white(GUI::from_u8(Slic3r::var("bullet_white.png")), wxBITMAP_TYPE_PNG)
|
||||
{
|
||||
SetMinSize(bg.GetSize());
|
||||
|
||||
wxClientDC dc(this);
|
||||
text_height = dc.GetCharHeight();
|
||||
|
||||
// Add logo bitmap.
|
||||
// This could be done in on_paint() along with the index labels, but I've found it tricky
|
||||
// to get the bitmap rendered well on all platforms with transparent background.
|
||||
// In some cases it didn't work at all. And so wxStaticBitmap is used here instead,
|
||||
// because it has all the platform quirks figured out.
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto *logo = new wxStaticBitmap(this, wxID_ANY, bg);
|
||||
sizer->AddStretchSpacer();
|
||||
sizer->Add(logo);
|
||||
SetSizer(sizer);
|
||||
|
||||
Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
|
||||
}
|
||||
|
||||
void ConfigWizardIndex::load_items(ConfigWizardPage *firstpage)
|
||||
{
|
||||
items.clear();
|
||||
item_active = items.cend();
|
||||
|
||||
for (auto *page = firstpage; page != nullptr; page = page->page_next()) {
|
||||
items.emplace_back(page->shortname);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ConfigWizardIndex::set_active(ConfigWizardPage *page)
|
||||
{
|
||||
item_active = std::find(items.cbegin(), items.cend(), page->shortname);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
|
||||
{
|
||||
enum {
|
||||
MARGIN = 10,
|
||||
SPACING = 5,
|
||||
};
|
||||
|
||||
const auto size = GetClientSize();
|
||||
if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
|
||||
|
||||
wxPaintDC dc(this);
|
||||
|
||||
const auto bullet_w = bullet_black.GetSize().GetWidth();
|
||||
const auto bullet_h = bullet_black.GetSize().GetHeight();
|
||||
const int yoff_icon = bullet_h < text_height ? (text_height - bullet_h) / 2 : 0;
|
||||
const int yoff_text = bullet_h > text_height ? (bullet_h - text_height) / 2 : 0;
|
||||
const int yinc = std::max(bullet_h, text_height) + SPACING;
|
||||
|
||||
unsigned y = 0;
|
||||
for (auto it = items.cbegin(); it != items.cend(); ++it) {
|
||||
if (it < item_active) { dc.DrawBitmap(bullet_black, MARGIN, y + yoff_icon, false); }
|
||||
if (it == item_active) { dc.DrawBitmap(bullet_blue, MARGIN, y + yoff_icon, false); }
|
||||
if (it > item_active) { dc.DrawBitmap(bullet_white, MARGIN, y + yoff_icon, false); }
|
||||
dc.DrawText(*it, MARGIN + bullet_w + SPACING, y + yoff_text);
|
||||
y += yinc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// priv
|
||||
|
||||
static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
|
||||
{ "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") },
|
||||
{ "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") },
|
||||
{ "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
|
||||
{ "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") },
|
||||
{ "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
|
||||
{ "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") },
|
||||
{ "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
|
||||
{ "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") },
|
||||
}};
|
||||
|
||||
void ConfigWizard::priv::load_vendors()
|
||||
{
|
||||
const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor";
|
||||
const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles";
|
||||
|
||||
// Load vendors from the "vendors" directory in datadir
|
||||
for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) {
|
||||
if (it->path().extension() == ".ini") {
|
||||
try {
|
||||
auto vp = VendorProfile::from_ini(it->path());
|
||||
vendors[vp.id] = std::move(vp);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Additionally load up vendors from the application resources directory, but only those not seen in the datadir
|
||||
for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) {
|
||||
if (it->path().extension() == ".ini") {
|
||||
const auto id = it->path().stem().string();
|
||||
if (vendors.find(id) == vendors.end()) {
|
||||
try {
|
||||
auto vp = VendorProfile::from_ini(it->path());
|
||||
vendors_rsrc[vp.id] = it->path().filename().string();
|
||||
vendors[vp.id] = std::move(vp);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load up the set of vendors / models / variants the user has had enabled up till now
|
||||
const AppConfig *app_config = GUI::get_app_config();
|
||||
if (! app_config->legacy_datadir()) {
|
||||
appconfig_vendors.set_vendors(*app_config);
|
||||
} else {
|
||||
// In case of legacy datadir, try to guess the preference based on the printer preset files that are present
|
||||
const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
|
||||
for (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) {
|
||||
auto needle = legacy_preset_map.find(it->path().filename().string());
|
||||
if (needle == legacy_preset_map.end()) { continue; }
|
||||
|
||||
const auto &model = needle->second.first;
|
||||
const auto &variant = needle->second.second;
|
||||
appconfig_vendors.set_variant("PrusaResearch", model, variant, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::index_refresh()
|
||||
{
|
||||
index->load_items(page_welcome);
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
|
||||
{
|
||||
hscroll_sizer->Add(page, 0, wxEXPAND);
|
||||
|
||||
auto *extra_buttons = page->extra_buttons();
|
||||
if (extra_buttons != nullptr) {
|
||||
btnsizer->Prepend(extra_buttons, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::set_page(ConfigWizardPage *page)
|
||||
{
|
||||
if (page == nullptr) { return; }
|
||||
if (page_current != nullptr) { page_current->Hide(); }
|
||||
page_current = page;
|
||||
enable_next(true);
|
||||
|
||||
page->on_page_set();
|
||||
index->load_items(page_welcome);
|
||||
index->set_active(page);
|
||||
page->Show();
|
||||
|
||||
btn_prev->Enable(page->page_prev() != nullptr);
|
||||
btn_next->Show(page->page_next() != nullptr);
|
||||
btn_finish->Show(page->page_next() == nullptr);
|
||||
|
||||
layout_fit();
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::layout_fit()
|
||||
{
|
||||
q->Layout();
|
||||
q->Fit();
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::enable_next(bool enable)
|
||||
{
|
||||
btn_next->Enable(enable);
|
||||
btn_finish->Enable(enable);
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::on_other_vendors()
|
||||
{
|
||||
page_welcome
|
||||
->chain(page_vendors)
|
||||
->chain(page_update);
|
||||
set_page(page_vendors);
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::on_custom_setup()
|
||||
{
|
||||
page_welcome->chain(page_firmware);
|
||||
page_temps->chain(page_update);
|
||||
set_page(page_firmware);
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
|
||||
{
|
||||
const bool is_custom_setup = page_welcome->page_next() == page_firmware;
|
||||
|
||||
if (! is_custom_setup) {
|
||||
const auto enabled_vendors = appconfig_vendors.vendors();
|
||||
|
||||
// Install bundles from resources if needed:
|
||||
std::vector<std::string> install_bundles;
|
||||
for (const auto &vendor_rsrc : vendors_rsrc) {
|
||||
const auto vendor = enabled_vendors.find(vendor_rsrc.first);
|
||||
if (vendor == enabled_vendors.end()) { continue; }
|
||||
|
||||
size_t size_sum = 0;
|
||||
for (const auto &model : vendor->second) { size_sum += model.second.size(); }
|
||||
if (size_sum == 0) { continue; }
|
||||
|
||||
// This vendor needs to be installed
|
||||
install_bundles.emplace_back(vendor_rsrc.second);
|
||||
}
|
||||
|
||||
// Decide whether to create snapshot based on run_reason and the reset profile checkbox
|
||||
bool snapshot = true;
|
||||
switch (run_reason) {
|
||||
case ConfigWizard::RR_DATA_EMPTY: snapshot = false; break;
|
||||
case ConfigWizard::RR_DATA_LEGACY: snapshot = true; break;
|
||||
case ConfigWizard::RR_DATA_INCOMPAT: snapshot = false; break; // In this case snapshot is done by PresetUpdater with the appropriate reason
|
||||
case ConfigWizard::RR_USER: snapshot = page_welcome->reset_user_profile(); break;
|
||||
}
|
||||
if (install_bundles.size() > 0) {
|
||||
// Install bundles from resources.
|
||||
updater->install_bundles_rsrc(std::move(install_bundles), snapshot);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
|
||||
}
|
||||
|
||||
if (page_welcome->reset_user_profile()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Resetting user profiles...";
|
||||
preset_bundle->reset(true);
|
||||
}
|
||||
|
||||
app_config->set_vendors(appconfig_vendors);
|
||||
app_config->set("version_check", page_update->version_check ? "1" : "0");
|
||||
app_config->set("preset_update", page_update->preset_update ? "1" : "0");
|
||||
app_config->reset_selections();
|
||||
preset_bundle->load_presets(*app_config);
|
||||
} else {
|
||||
for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) {
|
||||
page->apply_custom_config(*custom_config);
|
||||
}
|
||||
preset_bundle->load_config("My Settings", *custom_config);
|
||||
}
|
||||
// Update the selections from the compatibilty.
|
||||
preset_bundle->export_selections(*app_config);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) :
|
||||
wxDialog(parent, wxID_ANY, name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
p(new priv(this))
|
||||
{
|
||||
p->run_reason = reason;
|
||||
|
||||
p->load_vendors();
|
||||
p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
|
||||
"gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
|
||||
}));
|
||||
|
||||
p->index = new ConfigWizardIndex(this);
|
||||
|
||||
auto *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *hline = new wxStaticLine(this);
|
||||
p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling.
|
||||
// Later, we compare that to the size of the current screen and set minimum width based on that (see below).
|
||||
p->hscroll = new wxScrolledWindow(this);
|
||||
p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
p->hscroll->SetSizer(p->hscroll_sizer);
|
||||
|
||||
topsizer->Add(p->index, 0, wxEXPAND);
|
||||
topsizer->AddSpacer(INDEX_MARGIN);
|
||||
topsizer->Add(p->hscroll, 1, wxEXPAND);
|
||||
|
||||
p->btn_prev = new wxButton(this, wxID_NONE, _(L("< &Back")));
|
||||
p->btn_next = new wxButton(this, wxID_NONE, _(L("&Next >")));
|
||||
p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish")));
|
||||
p->btn_cancel = new wxButton(this, wxID_CANCEL);
|
||||
p->btnsizer->AddStretchSpacer();
|
||||
p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
|
||||
p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
|
||||
p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
|
||||
p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
|
||||
|
||||
p->add_page(p->page_welcome = new PageWelcome(this, reason == RR_DATA_EMPTY || reason == RR_DATA_LEGACY));
|
||||
p->add_page(p->page_update = new PageUpdate(this));
|
||||
p->add_page(p->page_vendors = new PageVendors(this));
|
||||
p->add_page(p->page_firmware = new PageFirmware(this));
|
||||
p->add_page(p->page_bed = new PageBedShape(this));
|
||||
p->add_page(p->page_diams = new PageDiameters(this));
|
||||
p->add_page(p->page_temps = new PageTemperatures(this));
|
||||
p->index_refresh();
|
||||
|
||||
p->page_welcome->chain(p->page_update);
|
||||
p->page_firmware
|
||||
->chain(p->page_bed)
|
||||
->chain(p->page_diams)
|
||||
->chain(p->page_temps);
|
||||
|
||||
vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
|
||||
vsizer->Add(hline, 0, wxEXPAND);
|
||||
vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
|
||||
|
||||
p->set_page(p->page_welcome);
|
||||
SetSizer(vsizer);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
// We can now enable scrolling on hscroll
|
||||
p->hscroll->SetScrollRate(30, 30);
|
||||
// Compare current ("ideal") wizard size with the size of the current screen.
|
||||
// If the screen is smaller, resize wizrad to match, which will enable scrollbars.
|
||||
auto wizard_size = GetSize();
|
||||
unsigned width, height;
|
||||
if (GUI::get_current_screen_size(this, width, height)) {
|
||||
wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN)));
|
||||
wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN)));
|
||||
SetMinSize(wizard_size);
|
||||
}
|
||||
Fit();
|
||||
|
||||
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });
|
||||
p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); });
|
||||
p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->EndModal(wxID_OK); });
|
||||
}
|
||||
|
||||
ConfigWizard::~ConfigWizard() {}
|
||||
|
||||
bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Running ConfigWizard, reason: " << p->run_reason;
|
||||
if (ShowModal() == wxID_OK) {
|
||||
auto *app_config = GUI::get_app_config();
|
||||
p->apply_config(app_config, preset_bundle, updater);
|
||||
app_config->set_legacy_datadir(false);
|
||||
BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
|
||||
return true;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const wxString& ConfigWizard::name()
|
||||
{
|
||||
// A different naming convention is used for the Wizard on Windows vs. OSX & GTK.
|
||||
#if WIN32
|
||||
static const wxString config_wizard_name = L("Configuration Wizard");
|
||||
#else
|
||||
static const wxString config_wizard_name = L("Configuration Assistant");
|
||||
#endif
|
||||
return config_wizard_name;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
50
src/slic3r/GUI/ConfigWizard.hpp
Normal file
50
src/slic3r/GUI/ConfigWizard.hpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef slic3r_ConfigWizard_hpp_
|
||||
#define slic3r_ConfigWizard_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PresetBundle;
|
||||
class PresetUpdater;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
class ConfigWizard: public wxDialog
|
||||
{
|
||||
public:
|
||||
// Why is the Wizard run
|
||||
enum RunReason {
|
||||
RR_DATA_EMPTY, // No or empty datadir
|
||||
RR_DATA_LEGACY, // Pre-updating datadir
|
||||
RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation
|
||||
RR_USER, // User requested the Wizard from the menus
|
||||
};
|
||||
|
||||
ConfigWizard(wxWindow *parent, RunReason run_reason);
|
||||
ConfigWizard(ConfigWizard &&) = delete;
|
||||
ConfigWizard(const ConfigWizard &) = delete;
|
||||
ConfigWizard &operator=(ConfigWizard &&) = delete;
|
||||
ConfigWizard &operator=(const ConfigWizard &) = delete;
|
||||
~ConfigWizard();
|
||||
|
||||
// Run the Wizard. Return whether it was completed.
|
||||
bool run(PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||
|
||||
static const wxString& name();
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
friend class ConfigWizardPage;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
241
src/slic3r/GUI/ConfigWizard_private.hpp
Normal file
241
src/slic3r/GUI/ConfigWizard_private.hpp
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
#ifndef slic3r_ConfigWizard_private_hpp_
|
||||
#define slic3r_ConfigWizard_private_hpp_
|
||||
|
||||
#include "ConfigWizard.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/spinctrl.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "Preset.hpp"
|
||||
#include "BedShapeDialog.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
enum {
|
||||
WRAP_WIDTH = 500,
|
||||
MODEL_MIN_WRAP = 150,
|
||||
|
||||
DIALOG_MARGIN = 15,
|
||||
INDEX_MARGIN = 40,
|
||||
BTN_SPACING = 10,
|
||||
INDENT_SPACING = 30,
|
||||
VERTICAL_SPACING = 10,
|
||||
};
|
||||
|
||||
struct PrinterPicker: wxPanel
|
||||
{
|
||||
struct Checkbox : wxCheckBox
|
||||
{
|
||||
Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
|
||||
wxCheckBox(parent, wxID_ANY, label),
|
||||
model(model),
|
||||
variant(variant)
|
||||
{}
|
||||
|
||||
std::string model;
|
||||
std::string variant;
|
||||
};
|
||||
|
||||
const std::string vendor_id;
|
||||
std::vector<Checkbox*> cboxes;
|
||||
unsigned variants_checked;
|
||||
|
||||
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors);
|
||||
|
||||
void select_all(bool select);
|
||||
void select_one(size_t i, bool select);
|
||||
void on_checkbox(const Checkbox *cbox, bool checked);
|
||||
};
|
||||
|
||||
struct ConfigWizardPage: wxPanel
|
||||
{
|
||||
ConfigWizard *parent;
|
||||
const wxString shortname;
|
||||
wxBoxSizer *content;
|
||||
|
||||
ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname);
|
||||
|
||||
virtual ~ConfigWizardPage();
|
||||
|
||||
ConfigWizardPage* page_prev() const { return p_prev; }
|
||||
ConfigWizardPage* page_next() const { return p_next; }
|
||||
ConfigWizardPage* chain(ConfigWizardPage *page);
|
||||
|
||||
template<class T>
|
||||
void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
|
||||
{
|
||||
content->Add(thing, proportion, flag, border);
|
||||
}
|
||||
|
||||
void append_text(wxString text);
|
||||
void append_spacer(int space);
|
||||
|
||||
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
|
||||
|
||||
virtual bool Show(bool show = true);
|
||||
virtual bool Hide() { return Show(false); }
|
||||
virtual wxPanel* extra_buttons() { return nullptr; }
|
||||
virtual void on_page_set() {}
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config) {}
|
||||
|
||||
void enable_next(bool enable);
|
||||
private:
|
||||
ConfigWizardPage *p_prev;
|
||||
ConfigWizardPage *p_next;
|
||||
};
|
||||
|
||||
struct PageWelcome: ConfigWizardPage
|
||||
{
|
||||
PrinterPicker *printer_picker;
|
||||
wxPanel *others_buttons;
|
||||
wxCheckBox *cbox_reset;
|
||||
|
||||
PageWelcome(ConfigWizard *parent, bool check_first_variant);
|
||||
|
||||
virtual wxPanel* extra_buttons() { return others_buttons; }
|
||||
virtual void on_page_set();
|
||||
|
||||
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
|
||||
void on_variant_checked();
|
||||
};
|
||||
|
||||
struct PageUpdate: ConfigWizardPage
|
||||
{
|
||||
bool version_check;
|
||||
bool preset_update;
|
||||
|
||||
PageUpdate(ConfigWizard *parent);
|
||||
};
|
||||
|
||||
struct PageVendors: ConfigWizardPage
|
||||
{
|
||||
std::vector<PrinterPicker*> pickers;
|
||||
|
||||
PageVendors(ConfigWizard *parent);
|
||||
|
||||
virtual void on_page_set();
|
||||
|
||||
void on_vendor_pick(size_t i);
|
||||
void on_variant_checked();
|
||||
};
|
||||
|
||||
struct PageFirmware: ConfigWizardPage
|
||||
{
|
||||
const ConfigOptionDef &gcode_opt;
|
||||
wxChoice *gcode_picker;
|
||||
|
||||
PageFirmware(ConfigWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct PageBedShape: ConfigWizardPage
|
||||
{
|
||||
BedShapePanel *shape_panel;
|
||||
|
||||
PageBedShape(ConfigWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct PageDiameters: ConfigWizardPage
|
||||
{
|
||||
wxSpinCtrlDouble *spin_nozzle;
|
||||
wxSpinCtrlDouble *spin_filam;
|
||||
|
||||
PageDiameters(ConfigWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct PageTemperatures: ConfigWizardPage
|
||||
{
|
||||
wxSpinCtrlDouble *spin_extr;
|
||||
wxSpinCtrlDouble *spin_bed;
|
||||
|
||||
PageTemperatures(ConfigWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
|
||||
class ConfigWizardIndex: public wxPanel
|
||||
{
|
||||
public:
|
||||
ConfigWizardIndex(wxWindow *parent);
|
||||
|
||||
void load_items(ConfigWizardPage *firstpage);
|
||||
void set_active(ConfigWizardPage *page);
|
||||
private:
|
||||
const wxBitmap bg;
|
||||
const wxBitmap bullet_black;
|
||||
const wxBitmap bullet_blue;
|
||||
const wxBitmap bullet_white;
|
||||
int text_height;
|
||||
|
||||
std::vector<wxString> items;
|
||||
std::vector<wxString>::const_iterator item_active;
|
||||
|
||||
void on_paint(wxPaintEvent &evt);
|
||||
};
|
||||
|
||||
struct ConfigWizard::priv
|
||||
{
|
||||
ConfigWizard *q;
|
||||
ConfigWizard::RunReason run_reason;
|
||||
AppConfig appconfig_vendors;
|
||||
std::unordered_map<std::string, VendorProfile> vendors;
|
||||
std::unordered_map<std::string, std::string> vendors_rsrc;
|
||||
std::unique_ptr<DynamicPrintConfig> custom_config;
|
||||
|
||||
wxScrolledWindow *hscroll = nullptr;
|
||||
wxBoxSizer *hscroll_sizer = nullptr;
|
||||
wxBoxSizer *btnsizer = nullptr;
|
||||
ConfigWizardPage *page_current = nullptr;
|
||||
ConfigWizardIndex *index = nullptr;
|
||||
wxButton *btn_prev = nullptr;
|
||||
wxButton *btn_next = nullptr;
|
||||
wxButton *btn_finish = nullptr;
|
||||
wxButton *btn_cancel = nullptr;
|
||||
|
||||
PageWelcome *page_welcome = nullptr;
|
||||
PageUpdate *page_update = nullptr;
|
||||
PageVendors *page_vendors = nullptr;
|
||||
PageFirmware *page_firmware = nullptr;
|
||||
PageBedShape *page_bed = nullptr;
|
||||
PageDiameters *page_diams = nullptr;
|
||||
PageTemperatures *page_temps = nullptr;
|
||||
|
||||
priv(ConfigWizard *q) : q(q) {}
|
||||
|
||||
void load_vendors();
|
||||
void add_page(ConfigWizardPage *page);
|
||||
void index_refresh();
|
||||
void set_page(ConfigWizardPage *page);
|
||||
void layout_fit();
|
||||
void go_prev() { if (page_current != nullptr) { set_page(page_current->page_prev()); } }
|
||||
void go_next() { if (page_current != nullptr) { set_page(page_current->page_next()); } }
|
||||
void enable_next(bool enable);
|
||||
|
||||
void on_other_vendors();
|
||||
void on_custom_setup();
|
||||
|
||||
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
66
src/slic3r/GUI/Event.hpp
Normal file
66
src/slic3r/GUI/Event.hpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef slic3r_Events_hpp_
|
||||
#define slic3r_Events_hpp_
|
||||
|
||||
#include <array>
|
||||
#include <wx/event.h>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
struct SimpleEvent : public wxEvent
|
||||
{
|
||||
SimpleEvent(wxEventType type, wxObject* origin = nullptr) : wxEvent(0, type)
|
||||
{
|
||||
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
|
||||
SetEventObject(origin);
|
||||
}
|
||||
|
||||
virtual wxEvent* Clone() const
|
||||
{
|
||||
return new SimpleEvent(GetEventType(), GetEventObject());
|
||||
}
|
||||
};
|
||||
|
||||
template<class T, size_t N> struct ArrayEvent : public wxEvent
|
||||
{
|
||||
std::array<T, N> data;
|
||||
|
||||
ArrayEvent(wxEventType type, std::array<T, N> data, wxObject* origin = nullptr)
|
||||
: wxEvent(0, type), data(std::move(data))
|
||||
{
|
||||
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
|
||||
SetEventObject(origin);
|
||||
}
|
||||
|
||||
virtual wxEvent* Clone() const
|
||||
{
|
||||
return new ArrayEvent<T, N>(GetEventType(), data, GetEventObject());
|
||||
}
|
||||
};
|
||||
template<class T> struct ArrayEvent<T, 1> : public wxEvent
|
||||
{
|
||||
T data;
|
||||
|
||||
ArrayEvent(wxEventType type, T data, wxObject* origin = nullptr)
|
||||
: wxEvent(0, type), data(std::move(data))
|
||||
{
|
||||
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
|
||||
SetEventObject(origin);
|
||||
}
|
||||
|
||||
virtual wxEvent* Clone() const
|
||||
{
|
||||
return new ArrayEvent<T, 1>(GetEventType(), data, GetEventObject());
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> using Event = ArrayEvent<T, 1>;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // slic3r_Events_hpp_
|
||||
791
src/slic3r/GUI/Field.cpp
Normal file
791
src/slic3r/GUI/Field.cpp
Normal file
|
|
@ -0,0 +1,791 @@
|
|||
#include "GUI.hpp"//"slic3r_gui.hpp"
|
||||
#include "Field.hpp"
|
||||
|
||||
//#include <wx/event.h>
|
||||
#include <regex>
|
||||
#include <wx/numformatter.h>
|
||||
#include <wx/tooltip.h>
|
||||
#include "PrintConfig.hpp"
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
wxString double_to_string(double const value)
|
||||
{
|
||||
if (value - int(value) == 0)
|
||||
return wxString::Format(_T("%i"), int(value));
|
||||
else {
|
||||
int precision = 4;
|
||||
for (size_t p = 1; p < 4; p++)
|
||||
{
|
||||
double cur_val = pow(10, p)*value;
|
||||
if (cur_val - int(cur_val) == 0) {
|
||||
precision = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
|
||||
}
|
||||
}
|
||||
|
||||
void Field::PostInitialize(){
|
||||
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
|
||||
m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
|
||||
if (wxMSW) {
|
||||
m_Undo_btn->SetBackgroundColour(color);
|
||||
m_Undo_to_sys_btn->SetBackgroundColour(color);
|
||||
}
|
||||
m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); }));
|
||||
m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); }));
|
||||
|
||||
//set default bitmap
|
||||
wxBitmap bmp;
|
||||
bmp.LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG);
|
||||
set_undo_bitmap(&bmp);
|
||||
set_undo_to_sys_bitmap(&bmp);
|
||||
|
||||
switch (m_opt.type)
|
||||
{
|
||||
case coPercents:
|
||||
case coFloats:
|
||||
case coStrings:
|
||||
case coBools:
|
||||
case coInts: {
|
||||
auto tag_pos = m_opt_id.find("#");
|
||||
if (tag_pos != std::string::npos)
|
||||
m_opt_idx = stoi(m_opt_id.substr(tag_pos + 1, m_opt_id.size()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BUILD();
|
||||
}
|
||||
|
||||
void Field::on_kill_focus(wxEvent& event) {
|
||||
// Without this, there will be nasty focus bugs on Windows.
|
||||
// Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
|
||||
// non-command events to allow the default handling to take place."
|
||||
event.Skip();
|
||||
// call the registered function if it is available
|
||||
if (m_on_kill_focus!=nullptr)
|
||||
m_on_kill_focus();
|
||||
}
|
||||
void Field::on_change_field()
|
||||
{
|
||||
// std::cerr << "calling Field::_on_change \n";
|
||||
if (m_on_change != nullptr && !m_disable_change_event)
|
||||
m_on_change(m_opt_id, get_value());
|
||||
}
|
||||
|
||||
void Field::on_back_to_initial_value(){
|
||||
if (m_back_to_initial_value != nullptr && m_is_modified_value)
|
||||
m_back_to_initial_value(m_opt_id);
|
||||
}
|
||||
|
||||
void Field::on_back_to_sys_value(){
|
||||
if (m_back_to_sys_value != nullptr && m_is_nonsys_value)
|
||||
m_back_to_sys_value(m_opt_id);
|
||||
}
|
||||
|
||||
wxString Field::get_tooltip_text(const wxString& default_string)
|
||||
{
|
||||
wxString tooltip_text("");
|
||||
wxString tooltip = _(m_opt.tooltip);
|
||||
if (tooltip.length() > 0)
|
||||
tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " +
|
||||
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string +
|
||||
(boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") +
|
||||
_(L("parameter name")) + "\t: " + m_opt_id;
|
||||
|
||||
return tooltip_text;
|
||||
}
|
||||
|
||||
bool Field::is_matched(const std::string& string, const std::string& pattern)
|
||||
{
|
||||
std::regex regex_pattern(pattern, std::regex_constants::icase); // use ::icase to make the matching case insensitive like /i in perl
|
||||
return std::regex_match(string, regex_pattern);
|
||||
}
|
||||
|
||||
void Field::get_value_by_opt_type(wxString& str)
|
||||
{
|
||||
switch (m_opt.type){
|
||||
case coInt:
|
||||
m_value = wxAtoi(str);
|
||||
break;
|
||||
case coPercent:
|
||||
case coPercents:
|
||||
case coFloats:
|
||||
case coFloat:{
|
||||
if (m_opt.type == coPercent && str.Last() == '%')
|
||||
str.RemoveLast();
|
||||
else if (str.Last() == '%') {
|
||||
wxString label = m_Label->GetLabel();
|
||||
if (label.Last() == '\n') label.RemoveLast();
|
||||
while (label.Last() == ' ') label.RemoveLast();
|
||||
if (label.Last() == ':') label.RemoveLast();
|
||||
show_error(m_parent, wxString::Format(_(L("%s doesn't support percentage")), label));
|
||||
set_value(double_to_string(m_opt.min), true);
|
||||
m_value = double(m_opt.min);
|
||||
break;
|
||||
}
|
||||
double val;
|
||||
if(!str.ToCDouble(&val))
|
||||
{
|
||||
show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits")));
|
||||
set_value(double_to_string(val), true);
|
||||
}
|
||||
if (m_opt.min > val || val > m_opt.max)
|
||||
{
|
||||
show_error(m_parent, _(L("Input value is out of range")));
|
||||
if (m_opt.min > val) val = m_opt.min;
|
||||
if (val > m_opt.max) val = m_opt.max;
|
||||
set_value(double_to_string(val), true);
|
||||
}
|
||||
m_value = val;
|
||||
break; }
|
||||
case coString:
|
||||
case coStrings:
|
||||
case coFloatOrPercent:
|
||||
m_value = str.ToStdString();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TextCtrl::BUILD() {
|
||||
auto size = wxSize(wxDefaultSize);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
|
||||
|
||||
wxString text_value = wxString("");
|
||||
|
||||
switch (m_opt.type) {
|
||||
case coFloatOrPercent:
|
||||
{
|
||||
text_value = double_to_string(m_opt.default_value->getFloat());
|
||||
if (static_cast<const ConfigOptionFloatOrPercent*>(m_opt.default_value)->percent)
|
||||
text_value += "%";
|
||||
break;
|
||||
}
|
||||
case coPercent:
|
||||
{
|
||||
text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat()));
|
||||
text_value += "%";
|
||||
break;
|
||||
}
|
||||
case coPercents:
|
||||
case coFloats:
|
||||
case coFloat:
|
||||
{
|
||||
double val = m_opt.type == coFloats ?
|
||||
static_cast<const ConfigOptionFloats*>(m_opt.default_value)->get_at(m_opt_idx) :
|
||||
m_opt.type == coFloat ?
|
||||
m_opt.default_value->getFloat() :
|
||||
static_cast<const ConfigOptionPercents*>(m_opt.default_value)->get_at(m_opt_idx);
|
||||
text_value = double_to_string(val);
|
||||
break;
|
||||
}
|
||||
case coString:
|
||||
text_value = static_cast<const ConfigOptionString*>(m_opt.default_value)->value;
|
||||
break;
|
||||
case coStrings:
|
||||
{
|
||||
const ConfigOptionStrings *vec = static_cast<const ConfigOptionStrings*>(m_opt.default_value);
|
||||
if (vec == nullptr || vec->empty()) break; //for the case of empty default value
|
||||
text_value = vec->get_at(m_opt_idx);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, (m_opt.multiline ? wxTE_MULTILINE : 0));
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(text_value));
|
||||
|
||||
temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event)
|
||||
{
|
||||
//! to allow the default handling
|
||||
event.Skip();
|
||||
//! eliminating the g-code pop up text description
|
||||
bool flag = false;
|
||||
#ifdef __WXGTK__
|
||||
// I have no idea why, but on GTK flag works in other way
|
||||
flag = true;
|
||||
#endif // __WXGTK__
|
||||
temp->GetToolTip()->Enable(flag);
|
||||
}), temp->GetId());
|
||||
|
||||
#if !defined(__WXGTK__)
|
||||
temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e)
|
||||
{
|
||||
e.Skip();// on_kill_focus(e);
|
||||
temp->GetToolTip()->Enable(true);
|
||||
}), temp->GetId());
|
||||
#endif // __WXGTK__
|
||||
|
||||
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt)
|
||||
{
|
||||
#ifdef __WXGTK__
|
||||
if (bChangedValueEvent)
|
||||
#endif //__WXGTK__
|
||||
on_change_field();
|
||||
}), temp->GetId());
|
||||
|
||||
#ifdef __WXGTK__
|
||||
// to correct value updating on GTK we should:
|
||||
// call on_change_field() on wxEVT_KEY_UP instead of wxEVT_TEXT
|
||||
// and prevent value updating on wxEVT_KEY_DOWN
|
||||
temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this);
|
||||
temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this);
|
||||
#endif //__WXGTK__
|
||||
|
||||
// select all text using Ctrl+A
|
||||
temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event)
|
||||
{
|
||||
if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
|
||||
temp->SetSelection(-1, -1); //select all
|
||||
event.Skip();
|
||||
}));
|
||||
|
||||
// recast as a wxWindow to fit the calling convention
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
}
|
||||
|
||||
boost::any& TextCtrl::get_value()
|
||||
{
|
||||
wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
|
||||
get_value_by_opt_type(ret_str);
|
||||
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void TextCtrl::enable() { dynamic_cast<wxTextCtrl*>(window)->Enable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(true); }
|
||||
void TextCtrl::disable() { dynamic_cast<wxTextCtrl*>(window)->Disable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(false); }
|
||||
|
||||
#ifdef __WXGTK__
|
||||
void TextCtrl::change_field_value(wxEvent& event)
|
||||
{
|
||||
if (bChangedValueEvent = event.GetEventType()==wxEVT_KEY_UP)
|
||||
on_change_field();
|
||||
event.Skip();
|
||||
};
|
||||
#endif //__WXGTK__
|
||||
|
||||
void CheckBox::BUILD() {
|
||||
auto size = wxSize(wxDefaultSize);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
|
||||
|
||||
bool check_value = m_opt.type == coBool ?
|
||||
m_opt.default_value->getBool() : m_opt.type == coBools ?
|
||||
static_cast<const ConfigOptionBools*>(m_opt.default_value)->get_at(m_opt_idx) :
|
||||
false;
|
||||
|
||||
auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
|
||||
temp->SetValue(check_value);
|
||||
if (m_opt.readonly) temp->Disable();
|
||||
|
||||
temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(check_value ? "true" : "false"));
|
||||
|
||||
// recast as a wxWindow to fit the calling convention
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
}
|
||||
|
||||
boost::any& CheckBox::get_value()
|
||||
{
|
||||
// boost::any m_value;
|
||||
bool value = dynamic_cast<wxCheckBox*>(window)->GetValue();
|
||||
if (m_opt.type == coBool)
|
||||
m_value = static_cast<bool>(value);
|
||||
else
|
||||
m_value = static_cast<unsigned char>(value);
|
||||
return m_value;
|
||||
}
|
||||
|
||||
int undef_spin_val = -9999; //! Probably, It's not necessary
|
||||
|
||||
void SpinCtrl::BUILD() {
|
||||
auto size = wxSize(wxDefaultSize);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
|
||||
|
||||
wxString text_value = wxString("");
|
||||
int default_value = 0;
|
||||
|
||||
switch (m_opt.type) {
|
||||
case coInt:
|
||||
default_value = m_opt.default_value->getInt();
|
||||
text_value = wxString::Format(_T("%i"), default_value);
|
||||
break;
|
||||
case coInts:
|
||||
{
|
||||
const ConfigOptionInts *vec = static_cast<const ConfigOptionInts*>(m_opt.default_value);
|
||||
if (vec == nullptr || vec->empty()) break;
|
||||
for (size_t id = 0; id < vec->size(); ++id)
|
||||
{
|
||||
default_value = vec->get_at(id);
|
||||
text_value += wxString::Format(_T("%i"), default_value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const int min_val = m_opt.min == INT_MIN ? 0: m_opt.min;
|
||||
const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647;
|
||||
|
||||
auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size,
|
||||
0, min_val, max_val, default_value);
|
||||
|
||||
// temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId());
|
||||
// temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId());
|
||||
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e)
|
||||
{
|
||||
// # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
|
||||
// # when it was changed from the text control, so the on_change callback
|
||||
// # gets the old one, and on_kill_focus resets the control to the old value.
|
||||
// # As a workaround, we get the new value from $event->GetString and store
|
||||
// # here temporarily so that we can return it from $self->get_value
|
||||
std::string value = e.GetString().utf8_str().data();
|
||||
if (is_matched(value, "^\\d+$"))
|
||||
tmp_value = std::stoi(value);
|
||||
on_change_field();
|
||||
// # We don't reset tmp_value here because _on_change might put callbacks
|
||||
// # in the CallAfter queue, and we want the tmp value to be available from
|
||||
// # them as well.
|
||||
}), temp->GetId());
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(text_value));
|
||||
|
||||
// recast as a wxWindow to fit the calling convention
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
}
|
||||
|
||||
void Choice::BUILD() {
|
||||
auto size = wxSize(wxDefaultSize);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
|
||||
|
||||
wxComboBox* temp;
|
||||
if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0)
|
||||
temp = new wxComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
|
||||
else
|
||||
temp = new wxComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, NULL, wxCB_READONLY);
|
||||
|
||||
// recast as a wxWindow to fit the calling convention
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
||||
if (m_opt.enum_labels.empty() && m_opt.enum_values.empty()){
|
||||
}
|
||||
else{
|
||||
for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels){
|
||||
const wxString& str = _(el);//m_opt_id == "support" ? _(el) : el;
|
||||
temp->Append(str);
|
||||
}
|
||||
set_selection();
|
||||
}
|
||||
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
|
||||
temp->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
|
||||
}
|
||||
|
||||
void Choice::set_selection()
|
||||
{
|
||||
wxString text_value = wxString("");
|
||||
switch (m_opt.type){
|
||||
case coFloat:
|
||||
case coPercent: {
|
||||
double val = m_opt.default_value->getFloat();
|
||||
text_value = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 1);
|
||||
size_t idx = 0;
|
||||
for (auto el : m_opt.enum_values)
|
||||
{
|
||||
if (el.compare(text_value) == 0)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
// if (m_opt.type == coPercent) text_value += "%";
|
||||
idx == m_opt.enum_values.size() ?
|
||||
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
|
||||
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
|
||||
break;
|
||||
}
|
||||
case coEnum:{
|
||||
int id_value = static_cast<const ConfigOptionEnum<SeamPosition>*>(m_opt.default_value)->value; //!!
|
||||
dynamic_cast<wxComboBox*>(window)->SetSelection(id_value);
|
||||
break;
|
||||
}
|
||||
case coInt:{
|
||||
int val = m_opt.default_value->getInt(); //!!
|
||||
text_value = wxString::Format(_T("%i"), int(val));
|
||||
size_t idx = 0;
|
||||
for (auto el : m_opt.enum_values)
|
||||
{
|
||||
if (el.compare(text_value) == 0)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
idx == m_opt.enum_values.size() ?
|
||||
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
|
||||
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
|
||||
break;
|
||||
}
|
||||
case coStrings:{
|
||||
text_value = static_cast<const ConfigOptionStrings*>(m_opt.default_value)->get_at(m_opt_idx);
|
||||
|
||||
size_t idx = 0;
|
||||
for (auto el : m_opt.enum_values)
|
||||
{
|
||||
if (el.compare(text_value) == 0)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
idx == m_opt.enum_values.size() ?
|
||||
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
|
||||
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Choice::set_value(const std::string& value, bool change_event) //! Redundant?
|
||||
{
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
size_t idx=0;
|
||||
for (auto el : m_opt.enum_values)
|
||||
{
|
||||
if (el.compare(value) == 0)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
|
||||
idx == m_opt.enum_values.size() ?
|
||||
dynamic_cast<wxComboBox*>(window)->SetValue(value) :
|
||||
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
|
||||
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
void Choice::set_value(const boost::any& value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
switch (m_opt.type){
|
||||
case coInt:
|
||||
case coFloat:
|
||||
case coPercent:
|
||||
case coString:
|
||||
case coStrings:{
|
||||
wxString text_value;
|
||||
if (m_opt.type == coInt)
|
||||
text_value = wxString::Format(_T("%i"), int(boost::any_cast<int>(value)));
|
||||
else
|
||||
text_value = boost::any_cast<wxString>(value);
|
||||
auto idx = 0;
|
||||
for (auto el : m_opt.enum_values)
|
||||
{
|
||||
if (el.compare(text_value) == 0)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
idx == m_opt.enum_values.size() ?
|
||||
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
|
||||
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
|
||||
break;
|
||||
}
|
||||
case coEnum:{
|
||||
int val = boost::any_cast<int>(value);
|
||||
if (m_opt_id.compare("external_fill_pattern") == 0)
|
||||
{
|
||||
if (!m_opt.enum_values.empty()){
|
||||
std::string key;
|
||||
t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
|
||||
for (auto it : map_names) {
|
||||
if (val == it.second) {
|
||||
key = it.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
for (auto el : m_opt.enum_values)
|
||||
{
|
||||
if (el.compare(key) == 0)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
|
||||
val = idx == m_opt.enum_values.size() ? 0 : idx;
|
||||
}
|
||||
else
|
||||
val = 0;
|
||||
}
|
||||
dynamic_cast<wxComboBox*>(window)->SetSelection(val);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
//! it's needed for _update_serial_ports()
|
||||
void Choice::set_values(const std::vector<std::string>& values)
|
||||
{
|
||||
if (values.empty())
|
||||
return;
|
||||
m_disable_change_event = true;
|
||||
|
||||
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
|
||||
// # but we want to preserve it
|
||||
auto ww = dynamic_cast<wxComboBox*>(window);
|
||||
auto value = ww->GetValue();
|
||||
ww->Clear();
|
||||
ww->Append("");
|
||||
for (auto el : values)
|
||||
ww->Append(wxString(el));
|
||||
ww->SetValue(value);
|
||||
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
boost::any& Choice::get_value()
|
||||
{
|
||||
// boost::any m_value;
|
||||
wxString ret_str = static_cast<wxComboBox*>(window)->GetValue();
|
||||
|
||||
// options from right panel
|
||||
std::vector <std::string> right_panel_options{ "support", "scale_unit" };
|
||||
for (auto rp_option: right_panel_options)
|
||||
if (m_opt_id == rp_option)
|
||||
return m_value = boost::any(ret_str);
|
||||
|
||||
if (m_opt.type != coEnum)
|
||||
/*m_value = */get_value_by_opt_type(ret_str);
|
||||
else
|
||||
{
|
||||
int ret_enum = static_cast<wxComboBox*>(window)->GetSelection();
|
||||
if (m_opt_id.compare("external_fill_pattern") == 0)
|
||||
{
|
||||
if (!m_opt.enum_values.empty()){
|
||||
std::string key = m_opt.enum_values[ret_enum];
|
||||
t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
|
||||
int value = map_names.at(key);
|
||||
|
||||
m_value = static_cast<InfillPattern>(value);
|
||||
}
|
||||
else
|
||||
m_value = static_cast<InfillPattern>(0);
|
||||
}
|
||||
if (m_opt_id.compare("fill_pattern") == 0)
|
||||
m_value = static_cast<InfillPattern>(ret_enum);
|
||||
else if (m_opt_id.compare("gcode_flavor") == 0)
|
||||
m_value = static_cast<GCodeFlavor>(ret_enum);
|
||||
else if (m_opt_id.compare("support_material_pattern") == 0)
|
||||
m_value = static_cast<SupportMaterialPattern>(ret_enum);
|
||||
else if (m_opt_id.compare("seam_position") == 0)
|
||||
m_value = static_cast<SeamPosition>(ret_enum);
|
||||
else if (m_opt_id.compare("host_type") == 0)
|
||||
m_value = static_cast<PrintHostType>(ret_enum);
|
||||
}
|
||||
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void ColourPicker::BUILD()
|
||||
{
|
||||
auto size = wxSize(wxDefaultSize);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
|
||||
|
||||
// Validate the color
|
||||
wxString clr_str(static_cast<const ConfigOptionStrings*>(m_opt.default_value)->get_at(m_opt_idx));
|
||||
wxColour clr(clr_str);
|
||||
if (! clr.IsOk()) {
|
||||
clr = wxTransparentColour;
|
||||
}
|
||||
|
||||
auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
|
||||
|
||||
// // recast as a wxWindow to fit the calling convention
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
||||
temp->Bind(wxEVT_COLOURPICKER_CHANGED, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(clr_str));
|
||||
}
|
||||
|
||||
boost::any& ColourPicker::get_value(){
|
||||
// boost::any m_value;
|
||||
|
||||
auto colour = static_cast<wxColourPickerCtrl*>(window)->GetColour();
|
||||
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue());
|
||||
m_value = clr_str.ToStdString();
|
||||
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void PointCtrl::BUILD()
|
||||
{
|
||||
auto size = wxSize(wxDefaultSize);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
|
||||
|
||||
auto temp = new wxBoxSizer(wxHORIZONTAL);
|
||||
// $self->wxSizer($sizer);
|
||||
//
|
||||
wxSize field_size(40, -1);
|
||||
|
||||
auto default_pt = static_cast<const ConfigOptionPoints*>(m_opt.default_value)->values.at(0);
|
||||
double val = default_pt(0);
|
||||
wxString X = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
|
||||
val = default_pt(1);
|
||||
wxString Y = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
|
||||
|
||||
x_textctrl = new wxTextCtrl(m_parent, wxID_ANY, X, wxDefaultPosition, field_size);
|
||||
y_textctrl = new wxTextCtrl(m_parent, wxID_ANY, Y, wxDefaultPosition, field_size);
|
||||
|
||||
temp->Add(new wxStaticText(m_parent, wxID_ANY, "x : "), 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
temp->Add(x_textctrl);
|
||||
temp->Add(new wxStaticText(m_parent, wxID_ANY, " y : "), 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
temp->Add(y_textctrl);
|
||||
|
||||
x_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), x_textctrl->GetId());
|
||||
y_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), y_textctrl->GetId());
|
||||
|
||||
// // recast as a wxWindow to fit the calling convention
|
||||
sizer = dynamic_cast<wxSizer*>(temp);
|
||||
|
||||
x_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
|
||||
y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
|
||||
}
|
||||
|
||||
void PointCtrl::set_value(const Vec2d& value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
double val = value(0);
|
||||
x_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
|
||||
val = value(1);
|
||||
y_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
|
||||
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
void PointCtrl::set_value(const boost::any& value, bool change_event)
|
||||
{
|
||||
Vec2d pt(Vec2d::Zero());
|
||||
const Vec2d *ptf = boost::any_cast<Vec2d>(&value);
|
||||
if (!ptf)
|
||||
{
|
||||
ConfigOptionPoints* pts = boost::any_cast<ConfigOptionPoints*>(value);
|
||||
pt = pts->values.at(0);
|
||||
}
|
||||
else
|
||||
pt = *ptf;
|
||||
set_value(pt, change_event);
|
||||
}
|
||||
|
||||
boost::any& PointCtrl::get_value()
|
||||
{
|
||||
double x, y;
|
||||
x_textctrl->GetValue().ToDouble(&x);
|
||||
y_textctrl->GetValue().ToDouble(&y);
|
||||
return m_value = Vec2d(x, y);
|
||||
}
|
||||
|
||||
void StaticText::BUILD()
|
||||
{
|
||||
auto size = wxSize(wxDefaultSize);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
|
||||
|
||||
wxString legend(static_cast<const ConfigOptionString*>(m_opt.default_value)->value);
|
||||
auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size);
|
||||
temp->SetFont(wxGetApp().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
|
||||
|
||||
|
||||
469
src/slic3r/GUI/Field.hpp
Normal file
469
src/slic3r/GUI/Field.hpp
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
#ifndef SLIC3R_GUI_FIELD_HPP
|
||||
#define SLIC3R_GUI_FIELD_HPP
|
||||
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/clrpicker.h>
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Config.hpp"
|
||||
|
||||
//#include "slic3r_gui.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#define wxMSW true
|
||||
#else
|
||||
#define wxMSW false
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Field;
|
||||
using t_field = std::unique_ptr<Field>;
|
||||
using t_kill_focus = std::function<void()>;
|
||||
using t_change = std::function<void(t_config_option_key, const boost::any&)>;
|
||||
using t_back_to_init = std::function<void(const std::string&)>;
|
||||
|
||||
wxString double_to_string(double const value);
|
||||
|
||||
class MyButton : public wxButton
|
||||
{
|
||||
bool hidden = false; // never show button if it's hidden ones
|
||||
public:
|
||||
MyButton() {}
|
||||
MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize, long style = 0,
|
||||
const wxValidator& validator = wxDefaultValidator,
|
||||
const wxString& name = wxTextCtrlNameStr)
|
||||
{
|
||||
this->Create(parent, id, label, pos, size, style, validator, name);
|
||||
}
|
||||
|
||||
// overridden from wxWindow base class
|
||||
virtual bool
|
||||
AcceptsFocusFromKeyboard() const { return false; }
|
||||
|
||||
void set_as_hidden() {
|
||||
Hide();
|
||||
hidden = true;
|
||||
}
|
||||
|
||||
virtual bool Show(bool show = true) override {
|
||||
return wxButton::Show(hidden ? false : show);
|
||||
}
|
||||
};
|
||||
|
||||
class Field {
|
||||
protected:
|
||||
// factory function to defer and enforce creation of derived type.
|
||||
virtual void PostInitialize();
|
||||
|
||||
/// Finish constructing the Field's wxWidget-related properties, including setting its own sizer, etc.
|
||||
virtual void BUILD() = 0;
|
||||
|
||||
/// Call the attached on_kill_focus method.
|
||||
//! It's important to use wxEvent instead of wxFocusEvent,
|
||||
//! in another case we can't unfocused control at all
|
||||
void on_kill_focus(wxEvent& event);
|
||||
/// Call the attached on_change method.
|
||||
void on_change_field();
|
||||
/// Call the attached m_back_to_initial_value method.
|
||||
void on_back_to_initial_value();
|
||||
/// Call the attached m_back_to_sys_value method.
|
||||
void on_back_to_sys_value();
|
||||
|
||||
public:
|
||||
/// parent wx item, opportunity to refactor (probably not necessary - data duplication)
|
||||
wxWindow* m_parent {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_kill_focus m_on_kill_focus {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_change m_on_change {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_back_to_init m_back_to_initial_value{ nullptr };
|
||||
t_back_to_init m_back_to_sys_value{ nullptr };
|
||||
|
||||
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
|
||||
bool m_disable_change_event {false};
|
||||
bool m_is_modified_value {false};
|
||||
bool m_is_nonsys_value {true};
|
||||
|
||||
/// Copy of ConfigOption for deduction purposes
|
||||
const ConfigOptionDef m_opt {ConfigOptionDef()};
|
||||
const t_config_option_key m_opt_id;//! {""};
|
||||
int m_opt_idx = 0;
|
||||
|
||||
/// Sets a value for this control.
|
||||
/// subclasses should overload with a specific version
|
||||
/// Postcondition: Method does not fire the on_change event.
|
||||
virtual void set_value(const boost::any& value, bool change_event) = 0;
|
||||
|
||||
/// Gets a boost::any representing this control.
|
||||
/// subclasses should overload with a specific version
|
||||
virtual boost::any& get_value() = 0;
|
||||
|
||||
virtual void enable() = 0;
|
||||
virtual void disable() = 0;
|
||||
|
||||
/// Fires the enable or disable function, based on the input.
|
||||
inline void toggle(bool en) { en ? enable() : disable(); }
|
||||
|
||||
virtual wxString get_tooltip_text(const wxString& default_string);
|
||||
|
||||
// set icon to "UndoToSystemValue" button according to an inheritance of preset
|
||||
// void set_nonsys_btn_icon(const wxBitmap& icon);
|
||||
|
||||
Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {};
|
||||
Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {};
|
||||
|
||||
/// If you don't know what you are getting back, check both methods for nullptr.
|
||||
virtual wxSizer* getSizer() { return nullptr; }
|
||||
virtual wxWindow* getWindow() { return nullptr; }
|
||||
|
||||
bool is_matched(const std::string& string, const std::string& pattern);
|
||||
void get_value_by_opt_type(wxString& str);
|
||||
|
||||
/// Factory method for generating new derived classes.
|
||||
template<class T>
|
||||
static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) // interface for creating shared objects
|
||||
{
|
||||
auto p = Slic3r::make_unique<T>(parent, opt, id);
|
||||
p->PostInitialize();
|
||||
return std::move(p); //!p;
|
||||
}
|
||||
|
||||
bool set_undo_bitmap(const wxBitmap *bmp) {
|
||||
if (m_undo_bitmap != bmp) {
|
||||
m_undo_bitmap = bmp;
|
||||
m_Undo_btn->SetBitmap(*bmp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_undo_to_sys_bitmap(const wxBitmap *bmp) {
|
||||
if (m_undo_to_sys_bitmap != bmp) {
|
||||
m_undo_to_sys_bitmap = bmp;
|
||||
m_Undo_to_sys_btn->SetBitmap(*bmp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_label_colour(const wxColour *clr) {
|
||||
if (m_Label == nullptr) return false;
|
||||
if (m_label_color != clr) {
|
||||
m_label_color = clr;
|
||||
m_Label->SetForegroundColour(*clr);
|
||||
m_Label->Refresh(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_label_colour_force(const wxColour *clr) {
|
||||
if (m_Label == nullptr) return false;
|
||||
m_Label->SetForegroundColour(*clr);
|
||||
m_Label->Refresh(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_undo_tooltip(const wxString *tip) {
|
||||
if (m_undo_tooltip != tip) {
|
||||
m_undo_tooltip = tip;
|
||||
m_Undo_btn->SetToolTip(*tip);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_undo_to_sys_tooltip(const wxString *tip) {
|
||||
if (m_undo_to_sys_tooltip != tip) {
|
||||
m_undo_to_sys_tooltip = tip;
|
||||
m_Undo_to_sys_btn->SetToolTip(*tip);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_side_text_ptr(wxStaticText* side_text) {
|
||||
m_side_text = side_text;
|
||||
}
|
||||
|
||||
protected:
|
||||
MyButton* m_Undo_btn = nullptr;
|
||||
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
|
||||
const wxBitmap* m_undo_bitmap = nullptr;
|
||||
const wxString* m_undo_tooltip = nullptr;
|
||||
MyButton* m_Undo_to_sys_btn = nullptr;
|
||||
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
|
||||
const wxBitmap* m_undo_to_sys_bitmap = nullptr;
|
||||
const wxString* m_undo_to_sys_tooltip = nullptr;
|
||||
|
||||
wxStaticText* m_Label = nullptr;
|
||||
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
|
||||
const wxColour* m_label_color = nullptr;
|
||||
|
||||
wxStaticText* m_side_text = nullptr;
|
||||
|
||||
// current value
|
||||
boost::any m_value;
|
||||
|
||||
friend class OptionsGroup;
|
||||
};
|
||||
|
||||
/// Convenience function, accepts a const reference to t_field and checks to see whether
|
||||
/// or not both wx pointers are null.
|
||||
inline bool is_bad_field(const t_field& obj) { return obj->getSizer() == nullptr && obj->getWindow() == nullptr; }
|
||||
|
||||
/// Covenience function to determine whether this field is a valid window field.
|
||||
inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr && obj->getSizer() == nullptr; }
|
||||
|
||||
/// Covenience function to determine whether this field is a valid sizer field.
|
||||
inline bool is_sizer_field(const t_field& obj) { return !is_bad_field(obj) && obj->getSizer() != nullptr; }
|
||||
|
||||
class TextCtrl : public Field {
|
||||
using Field::Field;
|
||||
#ifdef __WXGTK__
|
||||
bool bChangedValueEvent = true;
|
||||
void change_field_value(wxEvent& event);
|
||||
#endif //__WXGTK__
|
||||
public:
|
||||
TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~TextCtrl() {}
|
||||
|
||||
void BUILD();
|
||||
wxWindow* window {nullptr};
|
||||
|
||||
virtual void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxTextCtrl*>(window)->SetValue(wxString(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
virtual void set_value(const boost::any& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
boost::any& get_value() override;
|
||||
|
||||
virtual void enable();
|
||||
virtual void disable();
|
||||
virtual wxWindow* getWindow() { return window; }
|
||||
};
|
||||
|
||||
class CheckBox : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
CheckBox(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
CheckBox(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~CheckBox() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const bool value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxCheckBox*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
boost::any& get_value() override;
|
||||
|
||||
void enable() override { dynamic_cast<wxCheckBox*>(window)->Enable(); }
|
||||
void disable() override { dynamic_cast<wxCheckBox*>(window)->Disable(); }
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class SpinCtrl : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
SpinCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id), tmp_value(-9999) {}
|
||||
SpinCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id), tmp_value(-9999) {}
|
||||
~SpinCtrl() {}
|
||||
|
||||
int tmp_value;
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
tmp_value = boost::any_cast<int>(value);
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
boost::any& get_value() override {
|
||||
// return boost::any(tmp_value);
|
||||
return m_value = tmp_value;
|
||||
}
|
||||
|
||||
void enable() override { dynamic_cast<wxSpinCtrl*>(window)->Enable(); }
|
||||
void disable() override { dynamic_cast<wxSpinCtrl*>(window)->Disable(); }
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class Choice : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
Choice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
Choice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~Choice() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_selection();
|
||||
void set_value(const std::string& value, bool change_event = false);
|
||||
void set_value(const boost::any& value, bool change_event = false);
|
||||
void set_values(const std::vector<std::string> &values);
|
||||
boost::any& get_value() override;
|
||||
|
||||
void enable() override { dynamic_cast<wxComboBox*>(window)->Enable(); };
|
||||
void disable() override{ dynamic_cast<wxComboBox*>(window)->Disable(); };
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class ColourPicker : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
ColourPicker(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
ColourPicker(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~ColourPicker() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
boost::any& get_value() override;
|
||||
|
||||
void enable() override { dynamic_cast<wxColourPickerCtrl*>(window)->Enable(); };
|
||||
void disable() override{ dynamic_cast<wxColourPickerCtrl*>(window)->Disable(); };
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class PointCtrl : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
PointCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
PointCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~PointCtrl() {}
|
||||
|
||||
wxSizer* sizer{ nullptr };
|
||||
wxTextCtrl* x_textctrl{ nullptr };
|
||||
wxTextCtrl* y_textctrl{ nullptr };
|
||||
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const Vec2d& value, bool change_event = false);
|
||||
void set_value(const boost::any& value, bool change_event = false);
|
||||
boost::any& get_value() override;
|
||||
|
||||
void enable() override {
|
||||
x_textctrl->Enable();
|
||||
y_textctrl->Enable(); }
|
||||
void disable() override{
|
||||
x_textctrl->Disable();
|
||||
y_textctrl->Disable(); }
|
||||
wxSizer* getSizer() override { return sizer; }
|
||||
};
|
||||
|
||||
class StaticText : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
StaticText(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
StaticText(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~StaticText() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxStaticText*>(window)->SetLabel(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxStaticText*>(window)->SetLabel(boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
boost::any& get_value()override { return m_value; }
|
||||
|
||||
void enable() override { dynamic_cast<wxStaticText*>(window)->Enable(); };
|
||||
void disable() override{ dynamic_cast<wxStaticText*>(window)->Disable(); };
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class SliderCtrl : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
SliderCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
SliderCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~SliderCtrl() {}
|
||||
|
||||
wxSizer* m_sizer{ nullptr };
|
||||
wxTextCtrl* m_textctrl{ nullptr };
|
||||
wxSlider* m_slider{ nullptr };
|
||||
|
||||
int m_scale = 10;
|
||||
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const int value, bool change_event = false);
|
||||
void set_value(const boost::any& value, bool change_event = false);
|
||||
boost::any& get_value() override;
|
||||
|
||||
void enable() override {
|
||||
m_slider->Enable();
|
||||
m_textctrl->Enable();
|
||||
m_textctrl->SetEditable(true);
|
||||
}
|
||||
void disable() override{
|
||||
m_slider->Disable();
|
||||
m_textctrl->Disable();
|
||||
m_textctrl->SetEditable(false);
|
||||
}
|
||||
wxSizer* getSizer() override { return m_sizer; }
|
||||
wxWindow* getWindow() override { return dynamic_cast<wxWindow*>(m_slider); }
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* SLIC3R_GUI_FIELD_HPP */
|
||||
847
src/slic3r/GUI/FirmwareDialog.cpp
Normal file
847
src/slic3r/GUI/FirmwareDialog.cpp
Normal file
|
|
@ -0,0 +1,847 @@
|
|||
#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>
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
|
||||
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::wxGetApp().get_label_clr_modified());
|
||||
port_picker->Disable();
|
||||
btn_rescan->Disable();
|
||||
hex_picker->Disable();
|
||||
btn_close->Disable();
|
||||
btn_flash->SetLabel(btn_flash_label_flashing);
|
||||
progressbar->SetRange(200 * tasks); // See progress callback below
|
||||
progressbar->SetValue(0);
|
||||
progress_tasks_done = 0;
|
||||
progress_tasks_bar = 0;
|
||||
user_cancelled = false;
|
||||
timer_pulse.Start(50);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
|
||||
{
|
||||
auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
port_picker->Enable();
|
||||
btn_rescan->Enable();
|
||||
hex_picker->Enable();
|
||||
btn_close->Enable();
|
||||
btn_flash->SetLabel(btn_flash_label_ready);
|
||||
txt_status->SetForegroundColour(text_color);
|
||||
timer_pulse.Stop();
|
||||
progressbar->SetValue(progressbar->GetRange());
|
||||
|
||||
switch (complete) {
|
||||
case AC_SUCCESS: set_txt_status(_(L("Flashing succeeded!"))); break;
|
||||
case AC_FAILURE: set_txt_status(_(L("Flashing failed. Please see the avrdude log below."))); break;
|
||||
case AC_USER_CANCELLED: set_txt_status(_(L("Flashing cancelled."))); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::enable_port_picker(bool enable)
|
||||
{
|
||||
port_picker->Show(enable);
|
||||
btn_rescan->Show(enable);
|
||||
port_autodetect->Show(! enable);
|
||||
q->Layout();
|
||||
fit_no_shrink();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::load_hex_file(const wxString &path)
|
||||
{
|
||||
hex_file = HexFile(path.wx_str());
|
||||
enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::queue_status(wxString message)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_STATUS);
|
||||
evt->SetString(std::move(message));
|
||||
wxQueueEvent(this->q, evt);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::queue_error(const wxString &message)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_STATUS);
|
||||
evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message));
|
||||
|
||||
wxQueueEvent(this->q, evt); avrdude->cancel();
|
||||
}
|
||||
|
||||
bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model)
|
||||
{
|
||||
// model_id in the hex file doesn't match what the printer repoted.
|
||||
// Ask the user if it should be flashed anyway.
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
|
||||
auto evt = new wxCommandEvent(EVT_ASYNC_DIALOG, this->q->GetId());
|
||||
evt->SetString(wxString::Format(_(L(
|
||||
"This firmware hex file does not match the printer model.\n"
|
||||
"The hex file is intended for: %s\n"
|
||||
"Printer reported: %s\n\n"
|
||||
"Do you want to continue and flash this hex file anyway?\n"
|
||||
"Please only continue if you are sure this is the right thing to do.")),
|
||||
hex_file.model_id, printer_model
|
||||
));
|
||||
wxQueueEvent(this->q, evt);
|
||||
|
||||
response_cv.wait(lock, [this]() { return this->modal_response != wxID_NONE; });
|
||||
|
||||
if (modal_response == wxID_YES) {
|
||||
return true;
|
||||
} else {
|
||||
user_cancel();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FirmwareDialog::priv::check_model_id()
|
||||
{
|
||||
// XXX: The implementation in Serial doesn't currently work reliably enough to be used.
|
||||
// Therefore, regretably, so far the check cannot be used and we just return true here.
|
||||
// TODO: Rewrite Serial using more platform-native code.
|
||||
return true;
|
||||
|
||||
// if (hex_file.model_id.empty()) {
|
||||
// // No data to check against, assume it's ok
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// asio::io_service io;
|
||||
// Serial serial(io, port->port, 115200);
|
||||
// serial.printer_setup();
|
||||
|
||||
// enum {
|
||||
// TIMEOUT = 2000,
|
||||
// RETREIES = 5,
|
||||
// };
|
||||
|
||||
// if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) {
|
||||
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// std::string line;
|
||||
// error_code ec;
|
||||
// serial.printer_write_line("PRUSA Rev");
|
||||
// while (serial.read_line(TIMEOUT, line, ec)) {
|
||||
// if (ec) {
|
||||
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (line == "ok") { continue; }
|
||||
|
||||
// if (line == hex_file.model_id) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return ask_model_id_mismatch(line);
|
||||
// }
|
||||
|
||||
// line.clear();
|
||||
// }
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
|
||||
{
|
||||
enum {
|
||||
SLEEP_MS = 500,
|
||||
};
|
||||
|
||||
for (unsigned i = 0; i < retries && !user_cancelled; i++) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_MS));
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
|
||||
return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT;
|
||||
}), ports.end());
|
||||
|
||||
if (ports.size() == 1) {
|
||||
port = ports[0];
|
||||
return;
|
||||
} else if (ports.size() > 1) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
|
||||
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
|
||||
{
|
||||
asio::io_service io;
|
||||
Serial serial(io, port.port, 1200);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::lookup_port_mmu()
|
||||
{
|
||||
static const auto msg_not_found =
|
||||
"The Multi Material Control device was not found.\n"
|
||||
"If the device is connected, please press the Reset button next to the USB connector ...";
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
|
||||
return port.id_vendor != USB_VID_PRUSA ||
|
||||
port.id_product != USB_PID_MMU_BOOT &&
|
||||
port.id_product != USB_PID_MMU_APP;
|
||||
}), ports.end());
|
||||
|
||||
if (ports.size() == 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
|
||||
queue_status(_(L(msg_not_found)));
|
||||
wait_for_mmu_bootloader(30);
|
||||
} else if (ports.size() > 1) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
|
||||
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
|
||||
} else {
|
||||
if (ports[0].id_product == USB_PID_MMU_APP) {
|
||||
// The device needs to be rebooted into the bootloader mode
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
|
||||
mmu_reboot(ports[0]);
|
||||
wait_for_mmu_bootloader(10);
|
||||
|
||||
if (! port) {
|
||||
// The device in bootloader mode was not found, inform the user and wait some more...
|
||||
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ...";
|
||||
queue_status(_(L(msg_not_found)));
|
||||
wait_for_mmu_bootloader(30);
|
||||
}
|
||||
} else {
|
||||
port = ports[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_common()
|
||||
{
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega2560",
|
||||
// Using the "Wiring" mode to program Rambo or Einsy, using the STK500v2 protocol (not the STK500).
|
||||
// The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip
|
||||
// is flashed with a buggy firmware.
|
||||
"-c", "wiring",
|
||||
"-P", port->port,
|
||||
"-b", "115200", // TODO: Allow other rates? Ditto elsewhere.
|
||||
"-D",
|
||||
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
|
||||
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_mk2()
|
||||
{
|
||||
if (! port) { return; }
|
||||
|
||||
if (! check_model_id()) {
|
||||
avrdude->cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
prepare_common();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_mk3()
|
||||
{
|
||||
if (! port) { return; }
|
||||
|
||||
if (! check_model_id()) {
|
||||
avrdude->cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
prepare_common();
|
||||
|
||||
// The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy)
|
||||
// This is done via another avrdude invocation, here we build arg list for that:
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega2560",
|
||||
// Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2).
|
||||
// The Prusa's avrdude is patched again to never send semicolons inside the data packets.
|
||||
"-c", "arduino",
|
||||
"-P", port->port,
|
||||
"-b", "115200",
|
||||
"-D",
|
||||
"-u", // disable safe mode
|
||||
"-U", (boost::format("flash:w:1:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: "
|
||||
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_mm_control()
|
||||
{
|
||||
port = boost::none;
|
||||
lookup_port_mmu();
|
||||
if (! port) {
|
||||
queue_error(_(L("The device could not have been found")));
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port->port;
|
||||
queue_status(label_status_flashing);
|
||||
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega32u4",
|
||||
"-c", "avr109",
|
||||
"-P", port->port,
|
||||
"-b", "57600",
|
||||
"-D",
|
||||
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
|
||||
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
|
||||
void FirmwareDialog::priv::perform_upload()
|
||||
{
|
||||
auto filename = hex_picker->GetPath();
|
||||
if (filename.IsEmpty()) { return; }
|
||||
|
||||
load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh
|
||||
|
||||
int selection = port_picker->GetSelection();
|
||||
if (selection != wxNOT_FOUND) {
|
||||
port = this->ports[selection];
|
||||
|
||||
// Verify whether the combo box list selection equals to the combo box edit value.
|
||||
if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const bool extra_verbose = false; // For debugging
|
||||
|
||||
flashing_start(hex_file.device == HexFile::DEV_MK3 ? 2 : 1);
|
||||
|
||||
// Init the avrdude object
|
||||
AvrDude avrdude(avrdude_config);
|
||||
|
||||
// It is ok here to use the q-pointer to the FirmwareDialog
|
||||
// because the dialog ensures it doesn't exit before the background thread is done.
|
||||
auto q = this->q;
|
||||
|
||||
avrdude
|
||||
.on_run([this](AvrDude::Ptr avrdude) {
|
||||
this->avrdude = std::move(avrdude);
|
||||
|
||||
try {
|
||||
switch (this->hex_file.device) {
|
||||
case HexFile::DEV_MK3:
|
||||
this->prepare_mk3();
|
||||
break;
|
||||
|
||||
case HexFile::DEV_MM_CONTROL:
|
||||
this->prepare_mm_control();
|
||||
break;
|
||||
|
||||
default:
|
||||
this->prepare_mk2();
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what()));
|
||||
}
|
||||
})
|
||||
.on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) {
|
||||
if (extra_verbose) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg;
|
||||
}
|
||||
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
|
||||
auto wxmsg = wxString::FromUTF8(msg);
|
||||
evt->SetExtraLong(AE_MESSAGE);
|
||||
evt->SetString(std::move(wxmsg));
|
||||
wxQueueEvent(q, evt);
|
||||
}))
|
||||
.on_progress(std::move([q](const char * /* task */, unsigned progress) {
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
|
||||
evt->SetExtraLong(AE_PROGRESS);
|
||||
evt->SetInt(progress);
|
||||
wxQueueEvent(q, evt);
|
||||
}))
|
||||
.on_complete(std::move([this]() {
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_EXIT);
|
||||
evt->SetInt(this->avrdude->exit_code());
|
||||
wxQueueEvent(this->q, evt);
|
||||
}))
|
||||
.run();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::user_cancel()
|
||||
{
|
||||
if (avrdude) {
|
||||
user_cancelled = true;
|
||||
avrdude->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
|
||||
{
|
||||
AvrDudeComplete complete_kind;
|
||||
|
||||
switch (evt.GetExtraLong()) {
|
||||
case AE_MESSAGE:
|
||||
txt_stdout->AppendText(evt.GetString());
|
||||
break;
|
||||
|
||||
case AE_PROGRESS:
|
||||
// We try to track overall progress here.
|
||||
// Avrdude performs 3 tasks per one memory operation ("-U" arg),
|
||||
// first of which is reading of status data (very short).
|
||||
// We use the timer_pulse during the very first task to indicate intialization
|
||||
// and then display overall progress during the latter tasks.
|
||||
|
||||
if (progress_tasks_done > 0) {
|
||||
progressbar->SetValue(progress_tasks_bar + evt.GetInt());
|
||||
}
|
||||
|
||||
if (evt.GetInt() == 100) {
|
||||
timer_pulse.Stop();
|
||||
if (progress_tasks_done % 3 != 0) {
|
||||
progress_tasks_bar += 100;
|
||||
}
|
||||
progress_tasks_done++;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AE_EXIT:
|
||||
BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt();
|
||||
|
||||
// Figure out the exit state
|
||||
if (user_cancelled) { complete_kind = AC_USER_CANCELLED; }
|
||||
else if (avrdude->cancelled()) { complete_kind = AC_NONE; } // Ie. cancelled programatically
|
||||
else { complete_kind = evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE; }
|
||||
|
||||
flashing_done(complete_kind);
|
||||
ensure_joined();
|
||||
break;
|
||||
|
||||
case AE_STATUS:
|
||||
set_txt_status(evt.GetString());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::on_async_dialog(const wxCommandEvent &evt)
|
||||
{
|
||||
wxMessageDialog dlg(this->q, evt.GetString(), wxMessageBoxCaptionStr, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
modal_response = dlg.ShowModal();
|
||||
}
|
||||
response_cv.notify_all();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::ensure_joined()
|
||||
{
|
||||
// Make sure the background thread is collected and the AvrDude object reset
|
||||
if (avrdude) { avrdude->join(); }
|
||||
avrdude.reset();
|
||||
}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
||||
wxDialog(parent, wxID_ANY, _(L("Firmware flasher")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
p(new priv(this))
|
||||
{
|
||||
enum {
|
||||
DIALOG_MARGIN = 15,
|
||||
SPACING = 10,
|
||||
MIN_WIDTH = 600,
|
||||
MIN_HEIGHT = 200,
|
||||
MIN_HEIGHT_EXPANDED = 500,
|
||||
};
|
||||
|
||||
wxFont status_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
status_font.MakeBold();
|
||||
wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
|
||||
mono_font.MakeSmaller();
|
||||
|
||||
// Create GUI components and layout
|
||||
|
||||
auto *panel = new wxPanel(this);
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
panel->SetSizer(vsizer);
|
||||
|
||||
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
|
||||
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr,
|
||||
"Hex files (*.hex)|*.hex|All files|*.*");
|
||||
|
||||
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
|
||||
p->port_picker = new wxComboBox(panel, wxID_ANY);
|
||||
p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
|
||||
p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
|
||||
auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
|
||||
port_sizer->Add(p->btn_rescan, 0);
|
||||
port_sizer->Add(p->port_autodetect, 1, wxEXPAND);
|
||||
p->enable_port_picker(true);
|
||||
|
||||
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
|
||||
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
|
||||
|
||||
auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:")));
|
||||
p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready")));
|
||||
p->txt_status->SetFont(status_font);
|
||||
|
||||
auto *grid = new wxFlexGridSizer(2, SPACING, SPACING);
|
||||
grid->AddGrowableCol(1);
|
||||
|
||||
grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->hex_picker, 0, wxEXPAND);
|
||||
|
||||
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(port_sizer, 0, wxEXPAND);
|
||||
|
||||
grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->txt_status, 0, wxEXPAND);
|
||||
|
||||
vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING);
|
||||
|
||||
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE);
|
||||
auto *spoiler_pane = p->spoiler->GetPane();
|
||||
auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
|
||||
p->txt_stdout->SetFont(mono_font);
|
||||
spoiler_sizer->Add(p->txt_stdout, 1, wxEXPAND);
|
||||
spoiler_pane->SetSizer(spoiler_sizer);
|
||||
// The doc says proportion need to be 0 for wxCollapsiblePane.
|
||||
// Experience says it needs to be 1, otherwise things won't get sized properly.
|
||||
vsizer->Add(p->spoiler, 1, wxEXPAND | wxBOTTOM, SPACING);
|
||||
|
||||
p->btn_close = new wxButton(panel, wxID_CLOSE);
|
||||
p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready);
|
||||
p->btn_flash->Disable();
|
||||
auto *bsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
bsizer->Add(p->btn_close);
|
||||
bsizer->AddStretchSpacer();
|
||||
bsizer->Add(p->btn_flash);
|
||||
vsizer->Add(bsizer, 0, wxEXPAND);
|
||||
|
||||
auto *topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
topsizer->Add(panel, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
|
||||
SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
|
||||
SetSizerAndFit(topsizer);
|
||||
const auto size = GetSize();
|
||||
SetSize(std::max(size.GetWidth(), static_cast<int>(MIN_WIDTH)), std::max(size.GetHeight(), static_cast<int>(MIN_HEIGHT)));
|
||||
Layout();
|
||||
|
||||
// Bind events
|
||||
|
||||
p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
|
||||
if (wxFileExists(evt.GetPath())) {
|
||||
this->p->load_hex_file(evt.GetPath());
|
||||
this->p->btn_flash->Enable();
|
||||
}
|
||||
});
|
||||
|
||||
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) {
|
||||
if (evt.GetCollapsed()) {
|
||||
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
|
||||
const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight();
|
||||
this->SetSize(this->GetSize().GetWidth(), new_height);
|
||||
} else {
|
||||
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED));
|
||||
}
|
||||
|
||||
this->Layout();
|
||||
this->p->fit_no_shrink();
|
||||
});
|
||||
|
||||
p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); });
|
||||
p->btn_rescan->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->p->find_serial_ports(); });
|
||||
|
||||
p->btn_flash->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
|
||||
if (this->p->avrdude) {
|
||||
// Flashing is in progress, ask the user if they're really sure about canceling it
|
||||
wxMessageDialog dlg(this,
|
||||
_(L("Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!")),
|
||||
_(L("Confirmation")),
|
||||
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
if (dlg.ShowModal() == wxID_YES) {
|
||||
this->p->set_txt_status(_(L("Cancelling...")));
|
||||
this->p->user_cancel();
|
||||
}
|
||||
} else {
|
||||
// Start a flashing task
|
||||
this->p->perform_upload();
|
||||
}
|
||||
});
|
||||
|
||||
Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); });
|
||||
|
||||
Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); });
|
||||
Bind(EVT_ASYNC_DIALOG, [this](wxCommandEvent &evt) { this->p->on_async_dialog(evt); });
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
|
||||
if (this->p->avrdude) {
|
||||
evt.Veto();
|
||||
} else {
|
||||
evt.Skip();
|
||||
}
|
||||
});
|
||||
|
||||
p->find_serial_ports();
|
||||
}
|
||||
|
||||
FirmwareDialog::~FirmwareDialog()
|
||||
{
|
||||
// Needed bacuse of forward defs
|
||||
}
|
||||
|
||||
void FirmwareDialog::run(wxWindow *parent)
|
||||
{
|
||||
FirmwareDialog dialog(parent);
|
||||
dialog.ShowModal();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
31
src/slic3r/GUI/FirmwareDialog.hpp
Normal file
31
src/slic3r/GUI/FirmwareDialog.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef slic3r_FirmwareDialog_hpp_
|
||||
#define slic3r_FirmwareDialog_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
class FirmwareDialog: public wxDialog
|
||||
{
|
||||
public:
|
||||
FirmwareDialog(wxWindow *parent);
|
||||
FirmwareDialog(FirmwareDialog &&) = delete;
|
||||
FirmwareDialog(const FirmwareDialog &) = delete;
|
||||
FirmwareDialog &operator=(FirmwareDialog &&) = delete;
|
||||
FirmwareDialog &operator=(const FirmwareDialog &) = delete;
|
||||
~FirmwareDialog();
|
||||
|
||||
static void run(wxWindow *parent);
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
6958
src/slic3r/GUI/GLCanvas3D.cpp
Normal file
6958
src/slic3r/GUI/GLCanvas3D.cpp
Normal file
File diff suppressed because it is too large
Load diff
1013
src/slic3r/GUI/GLCanvas3D.hpp
Normal file
1013
src/slic3r/GUI/GLCanvas3D.hpp
Normal file
File diff suppressed because it is too large
Load diff
691
src/slic3r/GUI/GLCanvas3DManager.cpp
Normal file
691
src/slic3r/GUI/GLCanvas3DManager.cpp
Normal file
|
|
@ -0,0 +1,691 @@
|
|||
#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::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown;
|
||||
|
||||
GLCanvas3DManager::GLCanvas3DManager()
|
||||
#if ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
: m_context(nullptr)
|
||||
, m_current(nullptr)
|
||||
#else
|
||||
: m_current(nullptr)
|
||||
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
, m_gl_initialized(false)
|
||||
, m_use_legacy_opengl(false)
|
||||
, m_use_VBOs(false)
|
||||
{
|
||||
}
|
||||
|
||||
#if ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
GLCanvas3DManager::~GLCanvas3DManager()
|
||||
{
|
||||
if (m_context != nullptr)
|
||||
{
|
||||
delete m_context;
|
||||
m_context = nullptr;
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
|
||||
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();
|
||||
|
||||
#if ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
if (m_context == nullptr)
|
||||
{
|
||||
m_context = new wxGLContext(canvas);
|
||||
if (m_context == nullptr)
|
||||
return false;
|
||||
}
|
||||
|
||||
canvas3D->set_context(m_context);
|
||||
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
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);
|
||||
}
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas)
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second : nullptr;
|
||||
}
|
||||
#else
|
||||
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);
|
||||
}
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
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() : "";
|
||||
}
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->is_layers_editing_enabled() : false;
|
||||
}
|
||||
|
||||
bool GLCanvas3DManager::is_layers_editing_allowed(wxGLCanvas* canvas) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->is_layers_editing_allowed() : false;
|
||||
}
|
||||
|
||||
bool GLCanvas3DManager::is_shader_enabled(wxGLCanvas* canvas) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->is_shader_enabled() : false;
|
||||
}
|
||||
|
||||
bool GLCanvas3DManager::is_reload_delayed(wxGLCanvas* canvas) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->is_reload_delayed() : false;
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_layers_editing(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_layers_editing(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_warning_texture(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_warning_texture(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_legend_texture(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_legend_texture(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_picking(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_picking(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_moving(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_moving(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_gizmos(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_toolbar(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_toolbar(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_shader(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_force_zoom_to_bed(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_dynamic_background(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_dynamic_background(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->allow_multisample(allow);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_toolbar_item(name, enable);
|
||||
}
|
||||
|
||||
bool GLCanvas3DManager::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->is_toolbar_item_pressed(name) : false;
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->zoom_to_bed();
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::zoom_to_volumes(wxGLCanvas* canvas)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->zoom_to_volumes();
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::select_view(wxGLCanvas* canvas, const std::string& direction)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->select_view(direction);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
{
|
||||
CanvasesMap::iterator other_it = _get_canvas(other);
|
||||
if (other_it != m_canvases.end())
|
||||
it->second->set_viewport_from_scene(*other_it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::update_volumes_colors_by_extruder(wxGLCanvas* canvas)
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->update_volumes_colors_by_extruder();
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::update_gizmos_data(wxGLCanvas* canvas)
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->update_gizmos_data();
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::render(wxGLCanvas* canvas) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->render();
|
||||
}
|
||||
|
||||
std::vector<double> GLCanvas3DManager::get_current_print_zs(wxGLCanvas* canvas, bool active_only) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->get_current_print_zs(active_only) : std::vector<double>();
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::set_toolpaths_range(wxGLCanvas* canvas, double low, double high)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->set_toolpaths_range(low, high);
|
||||
}
|
||||
|
||||
std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs)
|
||||
{
|
||||
if (model_object == nullptr)
|
||||
return std::vector<int>();
|
||||
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector<int>();
|
||||
}
|
||||
|
||||
std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx)
|
||||
{
|
||||
if (model == nullptr)
|
||||
return std::vector<int>();
|
||||
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector<int>();
|
||||
}
|
||||
|
||||
int GLCanvas3DManager::get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->get_first_volume_id(obj_idx) : -1;
|
||||
}
|
||||
|
||||
int GLCanvas3DManager::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->get_in_object_volume_id(scene_vol_idx) : -1;
|
||||
}
|
||||
|
||||
#if ENABLE_MIRROR
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
void GLCanvas3DManager::mirror_selection(wxGLCanvas* canvas, Axis axis)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->mirror_selection(axis);
|
||||
}
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
#endif // ENABLE_MIRROR
|
||||
|
||||
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(wxGLCanvas* canvas)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->reset_legend_texture();
|
||||
}
|
||||
|
||||
wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent)
|
||||
{
|
||||
int attribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0 };
|
||||
|
||||
if (s_multisample == MS_Unknown)
|
||||
{
|
||||
_detect_multisample(attribList);
|
||||
// debug output
|
||||
std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl;
|
||||
}
|
||||
|
||||
if (! can_multisample()) {
|
||||
attribList[4] = 0;
|
||||
}
|
||||
|
||||
return new wxGLCanvas(parent, wxID_ANY, attribList);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::_detect_multisample(int* attribList)
|
||||
{
|
||||
int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER;
|
||||
const AppConfig* app_config = GUI::get_app_config();
|
||||
bool enable_multisample = app_config != nullptr
|
||||
&& app_config->get("use_legacy_opengl") != "1"
|
||||
&& wxVersion >= 30003;
|
||||
|
||||
s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? MS_Enabled : MS_Disabled;
|
||||
// Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows
|
||||
// s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample");
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
193
src/slic3r/GUI/GLCanvas3DManager.hpp
Normal file
193
src/slic3r/GUI/GLCanvas3DManager.hpp
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#ifndef slic3r_GLCanvas3DManager_hpp_
|
||||
#define slic3r_GLCanvas3DManager_hpp_
|
||||
|
||||
#include "../../libslic3r/BoundingBox.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
class wxWindow;
|
||||
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;
|
||||
};
|
||||
|
||||
enum EMultisampleState : unsigned char
|
||||
{
|
||||
MS_Unknown,
|
||||
MS_Enabled,
|
||||
MS_Disabled
|
||||
};
|
||||
|
||||
typedef std::map<wxGLCanvas*, GLCanvas3D*> CanvasesMap;
|
||||
|
||||
CanvasesMap m_canvases;
|
||||
#if ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
wxGLContext* m_context;
|
||||
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
wxGLCanvas* m_current;
|
||||
GLInfo m_gl_info;
|
||||
bool m_gl_initialized;
|
||||
bool m_use_legacy_opengl;
|
||||
bool m_use_VBOs;
|
||||
static EMultisampleState s_multisample;
|
||||
|
||||
public:
|
||||
GLCanvas3DManager();
|
||||
#if ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
~GLCanvas3DManager();
|
||||
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
|
||||
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);
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
void deselect_volumes(wxGLCanvas* canvas);
|
||||
void select_volume(wxGLCanvas* canvas, unsigned int id);
|
||||
void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
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);
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
GLCanvas3D* get_canvas(wxGLCanvas* canvas);
|
||||
#else
|
||||
void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
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);
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
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;
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
bool is_layers_editing_enabled(wxGLCanvas* canvas) const;
|
||||
bool is_layers_editing_allowed(wxGLCanvas* canvas) const;
|
||||
bool is_shader_enabled(wxGLCanvas* canvas) const;
|
||||
|
||||
bool is_reload_delayed(wxGLCanvas* canvas) const;
|
||||
|
||||
void enable_layers_editing(wxGLCanvas* canvas, bool enable);
|
||||
void enable_warning_texture(wxGLCanvas* canvas, bool enable);
|
||||
void enable_legend_texture(wxGLCanvas* canvas, bool enable);
|
||||
void enable_picking(wxGLCanvas* canvas, bool enable);
|
||||
void enable_moving(wxGLCanvas* canvas, bool enable);
|
||||
void enable_gizmos(wxGLCanvas* canvas, bool enable);
|
||||
void enable_toolbar(wxGLCanvas* canvas, bool enable);
|
||||
void enable_shader(wxGLCanvas* canvas, bool enable);
|
||||
void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
|
||||
void enable_dynamic_background(wxGLCanvas* canvas, bool enable);
|
||||
void allow_multisample(wxGLCanvas* canvas, bool allow);
|
||||
|
||||
void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable);
|
||||
bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const;
|
||||
|
||||
void zoom_to_bed(wxGLCanvas* canvas);
|
||||
void zoom_to_volumes(wxGLCanvas* canvas);
|
||||
void select_view(wxGLCanvas* canvas, const std::string& direction);
|
||||
void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
|
||||
|
||||
void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
|
||||
void update_gizmos_data(wxGLCanvas* canvas);
|
||||
|
||||
void render(wxGLCanvas* canvas) const;
|
||||
|
||||
std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only) const;
|
||||
void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
|
||||
|
||||
std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
|
||||
std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
|
||||
|
||||
int get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const;
|
||||
int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const;
|
||||
|
||||
#if ENABLE_MIRROR
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
void mirror_selection(wxGLCanvas* canvas, Axis axis);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
#endif // ENABLE_MIRROR
|
||||
|
||||
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(wxGLCanvas* canvas);
|
||||
|
||||
static bool can_multisample() { return s_multisample == MS_Enabled; }
|
||||
static wxGLCanvas* create_wxglcanvas(wxWindow *parent);
|
||||
|
||||
private:
|
||||
CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas);
|
||||
CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const;
|
||||
|
||||
bool _init(GLCanvas3D& canvas);
|
||||
static void _detect_multisample(int* attribList);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLCanvas3DManager_hpp_
|
||||
2145
src/slic3r/GUI/GLGizmo.cpp
Normal file
2145
src/slic3r/GUI/GLGizmo.cpp
Normal file
File diff suppressed because it is too large
Load diff
554
src/slic3r/GUI/GLGizmo.hpp
Normal file
554
src/slic3r/GUI/GLGizmo.hpp
Normal file
|
|
@ -0,0 +1,554 @@
|
|||
#ifndef slic3r_GLGizmo_hpp_
|
||||
#define slic3r_GLGizmo_hpp_
|
||||
|
||||
#include "../../slic3r/GUI/GLTexture.hpp"
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
#include "../../slic3r/GUI/GLCanvas3D.hpp"
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
#include "../../libslic3r/Point.hpp"
|
||||
#include "../../libslic3r/BoundingBox.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBoxf3;
|
||||
class Linef3;
|
||||
class ModelObject;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
|
||||
class GLGizmoBase
|
||||
{
|
||||
protected:
|
||||
struct Grabber
|
||||
{
|
||||
static const float SizeFactor;
|
||||
static const float MinHalfSize;
|
||||
static const float DraggingScaleFactor;
|
||||
|
||||
Vec3d center;
|
||||
Vec3d angles;
|
||||
float color[3];
|
||||
bool enabled;
|
||||
bool dragging;
|
||||
|
||||
Grabber();
|
||||
|
||||
void render(bool hover, float size) const;
|
||||
void render_for_picking(float size) const { render(size, color, false); }
|
||||
|
||||
private:
|
||||
void render(float size, 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(); }
|
||||
|
||||
std::string get_name() const { return on_get_name(); }
|
||||
|
||||
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(); }
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
bool is_activable(const GLCanvas3D::Selection& selection) const { return on_is_activable(selection); }
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
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);
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
void start_dragging(const GLCanvas3D::Selection& selection);
|
||||
#else
|
||||
void start_dragging(const BoundingBoxf3& box);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
void stop_dragging();
|
||||
bool is_dragging() const { return m_dragging; }
|
||||
|
||||
void update(const Linef3& mouse_ray, const Point* mouse_pos);
|
||||
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
void process_double_click() { on_process_double_click(); }
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
void render(const GLCanvas3D::Selection& selection) const { on_render(selection); }
|
||||
void render_for_picking(const GLCanvas3D::Selection& selection) const { on_render_for_picking(selection); }
|
||||
#else
|
||||
void render(const BoundingBoxf3& box) const { on_render(box); }
|
||||
void render_for_picking(const BoundingBoxf3& box) const { on_render_for_picking(box); }
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
protected:
|
||||
virtual bool on_init() = 0;
|
||||
virtual std::string on_get_name() const = 0;
|
||||
virtual void on_set_state() {}
|
||||
virtual void on_set_hover_id() {}
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return true; }
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_enable_grabber(unsigned int id) {}
|
||||
virtual void on_disable_grabber(unsigned int id) {}
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_start_dragging(const GLCanvas3D::Selection& selection) {}
|
||||
#else
|
||||
virtual void on_start_dragging(const BoundingBoxf3& box) {}
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_stop_dragging() {}
|
||||
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos) = 0;
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
virtual void on_process_double_click() {}
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_render(const GLCanvas3D::Selection& selection) const = 0;
|
||||
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const = 0;
|
||||
#else
|
||||
virtual void on_render(const BoundingBoxf3& box) const = 0;
|
||||
virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
float picking_color_component(unsigned int id) const;
|
||||
void render_grabbers(const BoundingBoxf3& box) const;
|
||||
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
|
||||
|
||||
void set_tooltip(const std::string& tooltip) const;
|
||||
std::string format(float value, unsigned int decimals) const;
|
||||
};
|
||||
|
||||
class GLGizmoRotate : public GLGizmoBase
|
||||
{
|
||||
static const float Offset;
|
||||
static const unsigned int CircleResolution;
|
||||
static const unsigned int AngleResolution;
|
||||
static const unsigned int ScaleStepsCount;
|
||||
static const float ScaleStepRad;
|
||||
static const unsigned int ScaleLongEvery;
|
||||
static const float ScaleLongTooth;
|
||||
static const 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;
|
||||
|
||||
mutable float m_snap_coarse_in_radius;
|
||||
mutable float m_snap_coarse_out_radius;
|
||||
mutable float m_snap_fine_in_radius;
|
||||
mutable float m_snap_fine_out_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 std::string on_get_name() const { return ""; }
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
|
||||
#else
|
||||
virtual void on_start_dragging(const BoundingBoxf3& box);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos);
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
virtual void on_process_double_click() { m_angle = 0.0; }
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_render(const GLCanvas3D::Selection& selection) const;
|
||||
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
|
||||
#else
|
||||
virtual void on_render(const BoundingBoxf3& box) const;
|
||||
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
private:
|
||||
void render_circle() const;
|
||||
void render_scale() const;
|
||||
void render_snap_radii() const;
|
||||
void render_reference_radius() const;
|
||||
void render_angle() const;
|
||||
void render_grabber(const BoundingBoxf3& box) const;
|
||||
|
||||
void transform_to_local() const;
|
||||
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
|
||||
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray) const;
|
||||
};
|
||||
|
||||
class GLGizmoRotate3D : public GLGizmoBase
|
||||
{
|
||||
std::vector<GLGizmoRotate> m_gizmos;
|
||||
|
||||
public:
|
||||
explicit GLGizmoRotate3D(GLCanvas3D& parent);
|
||||
|
||||
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
|
||||
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); }
|
||||
|
||||
protected:
|
||||
virtual bool on_init();
|
||||
virtual std::string on_get_name() const;
|
||||
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);
|
||||
}
|
||||
}
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); }
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
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);
|
||||
}
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
|
||||
#else
|
||||
virtual void on_start_dragging(const BoundingBoxf3& box);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_stop_dragging();
|
||||
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos)
|
||||
{
|
||||
for (GLGizmoRotate& g : m_gizmos)
|
||||
{
|
||||
g.update(mouse_ray, mouse_pos);
|
||||
}
|
||||
}
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
virtual void on_process_double_click()
|
||||
{
|
||||
if (m_hover_id != -1)
|
||||
m_gizmos[m_hover_id].process_double_click();
|
||||
}
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_render(const GLCanvas3D::Selection& selection) const;
|
||||
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const
|
||||
{
|
||||
for (const GLGizmoRotate& g : m_gizmos)
|
||||
{
|
||||
g.render_for_picking(selection);
|
||||
}
|
||||
}
|
||||
#else
|
||||
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);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
};
|
||||
|
||||
class GLGizmoScale3D : public GLGizmoBase
|
||||
{
|
||||
static const float Offset;
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
static const Vec3d OffsetVec;
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
mutable BoundingBoxf3 m_box;
|
||||
|
||||
Vec3d m_scale;
|
||||
|
||||
Vec3d m_starting_scale;
|
||||
Vec3d m_starting_drag_position;
|
||||
BoundingBoxf3 m_starting_box;
|
||||
|
||||
public:
|
||||
explicit GLGizmoScale3D(GLCanvas3D& parent);
|
||||
|
||||
const Vec3d& get_scale() const { return m_scale; }
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
void set_scale(const Vec3d& scale) { m_starting_scale = scale; m_scale = scale; }
|
||||
#else
|
||||
void set_scale(const Vec3d& scale) { m_starting_scale = scale; }
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
protected:
|
||||
virtual bool on_init();
|
||||
virtual std::string on_get_name() const;
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); }
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
|
||||
#else
|
||||
virtual void on_start_dragging(const BoundingBoxf3& box);
|
||||
virtual void on_stop_dragging() { m_show_starting_box = false; }
|
||||
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos);
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
virtual void on_process_double_click();
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_render(const GLCanvas3D::Selection& selection) const;
|
||||
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
|
||||
#else
|
||||
virtual void on_render(const BoundingBoxf3& box) const;
|
||||
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
private:
|
||||
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);
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
double calc_ratio(const Linef3& mouse_ray) const;
|
||||
#else
|
||||
double calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Vec3d& center) const;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
};
|
||||
|
||||
class GLGizmoMove3D : public GLGizmoBase
|
||||
{
|
||||
static const double Offset;
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
Vec3d m_displacement;
|
||||
#else
|
||||
Vec3d m_position;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
Vec3d m_starting_drag_position;
|
||||
Vec3d m_starting_box_center;
|
||||
Vec3d m_starting_box_bottom_center;
|
||||
|
||||
public:
|
||||
explicit GLGizmoMove3D(GLCanvas3D& parent);
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
const Vec3d& get_displacement() const { return m_displacement; }
|
||||
#else
|
||||
const Vec3d& get_position() const { return m_position; }
|
||||
void set_position(const Vec3d& position) { m_position = position; }
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
protected:
|
||||
virtual bool on_init();
|
||||
virtual std::string on_get_name() const;
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
|
||||
virtual void on_stop_dragging();
|
||||
#else
|
||||
virtual void on_start_dragging(const BoundingBoxf3& box);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos);
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_render(const GLCanvas3D::Selection& selection) const;
|
||||
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
|
||||
#else
|
||||
virtual void on_render(const BoundingBoxf3& box) const;
|
||||
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
private:
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
double calc_projection(const Linef3& mouse_ray) const;
|
||||
#else
|
||||
double calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
};
|
||||
|
||||
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
|
||||
Vec3d mesh_first_point;
|
||||
};
|
||||
|
||||
// This holds information to decide whether recalculation is necessary:
|
||||
SourceDataSummary m_source_data;
|
||||
|
||||
std::vector<PlaneData> m_planes;
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
struct InstanceData
|
||||
{
|
||||
Transform3d matrix;
|
||||
InstanceData(const Transform3d& matrix) : matrix(matrix) {}
|
||||
};
|
||||
std::vector<InstanceData> m_instances;
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
mutable 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_rotation() const;
|
||||
|
||||
protected:
|
||||
virtual bool on_init();
|
||||
virtual std::string on_get_name() const;
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return selection.is_single_full_instance(); }
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
|
||||
#else
|
||||
virtual void on_start_dragging(const BoundingBoxf3& box);
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos) {}
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_render(const GLCanvas3D::Selection& selection) const;
|
||||
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
|
||||
#else
|
||||
virtual void on_render(const BoundingBoxf3& box) const;
|
||||
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_set_state()
|
||||
{
|
||||
if (m_state == On && is_plane_update_necessary())
|
||||
update_planes();
|
||||
}
|
||||
};
|
||||
|
||||
class GLGizmoSlaSupports : public GLGizmoBase
|
||||
{
|
||||
private:
|
||||
ModelObject* m_model_object = nullptr;
|
||||
Transform3d m_model_object_matrix;
|
||||
Vec3f unproject_on_mesh(const Vec2d& mouse_pos);
|
||||
|
||||
Eigen::MatrixXf m_V; // vertices
|
||||
Eigen::MatrixXi m_F; // facets indices
|
||||
struct SourceDataSummary {
|
||||
BoundingBoxf3 bounding_box;
|
||||
#if !ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
|
||||
Vec3d scaling_factor;
|
||||
Vec3d rotation;
|
||||
Vec3d offset;
|
||||
#else
|
||||
Transform3d matrix;
|
||||
#endif
|
||||
Vec3d mesh_first_point;
|
||||
};
|
||||
|
||||
// This holds information to decide whether recalculation is necessary:
|
||||
SourceDataSummary m_source_data;
|
||||
|
||||
mutable Vec3d m_starting_center;
|
||||
|
||||
public:
|
||||
explicit GLGizmoSlaSupports(GLCanvas3D& parent);
|
||||
void set_model_object_ptr(ModelObject* model_object);
|
||||
void clicked_on_object(const Vec2d& mouse_position);
|
||||
void delete_current_grabber(bool delete_all);
|
||||
|
||||
private:
|
||||
bool on_init();
|
||||
void on_update(const Linef3& mouse_ray, const Point* mouse_pos);
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
virtual void on_render(const GLCanvas3D::Selection& selection) const;
|
||||
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
|
||||
#else
|
||||
virtual void on_render(const BoundingBoxf3& box) const;
|
||||
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
void render_grabbers(bool picking = false) const;
|
||||
void render_tooltip_texture() const;
|
||||
bool is_mesh_update_necessary() const;
|
||||
void update_mesh();
|
||||
|
||||
mutable GLTexture m_tooltip_texture;
|
||||
mutable GLTexture m_reset_texture;
|
||||
|
||||
protected:
|
||||
void on_set_state() override {
|
||||
if (m_state == On && is_mesh_update_necessary())
|
||||
update_mesh();
|
||||
}
|
||||
std::string on_get_name() const override;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmo_hpp_
|
||||
|
||||
256
src/slic3r/GUI/GLShader.cpp
Normal file
256
src/slic3r/GUI/GLShader.cpp
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
#include <GL/glew.h>
|
||||
|
||||
#include "GLShader.hpp"
|
||||
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <assert.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
GLShader::~GLShader()
|
||||
{
|
||||
assert(fragment_program_id == 0);
|
||||
assert(vertex_program_id == 0);
|
||||
assert(shader_program_id == 0);
|
||||
}
|
||||
|
||||
// A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr.
|
||||
inline std::string gl_get_string_safe(GLenum param)
|
||||
{
|
||||
const char *value = (const char*)glGetString(param);
|
||||
return std::string(value ? value : "N/A");
|
||||
}
|
||||
|
||||
bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader)
|
||||
{
|
||||
std::string gl_version = gl_get_string_safe(GL_VERSION);
|
||||
int major = atoi(gl_version.c_str());
|
||||
//int minor = atoi(gl_version.c_str() + gl_version.find('.') + 1);
|
||||
if (major < 2) {
|
||||
// Cannot create a shader object on OpenGL 1.x.
|
||||
// Form an error message.
|
||||
std::string gl_vendor = gl_get_string_safe(GL_VENDOR);
|
||||
std::string gl_renderer = gl_get_string_safe(GL_RENDERER);
|
||||
std::string glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION);
|
||||
last_error = "Your computer does not support OpenGL shaders.\n";
|
||||
#ifdef _WIN32
|
||||
if (gl_vendor == "Microsoft Corporation" && gl_renderer == "GDI Generic") {
|
||||
last_error = "Windows is using a software OpenGL renderer.\n"
|
||||
"You are either connected over remote desktop,\n"
|
||||
"or a hardware acceleration is not available.\n";
|
||||
}
|
||||
#endif
|
||||
last_error += "GL version: " + gl_version + "\n";
|
||||
last_error += "vendor: " + gl_vendor + "\n";
|
||||
last_error += "renderer: " + gl_renderer + "\n";
|
||||
last_error += "GLSL version: " + glsl_version + "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fragment_shader != nullptr) {
|
||||
this->fragment_program_id = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
if (this->fragment_program_id == 0) {
|
||||
last_error = "glCreateShader(GL_FRAGMENT_SHADER) failed.";
|
||||
return false;
|
||||
}
|
||||
GLint len = (GLint)strlen(fragment_shader);
|
||||
glShaderSource(this->fragment_program_id, 1, &fragment_shader, &len);
|
||||
glCompileShader(this->fragment_program_id);
|
||||
GLint params;
|
||||
glGetShaderiv(this->fragment_program_id, GL_COMPILE_STATUS, ¶ms);
|
||||
if (params == GL_FALSE) {
|
||||
// Compilation failed. Get the log.
|
||||
glGetShaderiv(this->fragment_program_id, GL_INFO_LOG_LENGTH, ¶ms);
|
||||
std::vector<char> msg(params);
|
||||
glGetShaderInfoLog(this->fragment_program_id, params, ¶ms, msg.data());
|
||||
this->last_error = std::string("Fragment shader compilation failed:\n") + msg.data();
|
||||
this->release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (vertex_shader != nullptr) {
|
||||
this->vertex_program_id = glCreateShader(GL_VERTEX_SHADER);
|
||||
if (this->vertex_program_id == 0) {
|
||||
last_error = "glCreateShader(GL_VERTEX_SHADER) failed.";
|
||||
this->release();
|
||||
return false;
|
||||
}
|
||||
GLint len = (GLint)strlen(vertex_shader);
|
||||
glShaderSource(this->vertex_program_id, 1, &vertex_shader, &len);
|
||||
glCompileShader(this->vertex_program_id);
|
||||
GLint params;
|
||||
glGetShaderiv(this->vertex_program_id, GL_COMPILE_STATUS, ¶ms);
|
||||
if (params == GL_FALSE) {
|
||||
// Compilation failed. Get the log.
|
||||
glGetShaderiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, ¶ms);
|
||||
std::vector<char> msg(params);
|
||||
glGetShaderInfoLog(this->vertex_program_id, params, ¶ms, msg.data());
|
||||
this->last_error = std::string("Vertex shader compilation failed:\n") + msg.data();
|
||||
this->release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Link shaders
|
||||
this->shader_program_id = glCreateProgram();
|
||||
if (this->shader_program_id == 0) {
|
||||
last_error = "glCreateProgram() failed.";
|
||||
this->release();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->fragment_program_id)
|
||||
glAttachShader(this->shader_program_id, this->fragment_program_id);
|
||||
if (this->vertex_program_id)
|
||||
glAttachShader(this->shader_program_id, this->vertex_program_id);
|
||||
glLinkProgram(this->shader_program_id);
|
||||
|
||||
GLint params;
|
||||
glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, ¶ms);
|
||||
if (params == GL_FALSE) {
|
||||
// Linking failed. Get the log.
|
||||
glGetProgramiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, ¶ms);
|
||||
std::vector<char> msg(params);
|
||||
glGetProgramInfoLog(this->vertex_program_id, params, ¶ms, msg.data());
|
||||
this->last_error = std::string("Shader linking failed:\n") + msg.data();
|
||||
this->release();
|
||||
return false;
|
||||
}
|
||||
|
||||
last_error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename)
|
||||
{
|
||||
const std::string& path = resources_dir() + "/shaders/";
|
||||
|
||||
boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary);
|
||||
if (!vs.good())
|
||||
return false;
|
||||
|
||||
vs.seekg(0, vs.end);
|
||||
int file_length = (int)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 = (int)fs.tellg();
|
||||
fs.seekg(0, fs.beg);
|
||||
std::string fragment_shader(file_length, '\0');
|
||||
fs.read(const_cast<char*>(fragment_shader.data()), file_length);
|
||||
if (!fs.good())
|
||||
return false;
|
||||
|
||||
fs.close();
|
||||
|
||||
return load_from_text(fragment_shader.c_str(), vertex_shader.c_str());
|
||||
}
|
||||
|
||||
void GLShader::release()
|
||||
{
|
||||
if (this->shader_program_id) {
|
||||
if (this->vertex_program_id)
|
||||
glDetachShader(this->shader_program_id, this->vertex_program_id);
|
||||
if (this->fragment_program_id)
|
||||
glDetachShader(this->shader_program_id, this->fragment_program_id);
|
||||
glDeleteProgram(this->shader_program_id);
|
||||
this->shader_program_id = 0;
|
||||
}
|
||||
|
||||
if (this->vertex_program_id) {
|
||||
glDeleteShader(this->vertex_program_id);
|
||||
this->vertex_program_id = 0;
|
||||
}
|
||||
if (this->fragment_program_id) {
|
||||
glDeleteShader(this->fragment_program_id);
|
||||
this->fragment_program_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GLShader::enable() const
|
||||
{
|
||||
glUseProgram(this->shader_program_id);
|
||||
}
|
||||
|
||||
void GLShader::disable() const
|
||||
{
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
// Return shader vertex attribute ID
|
||||
int GLShader::get_attrib_location(const char *name) const
|
||||
{
|
||||
return this->shader_program_id ? glGetAttribLocation(this->shader_program_id, name) : -1;
|
||||
}
|
||||
|
||||
// Return shader uniform variable ID
|
||||
int GLShader::get_uniform_location(const char *name) const
|
||||
{
|
||||
return this->shader_program_id ? glGetUniformLocation(this->shader_program_id, name) : -1;
|
||||
}
|
||||
|
||||
bool GLShader::set_uniform(const char *name, float value) const
|
||||
{
|
||||
int id = this->get_uniform_location(name);
|
||||
if (id >= 0) {
|
||||
glUniform1fARB(id, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GLShader::set_uniform(const char* name, const float* matrix) const
|
||||
{
|
||||
int id = get_uniform_location(name);
|
||||
if (id >= 0)
|
||||
{
|
||||
::glUniformMatrix4fv(id, 1, GL_FALSE, (const GLfloat*)matrix);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
# Set shader vector
|
||||
sub SetVector
|
||||
{
|
||||
my($self,$var,@values) = @_;
|
||||
|
||||
my $id = $self->Map($var);
|
||||
return 'Unable to map $var' if (!defined($id));
|
||||
|
||||
my $count = scalar(@values);
|
||||
eval('glUniform'.$count.'fARB($id,@values)');
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
# Set shader 4x4 matrix
|
||||
sub SetMatrix
|
||||
{
|
||||
my($self,$var,$oga) = @_;
|
||||
|
||||
my $id = $self->Map($var);
|
||||
return 'Unable to map $var' if (!defined($id));
|
||||
|
||||
glUniformMatrix4fvARB_c($id,1,0,$oga->ptr());
|
||||
return '';
|
||||
}
|
||||
*/
|
||||
|
||||
} // namespace Slic3r
|
||||
41
src/slic3r/GUI/GLShader.hpp
Normal file
41
src/slic3r/GUI/GLShader.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef slic3r_GLShader_hpp_
|
||||
#define slic3r_GLShader_hpp_
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GLShader
|
||||
{
|
||||
public:
|
||||
GLShader() :
|
||||
fragment_program_id(0),
|
||||
vertex_program_id(0),
|
||||
shader_program_id(0)
|
||||
{}
|
||||
~GLShader();
|
||||
|
||||
bool load_from_text(const char *fragment_shader, const char *vertex_shader);
|
||||
bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename);
|
||||
|
||||
void release();
|
||||
|
||||
int get_attrib_location(const char *name) const;
|
||||
int get_uniform_location(const char *name) const;
|
||||
|
||||
bool set_uniform(const char *name, float value) const;
|
||||
bool set_uniform(const char* name, const float* matrix) const;
|
||||
|
||||
void enable() const;
|
||||
void disable() const;
|
||||
|
||||
unsigned int fragment_program_id;
|
||||
unsigned int vertex_program_id;
|
||||
unsigned int shader_program_id;
|
||||
std::string last_error;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* slic3r_GLShader_hpp_ */
|
||||
199
src/slic3r/GUI/GLTexture.cpp
Normal file
199
src/slic3r/GUI/GLTexture.cpp
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
#include "GLTexture.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <wx/image.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };
|
||||
|
||||
GLTexture::GLTexture()
|
||||
: m_id(0)
|
||||
, m_width(0)
|
||||
, m_height(0)
|
||||
, m_source("")
|
||||
{
|
||||
}
|
||||
|
||||
GLTexture::~GLTexture()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmaps)
|
||||
{
|
||||
reset();
|
||||
|
||||
if (!boost::filesystem::exists(filename))
|
||||
return false;
|
||||
|
||||
// Load a PNG with an alpha channel.
|
||||
wxImage image;
|
||||
if (!image.LoadFile(filename, wxBITMAP_TYPE_PNG))
|
||||
{
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_width = image.GetWidth();
|
||||
m_height = image.GetHeight();
|
||||
int n_pixels = m_width * m_height;
|
||||
|
||||
if (n_pixels <= 0)
|
||||
{
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get RGB & alpha raw data from wxImage, pack them into an array.
|
||||
unsigned char* img_rgb = image.GetData();
|
||||
if (img_rgb == nullptr)
|
||||
{
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char* img_alpha = image.GetAlpha();
|
||||
|
||||
std::vector<unsigned char> data(n_pixels * 4, 0);
|
||||
for (int i = 0; i < n_pixels; ++i)
|
||||
{
|
||||
int data_id = i * 4;
|
||||
int img_id = i * 3;
|
||||
data[data_id + 0] = img_rgb[img_id + 0];
|
||||
data[data_id + 1] = img_rgb[img_id + 1];
|
||||
data[data_id + 2] = img_rgb[img_id + 2];
|
||||
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
|
||||
}
|
||||
|
||||
// sends data to gpu
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
::glGenTextures(1, &m_id);
|
||||
::glBindTexture(GL_TEXTURE_2D, m_id);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
|
||||
if (generate_mipmaps)
|
||||
{
|
||||
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
|
||||
unsigned int levels_count = _generate_mipmaps(image);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
}
|
||||
else
|
||||
{
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
}
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
::glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
m_source = filename;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLTexture::reset()
|
||||
{
|
||||
if (m_id != 0)
|
||||
::glDeleteTextures(1, &m_id);
|
||||
|
||||
m_id = 0;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_source = "";
|
||||
}
|
||||
|
||||
unsigned int GLTexture::get_id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
int GLTexture::get_width() const
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
int GLTexture::get_height() const
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
|
||||
const std::string& GLTexture::get_source() const
|
||||
{
|
||||
return m_source;
|
||||
}
|
||||
|
||||
void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top)
|
||||
{
|
||||
render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs);
|
||||
}
|
||||
|
||||
void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs)
|
||||
{
|
||||
::glEnable(GL_BLEND);
|
||||
::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
::glEnable(GL_TEXTURE_2D);
|
||||
::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id);
|
||||
|
||||
::glBegin(GL_QUADS);
|
||||
::glTexCoord2f(uvs.left_bottom.u, uvs.left_bottom.v); ::glVertex2f(left, bottom);
|
||||
::glTexCoord2f(uvs.right_bottom.u, uvs.right_bottom.v); ::glVertex2f(right, bottom);
|
||||
::glTexCoord2f(uvs.right_top.u, uvs.right_top.v); ::glVertex2f(right, top);
|
||||
::glTexCoord2f(uvs.left_top.u, uvs.left_top.v); ::glVertex2f(left, top);
|
||||
::glEnd();
|
||||
|
||||
::glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
::glDisable(GL_TEXTURE_2D);
|
||||
::glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
unsigned int GLTexture::_generate_mipmaps(wxImage& image)
|
||||
{
|
||||
int w = image.GetWidth();
|
||||
int h = image.GetHeight();
|
||||
GLint level = 0;
|
||||
std::vector<unsigned char> data(w * h * 4, 0);
|
||||
|
||||
while ((w > 1) || (h > 1))
|
||||
{
|
||||
++level;
|
||||
|
||||
w = std::max(w / 2, 1);
|
||||
h = std::max(h / 2, 1);
|
||||
|
||||
int n_pixels = w * h;
|
||||
|
||||
image = image.ResampleBicubic(w, h);
|
||||
|
||||
unsigned char* img_rgb = image.GetData();
|
||||
unsigned char* img_alpha = image.GetAlpha();
|
||||
|
||||
data.resize(n_pixels * 4);
|
||||
for (int i = 0; i < n_pixels; ++i)
|
||||
{
|
||||
int data_id = i * 4;
|
||||
int img_id = i * 3;
|
||||
data[data_id + 0] = img_rgb[img_id + 0];
|
||||
data[data_id + 1] = img_rgb[img_id + 1];
|
||||
data[data_id + 2] = img_rgb[img_id + 2];
|
||||
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
|
||||
}
|
||||
|
||||
::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
|
||||
}
|
||||
|
||||
return (unsigned int)level;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
60
src/slic3r/GUI/GLTexture.hpp
Normal file
60
src/slic3r/GUI/GLTexture.hpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#ifndef slic3r_GLTexture_hpp_
|
||||
#define slic3r_GLTexture_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
class wxImage;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class GLTexture
|
||||
{
|
||||
public:
|
||||
struct UV
|
||||
{
|
||||
float u;
|
||||
float v;
|
||||
};
|
||||
|
||||
struct Quad_UVs
|
||||
{
|
||||
UV left_bottom;
|
||||
UV right_bottom;
|
||||
UV right_top;
|
||||
UV left_top;
|
||||
};
|
||||
|
||||
static Quad_UVs FullTextureUVs;
|
||||
|
||||
protected:
|
||||
unsigned int m_id;
|
||||
int m_width;
|
||||
int m_height;
|
||||
std::string m_source;
|
||||
|
||||
public:
|
||||
GLTexture();
|
||||
virtual ~GLTexture();
|
||||
|
||||
bool load_from_file(const std::string& filename, bool generate_mipmaps);
|
||||
void reset();
|
||||
|
||||
unsigned int get_id() const;
|
||||
int get_width() const;
|
||||
int get_height() const;
|
||||
|
||||
const std::string& get_source() const;
|
||||
|
||||
static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
|
||||
static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs);
|
||||
|
||||
protected:
|
||||
unsigned int _generate_mipmaps(wxImage& image);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLTexture_hpp_
|
||||
|
||||
750
src/slic3r/GUI/GLToolbar.cpp
Normal file
750
src/slic3r/GUI/GLToolbar.cpp
Normal file
|
|
@ -0,0 +1,750 @@
|
|||
#include "../../libslic3r/Point.hpp"
|
||||
#include "GLToolbar.hpp"
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../slic3r/GUI/GLCanvas3D.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/dcmemory.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_CUT, SimpleEvent);
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_SETTINGS, SimpleEvent);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent);
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_SELECTBYPARTS, SimpleEvent);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
|
||||
GLToolbarItem::Data::Data()
|
||||
: name("")
|
||||
, tooltip("")
|
||||
, sprite_id(-1)
|
||||
, is_toggable(false)
|
||||
{
|
||||
}
|
||||
|
||||
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(wxEvtHandler *target)
|
||||
{
|
||||
wxPostEvent(target, SimpleEvent(m_data.action_event));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
GLToolbar::~GLToolbar()
|
||||
{
|
||||
for (GLToolbarItem* item : m_items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
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(m_parent.get_wxglcanvas());
|
||||
}
|
||||
else
|
||||
{
|
||||
item->set_state(GLToolbarItem::HoverPressed);
|
||||
m_parent.render();
|
||||
item->do_action(m_parent.get_wxglcanvas());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tooltip.empty())
|
||||
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
|
||||
197
src/slic3r/GUI/GLToolbar.hpp
Normal file
197
src/slic3r/GUI/GLToolbar.hpp
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#ifndef slic3r_GLToolbar_hpp_
|
||||
#define slic3r_GLToolbar_hpp_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "GLTexture.hpp"
|
||||
#include "Event.hpp"
|
||||
|
||||
|
||||
class wxEvtHandler;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_CUT, SimpleEvent);
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_SETTINGS, SimpleEvent);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent);
|
||||
#if !ENABLE_EXTENDED_SELECTION
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_SELECTBYPARTS, SimpleEvent);
|
||||
#endif // !ENABLE_EXTENDED_SELECTION
|
||||
|
||||
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;
|
||||
wxEventType action_event;
|
||||
|
||||
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(wxEvtHandler *target);
|
||||
|
||||
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);
|
||||
~GLToolbar();
|
||||
|
||||
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_
|
||||
471
src/slic3r/GUI/GUI.cpp
Normal file
471
src/slic3r/GUI/GUI.cpp
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "../AppController.hpp"
|
||||
#include "WipeTowerDialog.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#if __APPLE__
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#elif _WIN32
|
||||
#include <Windows.h>
|
||||
// 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"
|
||||
#endif
|
||||
|
||||
#include <wx/display.h>
|
||||
|
||||
#include "wxExtensions.hpp"
|
||||
#include "GUI_Preview.hpp"
|
||||
#include "GUI_PreviewIface.hpp"
|
||||
#include "AboutDialog.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "UpdateDialogs.hpp"
|
||||
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include "../../libslic3r/Print.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
#if __APPLE__
|
||||
IOPMAssertionID assertionID;
|
||||
#endif
|
||||
|
||||
void disable_screensaver()
|
||||
{
|
||||
#if __APPLE__
|
||||
CFStringRef reasonForActivity = CFSTR("Slic3r");
|
||||
IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
|
||||
kIOPMAssertionLevelOn, reasonForActivity, &assertionID);
|
||||
// ignore result: success == kIOReturnSuccess
|
||||
#elif _WIN32
|
||||
SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void enable_screensaver()
|
||||
{
|
||||
#if __APPLE__
|
||||
IOReturn success = IOPMAssertionRelease(assertionID);
|
||||
#elif _WIN32
|
||||
SetThreadExecutionState(ES_CONTINUOUS);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool debugged()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return IsDebuggerPresent();
|
||||
#else
|
||||
return false;
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
void break_to_debugger()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (IsDebuggerPresent())
|
||||
DebugBreak();
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
PreviewIface* g_preview = nullptr;
|
||||
|
||||
bool config_wizard_startup(bool app_config_exists)
|
||||
{
|
||||
if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) {
|
||||
config_wizard(ConfigWizard::RR_DATA_EMPTY);
|
||||
return true;
|
||||
} else if (get_app_config()->legacy_datadir()) {
|
||||
// Looks like user has legacy pre-vendorbundle data directory,
|
||||
// explain what this is and run the wizard
|
||||
|
||||
MsgDataLegacy dlg;
|
||||
dlg.ShowModal();
|
||||
|
||||
config_wizard(ConfigWizard::RR_DATA_LEGACY);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void config_wizard(int reason)
|
||||
{
|
||||
// Exit wizard if there are unsaved changes and the user cancels the action.
|
||||
if (! wxGetApp().check_unsaved_changes())
|
||||
return;
|
||||
|
||||
try {
|
||||
ConfigWizard wizard(nullptr, static_cast<ConfigWizard::RunReason>(reason));
|
||||
wizard.run(wxGetApp().preset_bundle, wxGetApp().preset_updater);
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
show_error(nullptr, e.what());
|
||||
}
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
wxGetApp().load_current_presets();
|
||||
}
|
||||
|
||||
PreviewIface* create_preview_iface(wxNotebook* parent, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data)
|
||||
{
|
||||
if (g_preview == nullptr)
|
||||
{
|
||||
Preview* panel = new Preview(parent, config, print, gcode_preview_data);
|
||||
g_preview = new PreviewIface(panel);
|
||||
}
|
||||
|
||||
return g_preview;
|
||||
}
|
||||
|
||||
// opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element)
|
||||
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/)
|
||||
{
|
||||
try{
|
||||
switch (config.def()->get(opt_key)->type){
|
||||
case coFloatOrPercent:{
|
||||
std::string str = boost::any_cast<std::string>(value);
|
||||
bool percent = false;
|
||||
if (str.back() == '%'){
|
||||
str.pop_back();
|
||||
percent = true;
|
||||
}
|
||||
double val = stod(str);
|
||||
config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent));
|
||||
break;}
|
||||
case coPercent:
|
||||
config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast<double>(value)));
|
||||
break;
|
||||
case coFloat:{
|
||||
double& val = config.opt_float(opt_key);
|
||||
val = boost::any_cast<double>(value);
|
||||
break;
|
||||
}
|
||||
case coPercents:{
|
||||
ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast<double>(value) };
|
||||
config.option<ConfigOptionPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
|
||||
break;
|
||||
}
|
||||
case coFloats:{
|
||||
ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast<double>(value) };
|
||||
config.option<ConfigOptionFloats>(opt_key)->set_at(vec_new, opt_index, opt_index);
|
||||
break;
|
||||
}
|
||||
case coString:
|
||||
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
|
||||
break;
|
||||
case coStrings:{
|
||||
if (opt_key.compare("compatible_printers") == 0) {
|
||||
config.option<ConfigOptionStrings>(opt_key)->values =
|
||||
boost::any_cast<std::vector<std::string>>(value);
|
||||
}
|
||||
else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0){
|
||||
std::string str = boost::any_cast<std::string>(value);
|
||||
if (str.back() == ';') str.pop_back();
|
||||
// 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.
|
||||
std::vector<std::string> values;
|
||||
boost::split(values, str, boost::is_any_of(";"));
|
||||
if (values.size() == 1 && values[0] == "")
|
||||
break;
|
||||
config.option<ConfigOptionStrings>(opt_key)->values = values;
|
||||
}
|
||||
else{
|
||||
ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast<std::string>(value) };
|
||||
config.option<ConfigOptionStrings>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case coBool:
|
||||
config.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast<bool>(value)));
|
||||
break;
|
||||
case coBools:{
|
||||
ConfigOptionBools* vec_new = new ConfigOptionBools{ (bool)boost::any_cast<unsigned char>(value) };
|
||||
config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
break;}
|
||||
case coInt:
|
||||
config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast<int>(value)));
|
||||
break;
|
||||
case coInts:{
|
||||
ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast<int>(value) };
|
||||
config.option<ConfigOptionInts>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
}
|
||||
break;
|
||||
case coEnum:{
|
||||
if (opt_key.compare("external_fill_pattern") == 0 ||
|
||||
opt_key.compare("fill_pattern") == 0)
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<InfillPattern>(boost::any_cast<InfillPattern>(value)));
|
||||
else if (opt_key.compare("gcode_flavor") == 0)
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));
|
||||
else if (opt_key.compare("support_material_pattern") == 0)
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<SupportMaterialPattern>(boost::any_cast<SupportMaterialPattern>(value)));
|
||||
else if (opt_key.compare("seam_position") == 0)
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<SeamPosition>(boost::any_cast<SeamPosition>(value)));
|
||||
else if (opt_key.compare("host_type") == 0)
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<PrintHostType>(boost::any_cast<PrintHostType>(value)));
|
||||
}
|
||||
break;
|
||||
case coPoints:{
|
||||
if (opt_key.compare("bed_shape") == 0){
|
||||
config.option<ConfigOptionPoints>(opt_key)->values = boost::any_cast<std::vector<Vec2d>>(value);
|
||||
break;
|
||||
}
|
||||
ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast<Vec2d>(value) };
|
||||
config.option<ConfigOptionPoints>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
}
|
||||
break;
|
||||
case coNone:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
int i = 0;//no reason, just experiment
|
||||
}
|
||||
}
|
||||
|
||||
void show_error(wxWindow* parent, const wxString& message) {
|
||||
ErrorDialog msg(parent, message);
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void show_error_id(int id, const std::string& message) {
|
||||
auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr;
|
||||
show_error(parent, wxString::FromUTF8(message.data()));
|
||||
}
|
||||
|
||||
void show_info(wxWindow* parent, const wxString& message, const wxString& title){
|
||||
wxMessageDialog msg_wingow(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION);
|
||||
msg_wingow.ShowModal();
|
||||
}
|
||||
|
||||
void warning_catcher(wxWindow* parent, const wxString& message){
|
||||
if (message == "GLUquadricObjPtr | " + _(L("Attempt to free unreferenced scalar")) )
|
||||
return;
|
||||
wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING);
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value)
|
||||
{
|
||||
if (comboCtrl == nullptr)
|
||||
return;
|
||||
|
||||
wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup;
|
||||
if (popup != nullptr)
|
||||
{
|
||||
// FIXME If the following line is removed, the combo box popup list will not react to mouse clicks.
|
||||
// On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
|
||||
comboCtrl->UseAltPopupWindow();
|
||||
|
||||
comboCtrl->EnablePopupAnimation(false);
|
||||
comboCtrl->SetPopupControl(popup);
|
||||
popup->SetStringValue(from_u8(text));
|
||||
popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
|
||||
popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
|
||||
popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
|
||||
popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
|
||||
|
||||
std::vector<std::string> items_str;
|
||||
boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
|
||||
|
||||
for (const std::string& item : items_str)
|
||||
{
|
||||
popup->Append(from_u8(item));
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < popup->GetCount(); ++i)
|
||||
{
|
||||
popup->Check(i, initial_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int combochecklist_get_flags(wxComboCtrl* comboCtrl)
|
||||
{
|
||||
int flags = 0;
|
||||
|
||||
wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
|
||||
if (popup != nullptr)
|
||||
{
|
||||
for (unsigned int i = 0; i < popup->GetCount(); ++i)
|
||||
{
|
||||
if (popup->IsChecked(i))
|
||||
flags |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
AppConfig* get_app_config()
|
||||
{
|
||||
return wxGetApp().app_config;
|
||||
}
|
||||
|
||||
wxString L_str(const std::string &str)
|
||||
{
|
||||
//! Explicitly specify that the source string is already in UTF-8 encoding
|
||||
return wxGetTranslation(wxString(str.c_str(), wxConvUTF8));
|
||||
}
|
||||
|
||||
wxString from_u8(const std::string &str)
|
||||
{
|
||||
return wxString::FromUTF8(str.c_str());
|
||||
}
|
||||
|
||||
std::string into_u8(const wxString &str)
|
||||
{
|
||||
auto buffer_utf8 = str.utf8_str();
|
||||
return std::string(buffer_utf8.data());
|
||||
}
|
||||
|
||||
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height)
|
||||
{
|
||||
const auto idx = wxDisplay::GetFromWindow(window);
|
||||
if (idx == wxNOT_FOUND) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wxDisplay display(idx);
|
||||
const auto disp_size = display.GetClientArea();
|
||||
width = disp_size.GetWidth();
|
||||
height = disp_size.GetHeight();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void save_window_size(wxTopLevelWindow *window, const std::string &name)
|
||||
{
|
||||
const wxSize size = window->GetSize();
|
||||
const wxPoint pos = window->GetPosition();
|
||||
const auto maximized = window->IsMaximized() ? "1" : "0";
|
||||
|
||||
get_app_config()->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str());
|
||||
get_app_config()->set((boost::format("window_%1%_maximized") % name).str(), maximized);
|
||||
}
|
||||
|
||||
void restore_window_size(wxTopLevelWindow *window, const std::string &name)
|
||||
{
|
||||
// XXX: This still doesn't behave nicely in some situations (mostly on Linux).
|
||||
// The problem is that it's hard to obtain window position with respect to screen geometry reliably
|
||||
// from wxWidgets. Sometimes wxWidgets claim a window is located on a different screen than on which
|
||||
// it's actually visible. I suspect this has something to do with window initialization (maybe we
|
||||
// restore window geometry too early), but haven't yet found a workaround.
|
||||
|
||||
const auto display_idx = wxDisplay::GetFromWindow(window);
|
||||
if (display_idx == wxNOT_FOUND) { return; }
|
||||
|
||||
const auto display = wxDisplay(display_idx).GetClientArea();
|
||||
std::vector<std::string> pair;
|
||||
|
||||
try {
|
||||
const auto key_size = (boost::format("window_%1%_size") % name).str();
|
||||
if (get_app_config()->has(key_size)) {
|
||||
if (unescape_strings_cstyle(get_app_config()->get(key_size), pair) && pair.size() == 2) {
|
||||
auto width = boost::lexical_cast<int>(pair[0]);
|
||||
auto height = boost::lexical_cast<int>(pair[1]);
|
||||
|
||||
window->SetSize(width, height);
|
||||
}
|
||||
}
|
||||
} catch(const boost::bad_lexical_cast &) {}
|
||||
|
||||
// Maximizing should be the last thing to do.
|
||||
// This ensure the size and position are sane when the user un-maximizes the window.
|
||||
const auto key_maximized = (boost::format("window_%1%_maximized") % name).str();
|
||||
if (get_app_config()->get(key_maximized) == "1") {
|
||||
window->Maximize(true);
|
||||
}
|
||||
}
|
||||
|
||||
void about()
|
||||
{
|
||||
AboutDialog dlg;
|
||||
dlg.ShowModal();
|
||||
dlg.Destroy();
|
||||
}
|
||||
|
||||
void desktop_open_datadir_folder()
|
||||
{
|
||||
// Execute command to open a file explorer, platform dependent.
|
||||
// FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
|
||||
|
||||
const auto path = data_dir();
|
||||
#ifdef _WIN32
|
||||
const auto widepath = wxString::FromUTF8(path.data());
|
||||
const wchar_t *argv[] = { L"explorer", widepath.GetData(), nullptr };
|
||||
::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
|
||||
#elif __APPLE__
|
||||
const char *argv[] = { "open", path.data(), nullptr };
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
|
||||
#else
|
||||
const char *argv[] = { "xdg-open", path.data(), nullptr };
|
||||
|
||||
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
||||
// because they may mess up the environment expected by the file manager.
|
||||
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||
if (wxGetEnv("APPIMAGE", nullptr)) {
|
||||
// We're running from AppImage
|
||||
wxEnvVariableHashMap env_vars;
|
||||
wxGetEnvMap(&env_vars);
|
||||
|
||||
env_vars.erase("APPIMAGE");
|
||||
env_vars.erase("APPDIR");
|
||||
env_vars.erase("LD_LIBRARY_PATH");
|
||||
env_vars.erase("LD_PRELOAD");
|
||||
env_vars.erase("UNION_PRELOAD");
|
||||
|
||||
wxExecuteEnv exec_env;
|
||||
exec_env.env = std::move(env_vars);
|
||||
|
||||
wxString owd;
|
||||
if (wxGetEnv("OWD", &owd)) {
|
||||
// This is the original work directory from which the AppImage image was run,
|
||||
// set it as CWD for the child process:
|
||||
exec_env.cwd = std::move(owd);
|
||||
}
|
||||
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
|
||||
} else {
|
||||
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
AppControllerPtr g_appctl;
|
||||
}
|
||||
|
||||
AppControllerPtr get_appctl()
|
||||
{
|
||||
return g_appctl;
|
||||
}
|
||||
|
||||
void set_cli_appctl()
|
||||
{
|
||||
g_appctl = std::make_shared<AppControllerCli>();
|
||||
}
|
||||
|
||||
void set_gui_appctl()
|
||||
{
|
||||
g_appctl = std::make_shared<AppControllerGui>();
|
||||
}
|
||||
|
||||
} }
|
||||
118
src/slic3r/GUI/GUI.hpp
Normal file
118
src/slic3r/GUI/GUI.hpp
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#ifndef slic3r_GUI_hpp_
|
||||
#define slic3r_GUI_hpp_
|
||||
|
||||
#include "Config.hpp"
|
||||
#include "callback.hpp"
|
||||
|
||||
#include <wx/intl.h>
|
||||
|
||||
class wxWindow;
|
||||
class wxMenuBar;
|
||||
class wxNotebook;
|
||||
class wxComboCtrl;
|
||||
class wxFileDialog;
|
||||
class wxTopLevelWindow;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig;
|
||||
class DynamicPrintConfig;
|
||||
class PreviewIface;
|
||||
class Print;
|
||||
class GCodePreviewData;
|
||||
class AppControllerBase;
|
||||
|
||||
using AppControllerPtr = std::shared_ptr<AppControllerBase>;
|
||||
|
||||
#define _(s) Slic3r::GUI::I18N::translate((s))
|
||||
|
||||
namespace GUI { namespace I18N {
|
||||
inline wxString translate(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)); }
|
||||
inline wxString translate(const wchar_t *s) { return wxGetTranslation(s); }
|
||||
inline wxString translate(const std::string &s) { return wxGetTranslation(wxString(s.c_str(), wxConvUTF8)); }
|
||||
inline wxString translate(const std::wstring &s) { return wxGetTranslation(s.c_str()); }
|
||||
} }
|
||||
|
||||
// !!! If you needed to translate some wxString,
|
||||
// !!! please use _(L(string))
|
||||
// !!! _() - is a standard wxWidgets macro to translate
|
||||
// !!! L() is used only for marking localizable string
|
||||
// !!! It will be used in "xgettext" to create a Locating Message Catalog.
|
||||
#define L(s) s
|
||||
|
||||
//! macro used to localization, return wxScopedCharBuffer
|
||||
//! With wxConvUTF8 explicitly specify that the source string is already in UTF-8 encoding
|
||||
#define _CHB(s) wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str()
|
||||
|
||||
// Minimal buffer length for translated string (char buf[MIN_BUF_LENGTH_FOR_L])
|
||||
#define MIN_BUF_LENGTH_FOR_L 512
|
||||
|
||||
namespace GUI {
|
||||
|
||||
void disable_screensaver();
|
||||
void enable_screensaver();
|
||||
bool debugged();
|
||||
void break_to_debugger();
|
||||
|
||||
AppConfig* get_app_config();
|
||||
|
||||
AppControllerPtr get_appctl();
|
||||
void set_cli_appctl();
|
||||
void set_gui_appctl();
|
||||
|
||||
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
|
||||
|
||||
// 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);
|
||||
|
||||
PreviewIface* create_preview_iface(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data);
|
||||
|
||||
// Change option value in config
|
||||
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
|
||||
|
||||
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);
|
||||
|
||||
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
|
||||
// Items are all initialized to the given value.
|
||||
// Items must be separated by '|', for example "Item1|Item2|Item3", and so on.
|
||||
void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value);
|
||||
|
||||
// Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
|
||||
// encoded inside an int.
|
||||
int combochecklist_get_flags(wxComboCtrl* comboCtrl);
|
||||
|
||||
// Return translated std::string as a wxString
|
||||
wxString L_str(const std::string &str);
|
||||
// Return wxString from std::string in UTF8
|
||||
wxString from_u8(const std::string &str);
|
||||
// Return std::string in UTF8 from wxString
|
||||
std::string into_u8(const wxString &str);
|
||||
|
||||
// Callback to trigger a configuration update timer on the Plater.
|
||||
static PerlCallback g_on_request_update_callback;
|
||||
|
||||
// Returns the dimensions of the screen on which the main frame is displayed
|
||||
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height);
|
||||
|
||||
// Save window size and maximized status into AppConfig
|
||||
void save_window_size(wxTopLevelWindow *window, const std::string &name);
|
||||
// Restore the above
|
||||
void restore_window_size(wxTopLevelWindow *window, const std::string &name);
|
||||
|
||||
// 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
|
||||
706
src/slic3r/GUI/GUI_App.cpp
Normal file
706
src/slic3r/GUI/GUI_App.cpp
Normal file
|
|
@ -0,0 +1,706 @@
|
|||
#include "GUI_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GUI_ObjectManipulation.hpp"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <wx/stdpaths.h>
|
||||
#include <wx/imagpng.h>
|
||||
#include <wx/display.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/menuitem.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/dir.h>
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include "../Utils/PresetUpdater.hpp"
|
||||
#include "ConfigWizard_private.hpp"
|
||||
#include "slic3r/Config/Snapshot.hpp"
|
||||
#include "ConfigSnapshotDialog.hpp"
|
||||
#include "FirmwareDialog.hpp"
|
||||
#include "Preferences.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include <I18N.hpp>
|
||||
#include <wx/wupdlock.h>
|
||||
#include "SysInfoDialog.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
const wxString file_wildcards[FT_SIZE] = {
|
||||
/* FT_STL */ "STL files (*.stl)|*.stl;*.STL",
|
||||
/* FT_OBJ */ "OBJ files (*.obj)|*.obj;*.OBJ",
|
||||
/* FT_AMF */ "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML",
|
||||
/* FT_3MF */ "3MF files (*.3mf)|*.3mf;*.3MF;",
|
||||
/* FT_PRUSA */ "Prusa Control files (*.prusa)|*.prusa;*.PRUSA",
|
||||
/* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC",
|
||||
/* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA",
|
||||
|
||||
/* FT_INI */ "INI files *.ini|*.ini;*.INI",
|
||||
/* FT_SVG */ "SVG files *.svg|*.svg;*.SVG",
|
||||
};
|
||||
|
||||
|
||||
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
|
||||
|
||||
IMPLEMENT_APP(GUI_App)
|
||||
bool GUI_App::OnInit()
|
||||
{
|
||||
SetAppName("Slic3rPE");
|
||||
SetAppDisplayName("Slic3r Prusa Edition");
|
||||
|
||||
// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
|
||||
|
||||
// Set the Slic3r data directory at the Slic3r XS module.
|
||||
// Unix: ~/ .Slic3r
|
||||
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
|
||||
// Mac : "~/Library/Application Support/Slic3r"
|
||||
if (data_dir().empty())
|
||||
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
|
||||
|
||||
app_config = new AppConfig();
|
||||
preset_bundle = new PresetBundle();
|
||||
|
||||
// just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
|
||||
// supplied as argument to --datadir; in that case we should still run the wizard
|
||||
// eval{
|
||||
preset_bundle->setup_directories();
|
||||
// };
|
||||
// if ($@) {
|
||||
// warn $@ . "\n";
|
||||
// fatal_error(undef, $@);
|
||||
// }
|
||||
app_conf_exists = app_config->exists();
|
||||
// load settings
|
||||
if (app_conf_exists) app_config->load();
|
||||
app_config->set("version", SLIC3R_VERSION);
|
||||
app_config->save();
|
||||
|
||||
preset_updater = new PresetUpdater();
|
||||
|
||||
load_language();
|
||||
|
||||
// Suppress the '- default -' presets.
|
||||
preset_bundle->set_default_suppressed(app_config->get("no_defaults").empty() ? false : true);
|
||||
// eval{
|
||||
preset_bundle->load_presets(*app_config);
|
||||
// };
|
||||
// if ($@) {
|
||||
// warn $@ . "\n";
|
||||
// show_error(undef, $@);
|
||||
// }
|
||||
|
||||
// Let the libslic3r know the callback, which will translate messages on demand.
|
||||
Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);
|
||||
// initialize label colors and fonts
|
||||
init_label_colours();
|
||||
init_fonts();
|
||||
|
||||
// application frame
|
||||
std::cerr << "Creating main frame..." << std::endl;
|
||||
// wxImage::FindHandlerType(wxBITMAP_TYPE_PNG) ||
|
||||
wxImage::AddHandler(new wxPNGHandler());
|
||||
mainframe = new MainFrame(no_plater, false);
|
||||
sidebar().obj_list()->init_objects(); // propagate model objects to object list
|
||||
update_mode();
|
||||
SetTopWindow(mainframe);
|
||||
|
||||
// This makes CallAfter() work
|
||||
Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
|
||||
{
|
||||
std::function<void()> cur_cb{ nullptr };
|
||||
// try to get the mutex. If we can't, just skip this idle event and get the next one.
|
||||
if (!callback_register.try_lock()) return;
|
||||
// pop callback
|
||||
if (m_cb.size() != 0){
|
||||
cur_cb = m_cb.top();
|
||||
m_cb.pop();
|
||||
}
|
||||
// unlock mutex
|
||||
this->callback_register.unlock();
|
||||
|
||||
try { // call the function if it's not nullptr;
|
||||
if (cur_cb != nullptr) cur_cb();
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
std::cerr << "Exception thrown: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
if (app_config->dirty())
|
||||
app_config->save();
|
||||
});
|
||||
|
||||
// On OS X the UI tends to freeze in weird ways if modal dialogs(config wizard, update notifications, ...)
|
||||
// are shown before or in the same event callback with the main frame creation.
|
||||
// Therefore we schedule them for later using CallAfter.
|
||||
CallAfter([this](){
|
||||
// eval{
|
||||
if (!preset_updater->config_update())
|
||||
mainframe->Close();
|
||||
// };
|
||||
// if ($@) {
|
||||
// show_error(undef, $@);
|
||||
// mainframe->Close();
|
||||
// }
|
||||
});
|
||||
|
||||
CallAfter([this](){
|
||||
if (!config_wizard_startup(app_conf_exists)) {
|
||||
// Only notify if there was not wizard so as not to bother too much ...
|
||||
preset_updater->slic3r_update_notify();
|
||||
}
|
||||
preset_updater->sync(preset_bundle);
|
||||
});
|
||||
|
||||
|
||||
mainframe->Show(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
|
||||
{
|
||||
double r = colour.Red();
|
||||
double g = colour.Green();
|
||||
double b = colour.Blue();
|
||||
|
||||
return std::round(std::sqrt(
|
||||
r * r * .241 +
|
||||
g * g * .691 +
|
||||
b * b * .068
|
||||
));
|
||||
}
|
||||
|
||||
void GUI_App::init_label_colours()
|
||||
{
|
||||
auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
if (luma >= 128) {
|
||||
m_color_label_modified = wxColour(252, 77, 1);
|
||||
m_color_label_sys = wxColour(26, 132, 57);
|
||||
}
|
||||
else {
|
||||
m_color_label_modified = wxColour(253, 111, 40);
|
||||
m_color_label_sys = wxColour(115, 220, 103);
|
||||
}
|
||||
m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
}
|
||||
|
||||
void GUI_App::update_label_colours_from_appconfig()
|
||||
{
|
||||
if (app_config->has("label_clr_sys")){
|
||||
auto str = app_config->get("label_clr_sys");
|
||||
if (str != "")
|
||||
m_color_label_sys = wxColour(str);
|
||||
}
|
||||
|
||||
if (app_config->has("label_clr_modified")){
|
||||
auto str = app_config->get("label_clr_modified");
|
||||
if (str != "")
|
||||
m_color_label_modified = wxColour(str);
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::init_fonts()
|
||||
{
|
||||
m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold();
|
||||
#ifdef __WXMAC__
|
||||
m_small_font.SetPointSize(11);
|
||||
m_bold_font.SetPointSize(13);
|
||||
#endif /*__WXMAC__*/
|
||||
}
|
||||
|
||||
void GUI_App::set_label_clr_modified(const wxColour& clr) {
|
||||
m_color_label_modified = clr;
|
||||
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue());
|
||||
std::string str = clr_str.ToStdString();
|
||||
app_config->set("label_clr_modified", str);
|
||||
app_config->save();
|
||||
}
|
||||
|
||||
void GUI_App::set_label_clr_sys(const wxColour& clr) {
|
||||
m_color_label_sys = clr;
|
||||
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue());
|
||||
std::string str = clr_str.ToStdString();
|
||||
app_config->set("label_clr_sys", str);
|
||||
app_config->save();
|
||||
}
|
||||
|
||||
void GUI_App::recreate_GUI()
|
||||
{
|
||||
std::cerr << "recreate_GUI" << std::endl;
|
||||
|
||||
auto topwindow = GetTopWindow();
|
||||
mainframe = new MainFrame(no_plater,false);
|
||||
sidebar().obj_list()->init_objects(); // propagate model objects to object list
|
||||
update_mode();
|
||||
|
||||
if (topwindow) {
|
||||
SetTopWindow(mainframe);
|
||||
topwindow->Destroy();
|
||||
}
|
||||
|
||||
// On OSX the UI was not initialized correctly if the wizard was called
|
||||
// before the UI was up and running.
|
||||
CallAfter([](){
|
||||
// Run the config wizard, don't offer the "reset user profile" checkbox.
|
||||
config_wizard_startup(true);
|
||||
});
|
||||
}
|
||||
|
||||
void GUI_App::system_info()
|
||||
{
|
||||
SysInfoDialog dlg;
|
||||
dlg.ShowModal();
|
||||
dlg.Destroy();
|
||||
}
|
||||
|
||||
// static method accepting a wxWindow object as first parameter
|
||||
bool GUI_App::catch_error(std::function<void()> cb,
|
||||
// wxMessageDialog* message_dialog,
|
||||
const std::string& err /*= ""*/){
|
||||
if (!err.empty()) {
|
||||
if (cb)
|
||||
cb();
|
||||
// if (message_dialog)
|
||||
// message_dialog->(err, "Error", wxOK | wxICON_ERROR);
|
||||
show_error(/*this*/nullptr, err);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static method accepting a wxWindow object as first parameter
|
||||
void fatal_error(wxWindow* parent){
|
||||
show_error(parent, "");
|
||||
// exit 1; // #ys_FIXME
|
||||
}
|
||||
|
||||
// Called after the Preferences dialog is closed and the program settings are saved.
|
||||
// Update the UI based on the current preferences.
|
||||
void GUI_App::update_ui_from_settings(){
|
||||
mainframe->update_ui_from_settings();
|
||||
}
|
||||
|
||||
|
||||
void GUI_App::open_model(wxWindow *parent, wxArrayString& input_files)
|
||||
{
|
||||
auto dialog = new wxFileDialog(parent ? parent : GetTopWindow(),
|
||||
_(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")),
|
||||
app_config->get_last_dir(), "",
|
||||
file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog->ShowModal() != wxID_OK) {
|
||||
dialog->Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
dialog->GetPaths(input_files);
|
||||
dialog->Destroy();
|
||||
}
|
||||
|
||||
void GUI_App::CallAfter(std::function<void()> cb)
|
||||
{
|
||||
// set mutex
|
||||
callback_register.lock();
|
||||
// push function onto stack
|
||||
m_cb.emplace(cb);
|
||||
// unset mutex
|
||||
callback_register.unlock();
|
||||
}
|
||||
|
||||
void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
|
||||
{
|
||||
if (name.empty()) { return; }
|
||||
const auto config_key = (boost::format("window_%1%") % name).str();
|
||||
|
||||
WindowMetrics metrics = WindowMetrics::from_window(window);
|
||||
app_config->set(config_key, metrics.serialize());
|
||||
app_config->save();
|
||||
}
|
||||
|
||||
void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name)
|
||||
{
|
||||
if (name.empty()) { return; }
|
||||
const auto config_key = (boost::format("window_%1%") % name).str();
|
||||
|
||||
if (! app_config->has(config_key)) { return; }
|
||||
|
||||
auto metrics = WindowMetrics::deserialize(app_config->get(config_key));
|
||||
if (! metrics) { return; }
|
||||
|
||||
window->SetSize(metrics->get_rect());
|
||||
window->Maximize(metrics->get_maximized());
|
||||
}
|
||||
|
||||
void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
|
||||
{
|
||||
const auto display_idx = wxDisplay::GetFromWindow(window);
|
||||
if (display_idx == wxNOT_FOUND) { return; }
|
||||
|
||||
const auto display = wxDisplay(display_idx).GetClientArea();
|
||||
|
||||
auto metrics = WindowMetrics::from_window(window);
|
||||
|
||||
metrics.sanitize_for_display(display);
|
||||
if (window->GetScreenRect() != metrics.get_rect()) {
|
||||
window->SetSize(metrics.get_rect());
|
||||
}
|
||||
}
|
||||
|
||||
// select language from the list of installed languages
|
||||
bool GUI_App::select_language( wxArrayString & names,
|
||||
wxArrayLong & identifiers)
|
||||
{
|
||||
wxCHECK_MSG(names.Count() == identifiers.Count(), false,
|
||||
_(L("Array of language names and identifiers should have the same size.")));
|
||||
int init_selection = 0;
|
||||
long current_language = m_wxLocale ? m_wxLocale->GetLanguage() : wxLANGUAGE_UNKNOWN;
|
||||
for (auto lang : identifiers){
|
||||
if (lang == current_language)
|
||||
break;
|
||||
++init_selection;
|
||||
}
|
||||
if (init_selection == identifiers.size())
|
||||
init_selection = 0;
|
||||
long index = wxGetSingleChoiceIndex(_(L("Select the language")), _(L("Language")),
|
||||
names, init_selection);
|
||||
if (index != -1)
|
||||
{
|
||||
m_wxLocale = new wxLocale;
|
||||
m_wxLocale->Init(identifiers[index]);
|
||||
m_wxLocale->AddCatalogLookupPathPrefix(localization_dir());
|
||||
m_wxLocale->AddCatalog(GetAppName());
|
||||
wxSetlocale(LC_NUMERIC, "C");
|
||||
Preset::update_suffix_modified();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// load language saved at application config
|
||||
bool GUI_App::load_language()
|
||||
{
|
||||
wxString language = wxEmptyString;
|
||||
if (app_config->has("translation_language"))
|
||||
language = app_config->get("translation_language");
|
||||
|
||||
if (language.IsEmpty())
|
||||
return false;
|
||||
wxArrayString names;
|
||||
wxArrayLong identifiers;
|
||||
get_installed_languages(names, identifiers);
|
||||
for (size_t i = 0; i < identifiers.Count(); i++)
|
||||
{
|
||||
if (wxLocale::GetLanguageCanonicalName(identifiers[i]) == language)
|
||||
{
|
||||
m_wxLocale = new wxLocale;
|
||||
m_wxLocale->Init(identifiers[i]);
|
||||
m_wxLocale->AddCatalogLookupPathPrefix(localization_dir());
|
||||
m_wxLocale->AddCatalog(GetAppName());
|
||||
wxSetlocale(LC_NUMERIC, "C");
|
||||
Preset::update_suffix_modified();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// save language at application config
|
||||
void GUI_App::save_language()
|
||||
{
|
||||
wxString language = wxEmptyString;
|
||||
if (m_wxLocale)
|
||||
language = m_wxLocale->GetCanonicalName();
|
||||
|
||||
app_config->set("translation_language", language.ToStdString());
|
||||
app_config->save();
|
||||
}
|
||||
|
||||
// get list of installed languages
|
||||
void GUI_App::get_installed_languages(wxArrayString & names, wxArrayLong & identifiers)
|
||||
{
|
||||
names.Clear();
|
||||
identifiers.Clear();
|
||||
|
||||
wxDir dir(localization_dir());
|
||||
wxString filename;
|
||||
const wxLanguageInfo * langinfo;
|
||||
wxString name = wxLocale::GetLanguageName(wxLANGUAGE_DEFAULT);
|
||||
if (!name.IsEmpty())
|
||||
{
|
||||
names.Add(_(L("Default")));
|
||||
identifiers.Add(wxLANGUAGE_DEFAULT);
|
||||
}
|
||||
for (bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
|
||||
cont; cont = dir.GetNext(&filename))
|
||||
{
|
||||
langinfo = wxLocale::FindLanguageInfo(filename);
|
||||
if (langinfo != NULL)
|
||||
{
|
||||
auto full_file_name = dir.GetName() + wxFileName::GetPathSeparator() +
|
||||
filename + wxFileName::GetPathSeparator() +
|
||||
GetAppName() + wxT(".mo");
|
||||
if (wxFileExists(full_file_name))
|
||||
{
|
||||
names.Add(langinfo->Description);
|
||||
identifiers.Add(langinfo->Language);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tab* GUI_App::get_tab(Preset::Type type)
|
||||
{
|
||||
for (Tab* tab: tabs_list)
|
||||
if (tab->type() == type)
|
||||
return tab;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ConfigMenuIDs GUI_App::get_view_mode()
|
||||
{
|
||||
if (!app_config->has("view_mode"))
|
||||
return ConfigMenuModeSimple;
|
||||
|
||||
const auto mode = app_config->get("view_mode");
|
||||
return mode == "expert" ? ConfigMenuModeExpert :
|
||||
mode == "simple" ? ConfigMenuModeSimple : ConfigMenuModeAdvanced;
|
||||
}
|
||||
|
||||
// Update view mode according to selected menu
|
||||
void GUI_App::update_mode()
|
||||
{
|
||||
wxWindowUpdateLocker noUpdates(mainframe->m_plater);
|
||||
|
||||
ConfigMenuIDs mode = wxGetApp().get_view_mode();
|
||||
|
||||
obj_list()->get_sizer()->Show(mode == ConfigMenuModeExpert);
|
||||
sidebar().show_info_sizers(mode == ConfigMenuModeExpert);
|
||||
sidebar().show_buttons(mode == ConfigMenuModeExpert);
|
||||
obj_manipul()->show_object_name(mode == ConfigMenuModeSimple);
|
||||
obj_list()->update_manipulation_sizer(mode == ConfigMenuModeSimple);
|
||||
|
||||
sidebar().Layout();
|
||||
mainframe->m_plater->Layout();
|
||||
|
||||
ConfigOptionMode opt_mode = mode == ConfigMenuModeSimple ? comSimple :
|
||||
mode == ConfigMenuModeExpert ? comExpert : comAdvanced;
|
||||
for (auto tab : tabs_list)
|
||||
tab->update_visibility(opt_mode);
|
||||
}
|
||||
|
||||
void GUI_App::add_config_menu(wxMenuBar *menu)
|
||||
{
|
||||
auto local_menu = new wxMenu();
|
||||
wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt);
|
||||
|
||||
const auto config_wizard_name = _(ConfigWizard::name().wx_str());
|
||||
const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), config_wizard_name);
|
||||
// Cmd+, is standard on OS X - what about other operating systems?
|
||||
local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
|
||||
local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots")) + dots, _(L("Inspect / activate configuration snapshots")));
|
||||
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot")));
|
||||
// local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates")));
|
||||
local_menu->AppendSeparator();
|
||||
local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences")) + dots + "\tCtrl+,", _(L("Application preferences")));
|
||||
local_menu->AppendSeparator();
|
||||
auto mode_menu = new wxMenu();
|
||||
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("&Simple")), _(L("Simple View Mode")));
|
||||
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _(L("&Advanced")), _(L("Advanced View Mode")));
|
||||
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("&Expert")), _(L("Expert View Mode")));
|
||||
mode_menu->Check(config_id_base + get_view_mode(), true);
|
||||
local_menu->AppendSubMenu(mode_menu, _(L("&Mode")), _(L("Slic3r View Mode")));
|
||||
local_menu->AppendSeparator();
|
||||
local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language")));
|
||||
local_menu->AppendSeparator();
|
||||
local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer firmware")), _(L("Upload a firmware image into an Arduino based printer")));
|
||||
// TODO: for when we're able to flash dictionaries
|
||||
// local_menu->Append(config_id_base + FirmwareMenuDict, _(L("Flash language file")), _(L("Upload a language dictionary file into a Prusa printer")));
|
||||
|
||||
local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event){
|
||||
switch (event.GetId() - config_id_base) {
|
||||
case ConfigMenuWizard:
|
||||
config_wizard(ConfigWizard::RR_USER);
|
||||
break;
|
||||
case ConfigMenuTakeSnapshot:
|
||||
// Take a configuration snapshot.
|
||||
if (check_unsaved_changes()) {
|
||||
wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name")));
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
app_config->set("on_snapshot",
|
||||
Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
|
||||
*app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
|
||||
}
|
||||
break;
|
||||
case ConfigMenuSnapshots:
|
||||
if (check_unsaved_changes()) {
|
||||
std::string on_snapshot;
|
||||
if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
|
||||
on_snapshot = app_config->get("on_snapshot");
|
||||
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
|
||||
dlg.ShowModal();
|
||||
if (!dlg.snapshot_to_activate().empty()) {
|
||||
if (!Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
|
||||
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
|
||||
app_config->set("on_snapshot",
|
||||
Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
|
||||
preset_bundle->load_presets(*app_config);
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
load_current_presets();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ConfigMenuPreferences:
|
||||
{
|
||||
PreferencesDialog dlg(mainframe);
|
||||
dlg.ShowModal();
|
||||
break;
|
||||
}
|
||||
case ConfigMenuLanguage:
|
||||
{
|
||||
wxArrayString names;
|
||||
wxArrayLong identifiers;
|
||||
get_installed_languages(names, identifiers);
|
||||
if (select_language(names, identifiers)) {
|
||||
save_language();
|
||||
show_info(mainframe->m_tabpanel, _(L("Application will be restarted")), _(L("Attention!")));
|
||||
_3DScene::remove_all_canvases();// remove all canvas before recreate GUI
|
||||
recreate_GUI();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ConfigMenuFlashFirmware:
|
||||
FirmwareDialog::run(mainframe);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
mode_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent& event) {
|
||||
int id_mode = event.GetId() - config_id_base;
|
||||
std::string mode = id_mode == ConfigMenuModeExpert ? "expert" :
|
||||
id_mode == ConfigMenuModeSimple ? "simple" : "advanced";
|
||||
app_config->set("view_mode", mode);
|
||||
app_config->save();
|
||||
update_mode();
|
||||
});
|
||||
menu->Append(local_menu, _(L("&Configuration")));
|
||||
}
|
||||
|
||||
// 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.
|
||||
bool GUI_App::check_unsaved_changes()
|
||||
{
|
||||
std::string dirty;
|
||||
for (Tab *tab : tabs_list)
|
||||
if (tab->current_preset_is_dirty())
|
||||
if (dirty.empty())
|
||||
dirty = tab->name();
|
||||
else
|
||||
dirty += std::string(", ") + tab->name();
|
||||
if (dirty.empty())
|
||||
// No changes, the application may close or reload presets.
|
||||
return true;
|
||||
// Ask the user.
|
||||
auto dialog = new wxMessageDialog(mainframe,
|
||||
_(L("You have unsaved changes ")) + dirty + _(L(". Discard changes and continue anyway?")),
|
||||
_(L("Unsaved Presets")),
|
||||
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
|
||||
return dialog->ShowModal() == wxID_YES;
|
||||
}
|
||||
|
||||
bool GUI_App::checked_tab(Tab* tab)
|
||||
{
|
||||
bool ret = true;
|
||||
if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end())
|
||||
ret = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GUI_App::delete_tab_from_list(Tab* tab)
|
||||
{
|
||||
std::vector<Tab *>::iterator itr = find(tabs_list.begin(), tabs_list.end(), tab);
|
||||
if (itr != tabs_list.end())
|
||||
tabs_list.erase(itr);
|
||||
}
|
||||
|
||||
// Update UI / Tabs to reflect changes in the currently loaded presets
|
||||
void GUI_App::load_current_presets()
|
||||
{
|
||||
for (Tab *tab : tabs_list) {
|
||||
tab->load_current_preset();
|
||||
}
|
||||
}
|
||||
|
||||
Sidebar& GUI_App::sidebar()
|
||||
{
|
||||
return plater_->sidebar();
|
||||
}
|
||||
|
||||
ObjectManipulation* GUI_App::obj_manipul()
|
||||
{
|
||||
return sidebar().obj_manipul();
|
||||
}
|
||||
|
||||
ObjectList* GUI_App::obj_list()
|
||||
{
|
||||
return sidebar().obj_list();
|
||||
}
|
||||
|
||||
Plater* GUI_App::plater()
|
||||
{
|
||||
return plater_;
|
||||
}
|
||||
|
||||
wxGLCanvas* GUI_App::canvas3D()
|
||||
{
|
||||
return plater_->canvas3D();
|
||||
}
|
||||
|
||||
ModelObjectPtrs* GUI_App::model_objects()
|
||||
{
|
||||
return &plater_->model().objects;
|
||||
}
|
||||
|
||||
wxNotebook* GUI_App::tab_panel() const
|
||||
{
|
||||
return mainframe->m_tabpanel;
|
||||
}
|
||||
|
||||
// static method accepting a wxWindow object as first parameter
|
||||
// void warning_catcher{
|
||||
// my($self, $message_dialog) = @_;
|
||||
// return sub{
|
||||
// my $message = shift;
|
||||
// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ;
|
||||
// my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
|
||||
// $message_dialog
|
||||
// ? $message_dialog->(@params)
|
||||
// : Wx::MessageDialog->new($self, @params)->ShowModal;
|
||||
// };
|
||||
// }
|
||||
|
||||
// Do we need this function???
|
||||
// void GUI_App::notify(message){
|
||||
// auto frame = GetTopWindow();
|
||||
// // try harder to attract user attention on OS X
|
||||
// if (!frame->IsActive())
|
||||
// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO);
|
||||
//
|
||||
// // There used to be notifier using a Growl application for OSX, but Growl is dead.
|
||||
// // The notifier also supported the Linux X D - bus notifications, but that support was broken.
|
||||
// //TODO use wxNotificationMessage ?
|
||||
// }
|
||||
|
||||
|
||||
} // GUI
|
||||
} //Slic3r
|
||||
154
src/slic3r/GUI/GUI_App.hpp
Normal file
154
src/slic3r/GUI/GUI_App.hpp
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
#ifndef slic3r_GUI_App_hpp_
|
||||
#define slic3r_GUI_App_hpp_
|
||||
|
||||
#include <string>
|
||||
#include "PrintConfig.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/font.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <stack>
|
||||
|
||||
class wxMenuItem;
|
||||
class wxMenuBar;
|
||||
class wxTopLevelWindow;
|
||||
class wxNotebook;
|
||||
|
||||
namespace Slic3r {
|
||||
class AppConfig;
|
||||
class PresetBundle;
|
||||
class PresetUpdater;
|
||||
class ModelObject;
|
||||
|
||||
namespace GUI
|
||||
{
|
||||
|
||||
enum FileType
|
||||
{
|
||||
FT_STL,
|
||||
FT_OBJ,
|
||||
FT_AMF,
|
||||
FT_3MF,
|
||||
FT_PRUSA,
|
||||
FT_GCODE,
|
||||
FT_MODEL,
|
||||
|
||||
FT_INI,
|
||||
FT_SVG,
|
||||
|
||||
FT_SIZE,
|
||||
};
|
||||
|
||||
extern const wxString file_wildcards[FT_SIZE];
|
||||
|
||||
enum ConfigMenuIDs {
|
||||
ConfigMenuWizard,
|
||||
ConfigMenuSnapshots,
|
||||
ConfigMenuTakeSnapshot,
|
||||
ConfigMenuUpdate,
|
||||
ConfigMenuPreferences,
|
||||
ConfigMenuModeSimple,
|
||||
ConfigMenuModeAdvanced,
|
||||
ConfigMenuModeExpert,
|
||||
ConfigMenuLanguage,
|
||||
ConfigMenuFlashFirmware,
|
||||
ConfigMenuCnt,
|
||||
};
|
||||
|
||||
class Tab;
|
||||
|
||||
static wxString dots("…", wxConvUTF8);
|
||||
|
||||
class GUI_App : public wxApp
|
||||
{
|
||||
bool no_plater{ false };
|
||||
bool app_conf_exists{ false };
|
||||
|
||||
// Lock to guard the callback stack
|
||||
std::mutex callback_register;
|
||||
// callbacks registered to run during idle event.
|
||||
std::stack<std::function<void()>> m_cb{};
|
||||
|
||||
wxColour m_color_label_modified;
|
||||
wxColour m_color_label_sys;
|
||||
wxColour m_color_label_default;
|
||||
|
||||
wxFont m_small_font;
|
||||
wxFont m_bold_font;
|
||||
|
||||
wxLocale* m_wxLocale{ nullptr };
|
||||
|
||||
public:
|
||||
bool OnInit() override;
|
||||
GUI_App() : wxApp() {}
|
||||
|
||||
unsigned get_colour_approx_luma(const wxColour &colour);
|
||||
void init_label_colours();
|
||||
void update_label_colours_from_appconfig();
|
||||
void init_fonts();
|
||||
void set_label_clr_modified(const wxColour& clr);
|
||||
void set_label_clr_sys(const wxColour& clr);
|
||||
|
||||
const wxColour& get_label_clr_modified(){ return m_color_label_modified; }
|
||||
const wxColour& get_label_clr_sys() { return m_color_label_sys; }
|
||||
const wxColour& get_label_clr_default() { return m_color_label_default; }
|
||||
|
||||
const wxFont& small_font() { return m_small_font; }
|
||||
const wxFont& bold_font() { return m_bold_font; }
|
||||
|
||||
void recreate_GUI();
|
||||
void system_info();
|
||||
void open_model(wxWindow *parent, wxArrayString& input_files);
|
||||
static bool catch_error(std::function<void()> cb,
|
||||
// wxMessageDialog* message_dialog,
|
||||
const std::string& err);
|
||||
// void notify(/*message*/);
|
||||
void update_ui_from_settings();
|
||||
void CallAfter(std::function<void()> cb);
|
||||
|
||||
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
|
||||
void window_pos_restore(wxTopLevelWindow* window, const std::string &name);
|
||||
void window_pos_sanitize(wxTopLevelWindow* window);
|
||||
|
||||
bool select_language(wxArrayString & names, wxArrayLong & identifiers);
|
||||
bool load_language();
|
||||
void save_language();
|
||||
void get_installed_languages(wxArrayString & names, wxArrayLong & identifiers);
|
||||
|
||||
Tab* get_tab(Preset::Type type);
|
||||
ConfigMenuIDs get_view_mode();
|
||||
void update_mode();
|
||||
|
||||
void add_config_menu(wxMenuBar *menu);
|
||||
bool check_unsaved_changes();
|
||||
bool checked_tab(Tab* tab);
|
||||
void delete_tab_from_list(Tab* tab);
|
||||
void load_current_presets();
|
||||
|
||||
Sidebar& sidebar();
|
||||
ObjectManipulation* obj_manipul();
|
||||
ObjectList* obj_list();
|
||||
Plater* plater();
|
||||
wxGLCanvas* canvas3D();
|
||||
std::vector<ModelObject*> *model_objects();
|
||||
|
||||
AppConfig* app_config{ nullptr };
|
||||
PresetBundle* preset_bundle{ nullptr };
|
||||
PresetUpdater* preset_updater{ nullptr };
|
||||
MainFrame* mainframe{ nullptr };
|
||||
Plater* plater_{ nullptr };
|
||||
|
||||
wxNotebook* tab_panel() const ;
|
||||
|
||||
std::vector<Tab *> tabs_list;
|
||||
|
||||
};
|
||||
DECLARE_APP(GUI_App)
|
||||
|
||||
} // GUI
|
||||
} //Slic3r
|
||||
|
||||
#endif // slic3r_GUI_App_hpp_
|
||||
1327
src/slic3r/GUI/GUI_ObjectList.cpp
Normal file
1327
src/slic3r/GUI/GUI_ObjectList.cpp
Normal file
File diff suppressed because it is too large
Load diff
146
src/slic3r/GUI/GUI_ObjectList.hpp
Normal file
146
src/slic3r/GUI/GUI_ObjectList.hpp
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#ifndef slic3r_GUI_ObjectList_hpp_
|
||||
#define slic3r_GUI_ObjectList_hpp_
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/dataview.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
class wxBoxSizer;
|
||||
class PrusaObjectDataViewModel;
|
||||
|
||||
namespace Slic3r {
|
||||
class ConfigOptionsGroup;
|
||||
class DynamicPrintConfig;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class ObjectList : public wxDataViewCtrl
|
||||
{
|
||||
wxBoxSizer *m_sizer {nullptr};
|
||||
|
||||
DynamicPrintConfig *m_default_config {nullptr};
|
||||
|
||||
wxBitmap m_bmp_modifiermesh;
|
||||
wxBitmap m_bmp_solidmesh;
|
||||
wxBitmap m_bmp_manifold_warning;
|
||||
wxBitmap m_bmp_cog;
|
||||
wxBitmap m_bmp_split;
|
||||
|
||||
int m_selected_object_id = -1;
|
||||
bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select()
|
||||
// happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler
|
||||
// calls this method again and again and again
|
||||
#ifdef __WXOSX__
|
||||
wxString m_selected_extruder = "";
|
||||
#endif //__WXOSX__
|
||||
bool m_parts_changed = false;
|
||||
bool m_part_settings_changed = false;
|
||||
|
||||
public:
|
||||
ObjectList(wxWindow* parent);
|
||||
~ObjectList();
|
||||
|
||||
|
||||
std::map<std::string, wxBitmap> CATEGORY_ICON;
|
||||
|
||||
PrusaObjectDataViewModel *m_objects_model{ nullptr };
|
||||
DynamicPrintConfig *m_config {nullptr};
|
||||
|
||||
std::vector<ModelObject*> *m_objects{ nullptr };
|
||||
|
||||
|
||||
void create_objects_ctrl();
|
||||
wxDataViewColumn* create_objects_list_extruder_column(int extruders_count);
|
||||
void update_objects_list_extruder_column(int extruders_count);
|
||||
// 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);
|
||||
|
||||
void init_icons();
|
||||
|
||||
void set_tooltip_for_item(const wxPoint& pt);
|
||||
|
||||
void selection_changed();
|
||||
void context_menu();
|
||||
void show_context_menu();
|
||||
void key_event(wxKeyEvent& event);
|
||||
void item_value_change(wxDataViewEvent& event);
|
||||
|
||||
void on_begin_drag(wxDataViewEvent &event);
|
||||
void on_drop_possible(wxDataViewEvent &event);
|
||||
void on_drop(wxDataViewEvent &event);
|
||||
|
||||
void get_settings_choice(wxMenu *menu, int id, bool is_part);
|
||||
void menu_item_add_generic(wxMenuItem* &menu, int id);
|
||||
wxMenuItem* menu_item_split(wxMenu* menu, int id);
|
||||
wxMenuItem* menu_item_settings(wxMenu* menu, int id, const bool is_part);
|
||||
wxMenu* create_add_part_popupmenu();
|
||||
wxMenu* create_part_settings_popupmenu();
|
||||
wxMenu* create_add_settings_popupmenu(bool is_part);
|
||||
|
||||
void load_subobject(bool is_modifier = false, bool is_lambda = false);
|
||||
void load_part(ModelObject* model_object, wxArrayString& part_names, const bool is_modifier);
|
||||
void load_lambda(ModelObject* model_object, wxArrayString& part_names, const bool is_modifier);
|
||||
void load_lambda(const std::string& type_name);
|
||||
void del_subobject_item(wxDataViewItem& item);
|
||||
void del_settings_from_config();
|
||||
void del_instances_from_object(const int obj_idx);
|
||||
bool del_subobject_from_object(const int obj_idx, const int idx, const int type);
|
||||
void split(const bool split_part);
|
||||
bool get_volume_by_item(const bool split_part, const wxDataViewItem& item, ModelVolume*& volume);
|
||||
bool is_splittable_object(const bool split_part);
|
||||
|
||||
wxPoint get_mouse_position_in_control();
|
||||
wxBoxSizer* get_sizer(){return m_sizer;}
|
||||
int get_selected_obj_idx() const;
|
||||
bool is_parts_changed() const { return m_parts_changed; }
|
||||
bool is_part_settings_changed() const{ return m_part_settings_changed; }
|
||||
|
||||
void parts_changed(int obj_idx);
|
||||
void part_selection_changed();
|
||||
|
||||
void update_manipulation_sizer(const bool is_simple_mode);
|
||||
|
||||
// Add object to the list
|
||||
void add_object_to_list(size_t obj_idx);
|
||||
// Delete object from the list
|
||||
void delete_object_from_list();
|
||||
void delete_object_from_list(const size_t obj_idx);
|
||||
void delete_volume_from_list(const size_t obj_idx, const size_t vol_idx);
|
||||
// Delete all objects from the list
|
||||
void delete_all_objects_from_list();
|
||||
// Increase instances count
|
||||
void increase_object_instances(const size_t obj_idx, const size_t num);
|
||||
// Decrease instances count
|
||||
void decrease_object_instances(const size_t obj_idx, const size_t num);
|
||||
|
||||
// #ys_FIXME_to_delete
|
||||
// Unselect all objects in the list on c++ side
|
||||
void unselect_objects();
|
||||
// Select current object in the list on c++ side
|
||||
void select_current_object(int idx);
|
||||
// Select current volume in the list on c++ side
|
||||
void select_current_volume(int idx, int vol_idx);
|
||||
|
||||
// Remove objects/sub-object from the list
|
||||
void remove();
|
||||
|
||||
void init_objects();
|
||||
bool multiple_selection() const ;
|
||||
void update_selections();
|
||||
void update_selections_on_canvas();
|
||||
void select_item(const wxDataViewItem& item);
|
||||
void select_items(const wxDataViewItemArray& sels);
|
||||
void select_all();
|
||||
// correct current selections to avoid of the possible conflicts
|
||||
void fix_multiselection_conflicts();
|
||||
};
|
||||
|
||||
|
||||
}}
|
||||
|
||||
#endif //slic3r_GUI_ObjectList_hpp_
|
||||
468
src/slic3r/GUI/GUI_ObjectManipulation.cpp
Normal file
468
src/slic3r/GUI/GUI_ObjectManipulation.cpp
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
#include "GUI_ObjectManipulation.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Geometry.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
namespace GUI
|
||||
{
|
||||
|
||||
OG_Settings::OG_Settings(wxWindow* parent, const bool staticbox)
|
||||
{
|
||||
wxString title = staticbox ? " " : ""; // temporary workaround - #ys_FIXME
|
||||
m_og = std::make_shared<ConfigOptionsGroup>(parent, title);
|
||||
}
|
||||
|
||||
wxSizer* OG_Settings::get_sizer()
|
||||
{
|
||||
return m_og->sizer;
|
||||
}
|
||||
|
||||
ObjectManipulation::ObjectManipulation(wxWindow* parent):
|
||||
OG_Settings(parent, true)
|
||||
{
|
||||
m_og->set_name(_(L("Object Manipulation")));
|
||||
m_og->label_width = 100;
|
||||
m_og->set_grid_vgap(5);
|
||||
|
||||
m_og->m_on_change = [this](t_config_option_key opt_key, boost::any value){
|
||||
if (opt_key == "scale_unit"){
|
||||
const wxString& selection = boost::any_cast<wxString>(value);
|
||||
std::vector<std::string> axes{ "x", "y", "z" };
|
||||
for (auto axis : axes) {
|
||||
std::string key = "scale_" + axis;
|
||||
m_og->set_side_text(key, selection);
|
||||
}
|
||||
|
||||
m_is_percent_scale = selection == _("%");
|
||||
update_scale_values();
|
||||
}
|
||||
};
|
||||
|
||||
ConfigOptionDef def;
|
||||
|
||||
// Objects(sub-objects) name
|
||||
def.label = L("Name");
|
||||
// def.type = coString;
|
||||
def.gui_type = "legend";
|
||||
def.tooltip = L("Object name");
|
||||
def.full_width = true;
|
||||
def.default_value = new ConfigOptionString{ " " };
|
||||
m_og->append_single_option_line(Option(def, "object_name"));
|
||||
|
||||
// Legend for object modification
|
||||
auto line = Line{ "", "" };
|
||||
def.label = "";
|
||||
def.type = coString;
|
||||
def.width = 55;
|
||||
|
||||
std::vector<std::string> axes{ "x", "y", "z" };
|
||||
for (const auto axis : axes) {
|
||||
const auto label = boost::algorithm::to_upper_copy(axis);
|
||||
def.default_value = new ConfigOptionString{ " " + label };
|
||||
Option option = Option(def, axis + "_axis_legend");
|
||||
line.append_option(option);
|
||||
}
|
||||
m_og->append_line(line);
|
||||
|
||||
|
||||
auto add_og_to_object_settings = [](const std::string& option_name, const std::string& sidetext)
|
||||
{
|
||||
int def_value = 0;
|
||||
Line line = { _(option_name), "" };
|
||||
if (option_name == "Scale") {
|
||||
line.near_label_widget = [](wxWindow* parent) {
|
||||
auto btn = new PrusaLockButton(parent, wxID_ANY);
|
||||
btn->Bind(wxEVT_BUTTON, [btn](wxCommandEvent &event){
|
||||
event.Skip();
|
||||
wxTheApp->CallAfter([btn]() {
|
||||
wxGetApp().obj_manipul()->set_uniform_scaling(btn->IsLocked());
|
||||
});
|
||||
});
|
||||
return btn;
|
||||
};
|
||||
}
|
||||
|
||||
ConfigOptionDef def;
|
||||
def.type = coInt;
|
||||
def.default_value = new ConfigOptionInt(def_value);
|
||||
def.width = 55;
|
||||
|
||||
if (option_name == "Rotation")
|
||||
def.min = -360;
|
||||
|
||||
const std::string lower_name = boost::algorithm::to_lower_copy(option_name);
|
||||
|
||||
std::vector<std::string> axes{ "x", "y", "z" };
|
||||
for (auto axis : axes) {
|
||||
if (axis == "z" && option_name != "Scale")
|
||||
def.sidetext = sidetext;
|
||||
Option option = Option(def, lower_name + "_" + axis);
|
||||
option.opt.full_width = true;
|
||||
line.append_option(option);
|
||||
}
|
||||
|
||||
if (option_name == "Scale")
|
||||
{
|
||||
def.width = 45;
|
||||
def.type = coStrings;
|
||||
def.gui_type = "select_open";
|
||||
def.enum_labels.push_back(L("%"));
|
||||
def.enum_labels.push_back(L("mm"));
|
||||
def.default_value = new ConfigOptionStrings{ "mm" };
|
||||
|
||||
const Option option = Option(def, lower_name + "_unit");
|
||||
line.append_option(option);
|
||||
}
|
||||
|
||||
return line;
|
||||
};
|
||||
|
||||
|
||||
// Settings table
|
||||
m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")));
|
||||
m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"));
|
||||
m_og->append_line(add_og_to_object_settings(L("Scale"), "mm"));
|
||||
|
||||
|
||||
def.label = L("Place on bed");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("Automatic placing of models on printing bed in Y axis");
|
||||
def.gui_type = "";
|
||||
def.sidetext = "";
|
||||
def.default_value = new ConfigOptionBool{ false };
|
||||
m_og->append_single_option_line(Option(def, "place_on_bed"));
|
||||
|
||||
m_settings_list_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_og->sizer->Add(m_settings_list_sizer, 1, wxEXPAND | wxLEFT, 5);
|
||||
|
||||
m_og->disable();
|
||||
}
|
||||
|
||||
int ObjectManipulation::ol_selection()
|
||||
{
|
||||
return wxGetApp().obj_list()->get_selected_obj_idx();
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_settings_list()
|
||||
{
|
||||
#ifdef __WXGTK__
|
||||
auto parent = m_og->get_parent();
|
||||
#else
|
||||
auto parent = m_og->parent();
|
||||
#endif /* __WXGTK__ */
|
||||
|
||||
// There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/Slic3r/issues/898 and https://github.com/prusa3d/Slic3r/issues/952.
|
||||
// The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason,
|
||||
// we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely.
|
||||
#ifdef __linux__
|
||||
std::unique_ptr<wxWindowUpdateLocker> no_updates(new wxWindowUpdateLocker(parent));
|
||||
#else
|
||||
wxWindowUpdateLocker noUpdates(parent);
|
||||
#endif
|
||||
|
||||
m_settings_list_sizer->Clear(true);
|
||||
bool show_manipulations = true;
|
||||
|
||||
auto objects_ctrl = wxGetApp().obj_list();
|
||||
auto objects_model = wxGetApp().obj_list()->m_objects_model;
|
||||
auto config = wxGetApp().obj_list()->m_config;
|
||||
|
||||
const auto item = objects_ctrl->GetSelection();
|
||||
if (!objects_ctrl->multiple_selection() &&
|
||||
config && objects_model->IsSettingsItem(item))
|
||||
{
|
||||
auto extra_column = [config](wxWindow* parent, const Line& line)
|
||||
{
|
||||
auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line
|
||||
|
||||
auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
#ifdef __WXMSW__
|
||||
btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif // __WXMSW__
|
||||
btn->Bind(wxEVT_BUTTON, [opt_key, config](wxEvent &event){
|
||||
config->erase(opt_key);
|
||||
wxTheApp->CallAfter([]() { wxGetApp().obj_manipul()->update_settings_list(); });
|
||||
});
|
||||
return btn;
|
||||
};
|
||||
|
||||
std::map<std::string, std::vector<std::string>> cat_options;
|
||||
auto opt_keys = config->keys();
|
||||
m_og_settings.resize(0);
|
||||
std::vector<std::string> categories;
|
||||
if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return;
|
||||
{
|
||||
auto extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 :
|
||||
wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
|
||||
|
||||
for (auto& opt_key : opt_keys) {
|
||||
auto category = config->def()->get(opt_key)->category;
|
||||
if (category.empty() ||
|
||||
(category == "Extruders" && extruders_cnt == 1)) continue;
|
||||
|
||||
std::vector< std::string > new_category;
|
||||
|
||||
auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category);
|
||||
cat_opt.push_back(opt_key);
|
||||
if (cat_opt.size() == 1)
|
||||
cat_options[category] = cat_opt;
|
||||
}
|
||||
|
||||
for (auto& cat : cat_options) {
|
||||
if (cat.second.size() == 1 && cat.second[0] == "extruder")
|
||||
continue;
|
||||
|
||||
auto optgroup = std::make_shared<ConfigOptionsGroup>(parent, cat.first, config, false, extra_column);
|
||||
optgroup->label_width = 150;
|
||||
optgroup->sidetext_width = 70;
|
||||
|
||||
for (auto& opt : cat.second)
|
||||
{
|
||||
if (opt == "extruder")
|
||||
continue;
|
||||
Option option = optgroup->get_option(opt);
|
||||
option.opt.width = 70;
|
||||
optgroup->append_single_option_line(option);
|
||||
}
|
||||
optgroup->reload_config();
|
||||
m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
|
||||
m_og_settings.push_back(optgroup);
|
||||
|
||||
categories.push_back(cat.first);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_og_settings.empty()) {
|
||||
objects_ctrl->Select(objects_model->Delete(item));
|
||||
wxGetApp().obj_list()->part_selection_changed();
|
||||
}
|
||||
else {
|
||||
if (!categories.empty())
|
||||
objects_model->UpdateSettingsDigest(item, categories);
|
||||
show_manipulations = false;
|
||||
}
|
||||
}
|
||||
|
||||
show_manipulation_og(show_manipulations);
|
||||
wxGetApp().sidebar().show_info_sizers(show_manipulations && item && objects_model->GetParent(item) == wxDataViewItem(0));
|
||||
|
||||
#ifdef __linux__
|
||||
no_updates.reset(nullptr);
|
||||
#endif
|
||||
|
||||
parent->Layout();
|
||||
parent->GetParent()->Layout();
|
||||
}
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection)
|
||||
{
|
||||
if (selection.is_single_full_object())
|
||||
{
|
||||
if (wxGetApp().mainframe->m_plater->model().objects[selection.get_object_idx()]->instances.size() == 1)
|
||||
{
|
||||
// all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
update_position_value(volume->get_offset());
|
||||
update_rotation_value(volume->get_rotation());
|
||||
update_scale_value(volume->get_scaling_factor());
|
||||
m_og->enable();
|
||||
}
|
||||
else
|
||||
reset_settings_value();
|
||||
}
|
||||
else if (selection.is_single_full_instance())
|
||||
{
|
||||
// all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
update_position_value(volume->get_offset());
|
||||
update_rotation_value(volume->get_rotation());
|
||||
update_scale_value(volume->get_scaling_factor());
|
||||
m_og->enable();
|
||||
}
|
||||
else if (selection.is_wipe_tower())
|
||||
{
|
||||
// the selection contains a single volume
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
update_position_value(volume->get_offset());
|
||||
update_rotation_value(volume->get_rotation());
|
||||
update_scale_value(volume->get_scaling_factor());
|
||||
m_og->enable();
|
||||
}
|
||||
else if (selection.is_modifier())
|
||||
{
|
||||
// the selection contains a single volume
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
update_position_value(volume->get_offset());
|
||||
update_rotation_value(volume->get_rotation());
|
||||
update_scale_value(volume->get_scaling_factor());
|
||||
m_og->enable();
|
||||
}
|
||||
else
|
||||
reset_settings_value();
|
||||
}
|
||||
|
||||
void ObjectManipulation::reset_settings_value()
|
||||
{
|
||||
reset_position_value();
|
||||
reset_rotation_value();
|
||||
reset_scale_value();
|
||||
m_og->disable();
|
||||
}
|
||||
|
||||
void ObjectManipulation::reset_position_value()
|
||||
{
|
||||
m_og->set_value("position_x", 0);
|
||||
m_og->set_value("position_y", 0);
|
||||
m_og->set_value("position_z", 0);
|
||||
}
|
||||
|
||||
void ObjectManipulation::reset_rotation_value()
|
||||
{
|
||||
m_og->set_value("rotation_x", 0);
|
||||
m_og->set_value("rotation_y", 0);
|
||||
m_og->set_value("rotation_z", 0);
|
||||
}
|
||||
|
||||
void ObjectManipulation::reset_scale_value()
|
||||
{
|
||||
m_is_percent_scale = true;
|
||||
m_og->set_value("scale_unit", _("%"));
|
||||
m_og->set_value("scale_x", 100);
|
||||
m_og->set_value("scale_y", 100);
|
||||
m_og->set_value("scale_z", 100);
|
||||
}
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
void ObjectManipulation::update_values()
|
||||
{
|
||||
int selection = ol_selection();
|
||||
if (selection < 0 || wxGetApp().mainframe->m_plater->model().objects.size() <= selection) {
|
||||
m_og->set_value("position_x", 0);
|
||||
m_og->set_value("position_y", 0);
|
||||
m_og->set_value("position_z", 0);
|
||||
m_og->set_value("scale_x", 0);
|
||||
m_og->set_value("scale_y", 0);
|
||||
m_og->set_value("scale_z", 0);
|
||||
m_og->set_value("rotation_x", 0);
|
||||
m_og->set_value("rotation_y", 0);
|
||||
m_og->set_value("rotation_z", 0);
|
||||
m_og->disable();
|
||||
return;
|
||||
}
|
||||
m_is_percent_scale = boost::any_cast<wxString>(m_og->get_value("scale_unit")) == _("%");
|
||||
|
||||
update_position_values();
|
||||
update_scale_values();
|
||||
update_rotation_values();
|
||||
m_og->enable();
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_scale_values()
|
||||
{
|
||||
int selection = ol_selection();
|
||||
ModelObjectPtrs& objects = wxGetApp().mainframe->m_plater->model().objects;
|
||||
|
||||
auto instance = objects[selection]->instances.front();
|
||||
auto size = objects[selection]->instance_bounding_box(0).size();
|
||||
|
||||
if (m_is_percent_scale) {
|
||||
m_og->set_value("scale_x", int(instance->get_scaling_factor(X) * 100));
|
||||
m_og->set_value("scale_y", int(instance->get_scaling_factor(Y) * 100));
|
||||
m_og->set_value("scale_z", int(instance->get_scaling_factor(Z) * 100));
|
||||
}
|
||||
else {
|
||||
m_og->set_value("scale_x", int(instance->get_scaling_factor(X) * size(0) + 0.5));
|
||||
m_og->set_value("scale_y", int(instance->get_scaling_factor(Y) * size(1) + 0.5));
|
||||
m_og->set_value("scale_z", int(instance->get_scaling_factor(Z) * size(2) + 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_position_values()
|
||||
{
|
||||
auto instance = wxGetApp().mainframe->m_plater->model().objects[ol_selection()]->instances.front();
|
||||
|
||||
m_og->set_value("position_x", int(instance->get_offset(X)));
|
||||
m_og->set_value("position_y", int(instance->get_offset(Y)));
|
||||
m_og->set_value("position_z", int(instance->get_offset(Z)));
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_position_value(const Vec3d& position)
|
||||
{
|
||||
m_og->set_value("position_x", int(position(0)));
|
||||
m_og->set_value("position_y", int(position(1)));
|
||||
m_og->set_value("position_z", int(position(2)));
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_scale_value(const Vec3d& scaling_factor)
|
||||
{
|
||||
// this is temporary
|
||||
// to be able to update the values as size
|
||||
// we need to store somewhere the original size
|
||||
// or have it passed as parameter
|
||||
if (!m_is_percent_scale)
|
||||
m_og->set_value("scale_unit", _("%"));
|
||||
|
||||
auto scale = scaling_factor * 100.0;
|
||||
m_og->set_value("scale_x", int(scale(0)));
|
||||
m_og->set_value("scale_y", int(scale(1)));
|
||||
m_og->set_value("scale_z", int(scale(2)));
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_rotation_values()
|
||||
{
|
||||
update_rotation_value(wxGetApp().mainframe->m_plater->model().objects[ol_selection()]->instances.front()->get_rotation());
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_rotation_value(double angle, Axis axis)
|
||||
{
|
||||
std::string axis_str;
|
||||
switch (axis) {
|
||||
case X: {
|
||||
axis_str = "rotation_x";
|
||||
break; }
|
||||
case Y: {
|
||||
axis_str = "rotation_y";
|
||||
break; }
|
||||
case Z: {
|
||||
axis_str = "rotation_z";
|
||||
break; }
|
||||
}
|
||||
|
||||
m_og->set_value(axis_str, round_nearest(int(Geometry::rad2deg(angle)), 0));
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_rotation_value(const Vec3d& rotation)
|
||||
{
|
||||
m_og->set_value("rotation_x", int(round_nearest(Geometry::rad2deg(rotation(0)), 0)));
|
||||
m_og->set_value("rotation_y", int(round_nearest(Geometry::rad2deg(rotation(1)), 0)));
|
||||
m_og->set_value("rotation_z", int(round_nearest(Geometry::rad2deg(rotation(2)), 0)));
|
||||
}
|
||||
|
||||
void ObjectManipulation::show_object_name(bool show)
|
||||
{
|
||||
wxGridSizer* grid_sizer = m_og->get_grid_sizer();
|
||||
grid_sizer->Show(static_cast<size_t>(0), show);
|
||||
grid_sizer->Show(static_cast<size_t>(1), show);
|
||||
}
|
||||
|
||||
void ObjectManipulation::show_manipulation_og(const bool show)
|
||||
{
|
||||
wxGridSizer* grid_sizer = m_og->get_grid_sizer();
|
||||
if (show == grid_sizer->IsShown(2))
|
||||
return;
|
||||
for (size_t id = 2; id < 12; id++)
|
||||
grid_sizer->Show(id, show);
|
||||
}
|
||||
|
||||
} //namespace GUI
|
||||
} //namespace Slic3r
|
||||
79
src/slic3r/GUI/GUI_ObjectManipulation.hpp
Normal file
79
src/slic3r/GUI/GUI_ObjectManipulation.hpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#ifndef slic3r_GUI_ObjectManipulation_hpp_
|
||||
#define slic3r_GUI_ObjectManipulation_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/panel.h>
|
||||
|
||||
#include "Preset.hpp"
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
#include "GLCanvas3D.hpp"
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
class wxBoxSizer;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
class ConfigOptionsGroup;
|
||||
|
||||
|
||||
class OG_Settings
|
||||
{
|
||||
protected:
|
||||
std::shared_ptr<ConfigOptionsGroup> m_og;
|
||||
public:
|
||||
OG_Settings(wxWindow* parent, const bool staticbox);
|
||||
~OG_Settings() {}
|
||||
|
||||
wxSizer* get_sizer();
|
||||
ConfigOptionsGroup* get_og() { return m_og.get(); }
|
||||
};
|
||||
|
||||
|
||||
class ObjectManipulation : public OG_Settings
|
||||
{
|
||||
bool m_is_percent_scale = false; // true -> percentage scale unit
|
||||
// false -> uniform scale unit
|
||||
bool m_is_uniform_scale = false; // It indicates if scale is uniform
|
||||
// sizer for extra Object/Part's settings
|
||||
wxBoxSizer* m_settings_list_sizer{ nullptr };
|
||||
// option groups for settings
|
||||
std::vector <std::shared_ptr<ConfigOptionsGroup>> m_og_settings;
|
||||
|
||||
public:
|
||||
ObjectManipulation(wxWindow* parent);
|
||||
~ObjectManipulation() {}
|
||||
|
||||
int ol_selection();
|
||||
void update_settings_list();
|
||||
|
||||
#if ENABLE_EXTENDED_SELECTION
|
||||
void update_settings_value(const GLCanvas3D::Selection& selection);
|
||||
void reset_settings_value();
|
||||
void reset_position_value();
|
||||
void reset_rotation_value();
|
||||
void reset_scale_value();
|
||||
#endif // ENABLE_EXTENDED_SELECTION
|
||||
|
||||
void update_values();
|
||||
// update position values displacements or "gizmos"
|
||||
void update_position_values();
|
||||
void update_position_value(const Vec3d& position);
|
||||
// update scale values after scale unit changing or "gizmos"
|
||||
void update_scale_values();
|
||||
void update_scale_value(const Vec3d& 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 update_rotation_value(const Vec3d& rotation);
|
||||
|
||||
void set_uniform_scaling(const bool uniform_scale) { m_is_uniform_scale = uniform_scale; }
|
||||
|
||||
void show_object_name(bool show);
|
||||
void show_manipulation_og(const bool show);
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif // slic3r_GUI_ObjectManipulation_hpp_
|
||||
572
src/slic3r/GUI/GUI_Preview.cpp
Normal file
572
src/slic3r/GUI/GUI_Preview.cpp
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "GUI_Preview.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "GLCanvas3DManager.hpp"
|
||||
#include "../../libslic3r/GCode/PreviewData.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/combo.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
// this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421
|
||||
#include "../../libslic3r/Print.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data)
|
||||
: m_canvas(nullptr)
|
||||
, m_double_slider_sizer(nullptr)
|
||||
, m_label_view_type(nullptr)
|
||||
, m_choice_view_type(nullptr)
|
||||
, m_label_show_features(nullptr)
|
||||
, m_combochecklist_features(nullptr)
|
||||
, m_checkbox_travel(nullptr)
|
||||
, m_checkbox_retractions(nullptr)
|
||||
, m_checkbox_unretractions(nullptr)
|
||||
, m_checkbox_shells(nullptr)
|
||||
, m_config(config)
|
||||
, m_print(print)
|
||||
, m_gcode_preview_data(gcode_preview_data)
|
||||
, m_number_extruders(1)
|
||||
, m_preferred_color_mode("feature")
|
||||
, m_loaded(false)
|
||||
, m_enabled(false)
|
||||
, m_force_sliders_full_range(false)
|
||||
{
|
||||
if (init(notebook, config, print, gcode_preview_data))
|
||||
{
|
||||
notebook->AddPage(this, _(L("Preview")));
|
||||
show_hide_ui_elements("none");
|
||||
load_print();
|
||||
}
|
||||
}
|
||||
|
||||
bool Preview::init(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data)
|
||||
{
|
||||
if ((notebook == nullptr) || (config == nullptr) || (print == nullptr) || (gcode_preview_data == nullptr))
|
||||
return false;
|
||||
|
||||
// creates this panel add append it to the given notebook as a new page
|
||||
if (!Create(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize))
|
||||
return false;
|
||||
|
||||
m_canvas = GLCanvas3DManager::create_wxglcanvas(this);
|
||||
|
||||
_3DScene::add_canvas(m_canvas);
|
||||
_3DScene::allow_multisample(m_canvas, GLCanvas3DManager::can_multisample());
|
||||
_3DScene::enable_shader(m_canvas, true);
|
||||
_3DScene::set_config(m_canvas, m_config);
|
||||
_3DScene::set_print(m_canvas, m_print);
|
||||
_3DScene::enable_legend_texture(m_canvas, true);
|
||||
_3DScene::enable_dynamic_background(m_canvas, true);
|
||||
|
||||
m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
create_double_slider();
|
||||
|
||||
m_label_view_type = new wxStaticText(this, wxID_ANY, _(L("View")));
|
||||
|
||||
m_choice_view_type = new wxChoice(this, wxID_ANY);
|
||||
m_choice_view_type->Append(_(L("Feature type")));
|
||||
m_choice_view_type->Append(_(L("Height")));
|
||||
m_choice_view_type->Append(_(L("Width")));
|
||||
m_choice_view_type->Append(_(L("Speed")));
|
||||
m_choice_view_type->Append(_(L("Volumetric flow rate")));
|
||||
m_choice_view_type->Append(_(L("Tool")));
|
||||
m_choice_view_type->SetSelection(0);
|
||||
|
||||
m_label_show_features = new wxStaticText(this, wxID_ANY, _(L("Show")));
|
||||
|
||||
m_combochecklist_features = new wxComboCtrl();
|
||||
m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(200, -1), wxCB_READONLY);
|
||||
std::string feature_text = GUI::into_u8(_(L("Feature types")));
|
||||
std::string feature_items = GUI::into_u8(
|
||||
_(L("Perimeter")) + "|" +
|
||||
_(L("External perimeter")) + "|" +
|
||||
_(L("Overhang perimeter")) + "|" +
|
||||
_(L("Internal infill")) + "|" +
|
||||
_(L("Solid infill")) + "|" +
|
||||
_(L("Top solid infill")) + "|" +
|
||||
_(L("Bridge infill")) + "|" +
|
||||
_(L("Gap fill")) + "|" +
|
||||
_(L("Skirt")) + "|" +
|
||||
_(L("Support material")) + "|" +
|
||||
_(L("Support material interface")) + "|" +
|
||||
_(L("Wipe tower")) + "|" +
|
||||
_(L("Custom"))
|
||||
);
|
||||
Slic3r::GUI::create_combochecklist(m_combochecklist_features, feature_text, feature_items, true);
|
||||
|
||||
m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel")));
|
||||
m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions")));
|
||||
m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions")));
|
||||
m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells")));
|
||||
|
||||
wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
top_sizer->Add(m_canvas, 1, wxALL | wxEXPAND, 0);
|
||||
top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0);
|
||||
|
||||
wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5);
|
||||
bottom_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5);
|
||||
bottom_sizer->AddSpacer(10);
|
||||
bottom_sizer->Add(m_label_show_features, 0, wxALIGN_CENTER_VERTICAL, 5);
|
||||
bottom_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5);
|
||||
bottom_sizer->AddSpacer(20);
|
||||
bottom_sizer->Add(m_checkbox_travel, 0, wxEXPAND | wxALL, 5);
|
||||
bottom_sizer->AddSpacer(10);
|
||||
bottom_sizer->Add(m_checkbox_retractions, 0, wxEXPAND | wxALL, 5);
|
||||
bottom_sizer->AddSpacer(10);
|
||||
bottom_sizer->Add(m_checkbox_unretractions, 0, wxEXPAND | wxALL, 5);
|
||||
bottom_sizer->AddSpacer(10);
|
||||
bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0);
|
||||
main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetMinSize(GetSize());
|
||||
GetSizer()->SetSizeHints(this);
|
||||
|
||||
bind_event_handlers();
|
||||
|
||||
// sets colors for gcode preview extrusion roles
|
||||
std::vector<std::string> extrusion_roles_colors = {
|
||||
"Perimeter", "FFFF66",
|
||||
"External perimeter", "FFA500",
|
||||
"Overhang perimeter", "0000FF",
|
||||
"Internal infill", "B1302A",
|
||||
"Solid infill", "D732D7",
|
||||
"Top solid infill", "FF1A1A",
|
||||
"Bridge infill", "9999FF",
|
||||
"Gap fill", "FFFFFF",
|
||||
"Skirt", "845321",
|
||||
"Support material", "00FF00",
|
||||
"Support material interface", "008000",
|
||||
"Wipe tower", "B3E3AB",
|
||||
"Custom", "28CC94"
|
||||
};
|
||||
m_gcode_preview_data->set_extrusion_paths_colors(extrusion_roles_colors);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Preview::~Preview()
|
||||
{
|
||||
unbind_event_handlers();
|
||||
|
||||
if (m_canvas != nullptr)
|
||||
{
|
||||
_3DScene::remove_canvas(m_canvas);
|
||||
delete m_canvas;
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::set_number_extruders(unsigned int number_extruders)
|
||||
{
|
||||
if (m_number_extruders != number_extruders)
|
||||
{
|
||||
m_number_extruders = number_extruders;
|
||||
int type = 0; // color by a feature type
|
||||
if (number_extruders > 1)
|
||||
{
|
||||
int tool_idx = m_choice_view_type->FindString(_(L("Tool")));
|
||||
int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type
|
||||
m_choice_view_type->SetSelection(type);
|
||||
if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types))
|
||||
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
|
||||
|
||||
m_preferred_color_mode = (type == tool_idx) ? "tool_or_feature" : "feature";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::reset_gcode_preview_data()
|
||||
{
|
||||
m_gcode_preview_data->reset();
|
||||
if (m_canvas != nullptr)
|
||||
_3DScene::reset_legend_texture(m_canvas);
|
||||
}
|
||||
|
||||
void Preview::set_canvas_as_dirty()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
_3DScene::set_as_dirty(m_canvas);
|
||||
}
|
||||
|
||||
void Preview::set_enabled(bool enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
void Preview::set_bed_shape(const Pointfs& shape)
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
_3DScene::set_bed_shape(m_canvas, shape);
|
||||
}
|
||||
|
||||
void Preview::select_view(const std::string& direction)
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
_3DScene::select_view(m_canvas, direction);
|
||||
}
|
||||
|
||||
void Preview::set_viewport_from_scene(wxGLCanvas* canvas)
|
||||
{
|
||||
if ((m_canvas != nullptr) && (canvas != nullptr))
|
||||
_3DScene::set_viewport_from_scene(m_canvas, canvas);
|
||||
}
|
||||
|
||||
void Preview::set_viewport_into_scene(wxGLCanvas* canvas)
|
||||
{
|
||||
if ((m_canvas != nullptr) && (canvas != nullptr))
|
||||
_3DScene::set_viewport_from_scene(canvas, m_canvas);
|
||||
}
|
||||
|
||||
void Preview::set_drop_target(wxDropTarget* target)
|
||||
{
|
||||
if (target != nullptr)
|
||||
SetDropTarget(target);
|
||||
}
|
||||
|
||||
void Preview::load_print()
|
||||
{
|
||||
if (m_loaded)
|
||||
return;
|
||||
|
||||
// we require that there's at least one object and the posSlice step
|
||||
// is performed on all of them(this ensures that _shifted_copies was
|
||||
// populated and we know the number of layers)
|
||||
unsigned int n_layers = 0;
|
||||
if (m_print->is_step_done(posSlice))
|
||||
{
|
||||
std::set<float> zs;
|
||||
for (const PrintObject* print_object : m_print->objects())
|
||||
{
|
||||
const LayerPtrs& layers = print_object->layers();
|
||||
const SupportLayerPtrs& support_layers = print_object->support_layers();
|
||||
for (const Layer* layer : layers)
|
||||
{
|
||||
zs.insert(layer->print_z);
|
||||
}
|
||||
for (const SupportLayer* layer : support_layers)
|
||||
{
|
||||
zs.insert(layer->print_z);
|
||||
}
|
||||
}
|
||||
|
||||
n_layers = (unsigned int)zs.size();
|
||||
}
|
||||
|
||||
if (n_layers == 0)
|
||||
{
|
||||
reset_sliders();
|
||||
if (m_canvas != nullptr)
|
||||
{
|
||||
_3DScene::reset_legend_texture(m_canvas);
|
||||
m_canvas->Refresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_preferred_color_mode == "tool_or_feature")
|
||||
{
|
||||
// It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature.
|
||||
// Color by feature if it is a single extruder print.
|
||||
unsigned int number_extruders = (unsigned int)m_print->extruders().size();
|
||||
int tool_idx = m_choice_view_type->FindString(_(L("Tool")));
|
||||
int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type
|
||||
m_choice_view_type->SetSelection(type);
|
||||
if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types))
|
||||
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
|
||||
// If the->SetSelection changed the following line, revert it to "decide yourself".
|
||||
m_preferred_color_mode = "tool_or_feature";
|
||||
}
|
||||
|
||||
// Collect colors per extruder.
|
||||
std::vector<std::string> colors;
|
||||
if (!m_gcode_preview_data->empty() || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool))
|
||||
{
|
||||
const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("extruder_colour"));
|
||||
const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("filament_colour"));
|
||||
unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size());
|
||||
|
||||
unsigned char rgb[3];
|
||||
for (unsigned int i = 0; i < colors_count; ++i)
|
||||
{
|
||||
std::string color = m_config->opt_string("extruder_colour", i);
|
||||
if (!PresetBundle::parse_color(color, rgb))
|
||||
{
|
||||
color = m_config->opt_string("filament_colour", i);
|
||||
if (!PresetBundle::parse_color(color, rgb))
|
||||
color = "#FFFFFF";
|
||||
}
|
||||
|
||||
colors.push_back(color);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsShown() && (m_canvas != nullptr))
|
||||
{
|
||||
// used to set the sliders to the extremes of the current zs range
|
||||
m_force_sliders_full_range = false;
|
||||
|
||||
if (m_gcode_preview_data->empty())
|
||||
{
|
||||
// load skirt and brim
|
||||
_3DScene::load_preview(m_canvas, colors);
|
||||
show_hide_ui_elements("simple");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_force_sliders_full_range = (_3DScene::get_volumes_count(m_canvas) == 0);
|
||||
_3DScene::load_gcode_preview(m_canvas, m_gcode_preview_data, colors);
|
||||
show_hide_ui_elements("full");
|
||||
|
||||
// recalculates zs and update sliders accordingly
|
||||
n_layers = (unsigned int)_3DScene::get_current_print_zs(m_canvas, true).size();
|
||||
if (n_layers == 0)
|
||||
{
|
||||
// all layers filtered out
|
||||
reset_sliders();
|
||||
m_canvas->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
if (n_layers > 0)
|
||||
update_sliders();
|
||||
|
||||
m_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::reload_print(bool force)
|
||||
{
|
||||
_3DScene::reset_volumes(m_canvas);
|
||||
m_loaded = false;
|
||||
|
||||
if (!IsShown() && !force)
|
||||
return;
|
||||
|
||||
load_print();
|
||||
}
|
||||
|
||||
void Preview::refresh_print()
|
||||
{
|
||||
m_loaded = false;
|
||||
|
||||
if (!IsShown())
|
||||
return;
|
||||
|
||||
load_print();
|
||||
}
|
||||
|
||||
void Preview::bind_event_handlers()
|
||||
{
|
||||
this->Bind(wxEVT_SIZE, &Preview::on_size, this);
|
||||
m_choice_view_type->Bind(wxEVT_CHOICE, &Preview::on_choice_view_type, this);
|
||||
m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
|
||||
m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
|
||||
m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
|
||||
m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this);
|
||||
m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this);
|
||||
}
|
||||
|
||||
void Preview::unbind_event_handlers()
|
||||
{
|
||||
this->Unbind(wxEVT_SIZE, &Preview::on_size, this);
|
||||
m_choice_view_type->Unbind(wxEVT_CHOICE, &Preview::on_choice_view_type, this);
|
||||
m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
|
||||
m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
|
||||
m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
|
||||
m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this);
|
||||
m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this);
|
||||
}
|
||||
|
||||
void Preview::show_hide_ui_elements(const std::string& what)
|
||||
{
|
||||
bool enable = (what == "full");
|
||||
m_label_show_features->Enable(enable);
|
||||
m_combochecklist_features->Enable(enable);
|
||||
m_checkbox_travel->Enable(enable);
|
||||
m_checkbox_retractions->Enable(enable);
|
||||
m_checkbox_unretractions->Enable(enable);
|
||||
m_checkbox_shells->Enable(enable);
|
||||
|
||||
enable = (what != "none");
|
||||
m_label_view_type->Enable(enable);
|
||||
m_choice_view_type->Enable(enable);
|
||||
}
|
||||
|
||||
void Preview::reset_sliders()
|
||||
{
|
||||
m_enabled = false;
|
||||
reset_double_slider();
|
||||
m_double_slider_sizer->Hide((size_t)0);
|
||||
}
|
||||
|
||||
void Preview::update_sliders()
|
||||
{
|
||||
m_enabled = true;
|
||||
update_double_slider(m_force_sliders_full_range);
|
||||
m_double_slider_sizer->Show((size_t)0);
|
||||
Layout();
|
||||
}
|
||||
|
||||
void Preview::on_size(wxSizeEvent& evt)
|
||||
{
|
||||
evt.Skip();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void Preview::on_choice_view_type(wxCommandEvent& evt)
|
||||
{
|
||||
m_preferred_color_mode = (m_choice_view_type->GetStringSelection() == L("Tool")) ? "tool" : "feature";
|
||||
int selection = m_choice_view_type->GetCurrentSelection();
|
||||
if ((0 <= selection) && (selection < (int)GCodePreviewData::Extrusion::Num_View_Types))
|
||||
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)selection;
|
||||
|
||||
reload_print();
|
||||
}
|
||||
|
||||
void Preview::on_combochecklist_features(wxCommandEvent& evt)
|
||||
{
|
||||
int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features);
|
||||
m_gcode_preview_data->extrusion.role_flags = (unsigned int)flags;
|
||||
refresh_print();
|
||||
}
|
||||
|
||||
void Preview::on_checkbox_travel(wxCommandEvent& evt)
|
||||
{
|
||||
m_gcode_preview_data->travel.is_visible = m_checkbox_travel->IsChecked();
|
||||
refresh_print();
|
||||
}
|
||||
|
||||
void Preview::on_checkbox_retractions(wxCommandEvent& evt)
|
||||
{
|
||||
m_gcode_preview_data->retraction.is_visible = m_checkbox_retractions->IsChecked();
|
||||
refresh_print();
|
||||
}
|
||||
|
||||
void Preview::on_checkbox_unretractions(wxCommandEvent& evt)
|
||||
{
|
||||
m_gcode_preview_data->unretraction.is_visible = m_checkbox_unretractions->IsChecked();
|
||||
refresh_print();
|
||||
}
|
||||
|
||||
void Preview::on_checkbox_shells(wxCommandEvent& evt)
|
||||
{
|
||||
m_gcode_preview_data->shell.is_visible = m_checkbox_shells->IsChecked();
|
||||
refresh_print();
|
||||
}
|
||||
|
||||
void Preview::create_double_slider()
|
||||
{
|
||||
m_slider = new PrusaDoubleSlider(this, wxID_ANY, 0, 0, 0, 100);
|
||||
m_double_slider_sizer->Add(m_slider, 0, wxEXPAND, 0);
|
||||
|
||||
// sizer, m_canvas
|
||||
m_canvas->Bind(wxEVT_KEY_DOWN, &Preview::update_double_slider_from_canvas, this);
|
||||
|
||||
m_slider->Bind(wxEVT_SCROLL_CHANGED, [this](wxEvent& event) {
|
||||
_3DScene::set_toolpaths_range(m_canvas, m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6);
|
||||
if (IsShown())
|
||||
m_canvas->Refresh();
|
||||
});
|
||||
}
|
||||
|
||||
void Preview::update_double_slider(bool force_sliders_full_range)
|
||||
{
|
||||
std::vector<std::pair<int, double>> values;
|
||||
std::vector<double> layers_z = _3DScene::get_current_print_zs(m_canvas, true);
|
||||
fill_slider_values(values, layers_z);
|
||||
|
||||
const double z_low = m_slider->GetLowerValueD();
|
||||
const double z_high = m_slider->GetHigherValueD();
|
||||
m_slider->SetMaxValue(layers_z.size() - 1);
|
||||
m_slider->SetSliderValues(values);
|
||||
|
||||
set_double_slider_thumbs(force_sliders_full_range, layers_z, z_low, z_high);
|
||||
}
|
||||
|
||||
void Preview::fill_slider_values(std::vector<std::pair<int, double>> &values,
|
||||
const std::vector<double> &layers_z)
|
||||
{
|
||||
std::vector<double> layers_all_z = _3DScene::get_current_print_zs(m_canvas, false);
|
||||
if (layers_all_z.size() == layers_z.size())
|
||||
for (int i = 0; i < layers_z.size(); i++)
|
||||
values.push_back(std::pair<int, double>(i + 1, layers_z[i]));
|
||||
else if (layers_all_z.size() > layers_z.size()) {
|
||||
int cur_id = 0;
|
||||
for (int i = 0; i < layers_z.size(); i++)
|
||||
for (int j = cur_id; j < layers_all_z.size(); j++)
|
||||
if (layers_z[i] - 1e-6 < layers_all_z[j] && layers_all_z[j] < layers_z[i] + 1e-6) {
|
||||
values.push_back(std::pair<int, double>(j + 1, layers_z[i]));
|
||||
cur_id = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::set_double_slider_thumbs(const bool force_sliders_full_range,
|
||||
const std::vector<double> &layers_z,
|
||||
const double z_low,
|
||||
const double z_high)
|
||||
{
|
||||
// Force slider full range only when slider is created.
|
||||
// Support selected diapason on the all next steps
|
||||
if (/*force_sliders_full_range*/z_high == 0.0) {
|
||||
m_slider->SetLowerValue(0);
|
||||
m_slider->SetHigherValue(layers_z.size() - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = layers_z.size() - 1; i >= 0; i--)
|
||||
if (z_low >= layers_z[i]) {
|
||||
m_slider->SetLowerValue(i);
|
||||
break;
|
||||
}
|
||||
for (int i = layers_z.size() - 1; i >= 0; i--)
|
||||
if (z_high >= layers_z[i]) {
|
||||
m_slider->SetHigherValue(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::reset_double_slider()
|
||||
{
|
||||
m_slider->SetHigherValue(0);
|
||||
m_slider->SetLowerValue(0);
|
||||
}
|
||||
|
||||
void Preview::update_double_slider_from_canvas(wxKeyEvent& event)
|
||||
{
|
||||
if (event.HasModifiers()) {
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto key = event.GetKeyCode();
|
||||
|
||||
if (key == 'U' || key == 'D') {
|
||||
const int new_pos = key == 'U' ? m_slider->GetHigherValue() + 1 : m_slider->GetHigherValue() - 1;
|
||||
m_slider->SetHigherValue(new_pos);
|
||||
if (event.ShiftDown()) m_slider->SetLowerValue(m_slider->GetHigherValue());
|
||||
}
|
||||
else if (key == 'S')
|
||||
m_slider->ChangeOneLayerLock();
|
||||
else
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
108
src/slic3r/GUI/GUI_Preview.hpp
Normal file
108
src/slic3r/GUI/GUI_Preview.hpp
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#ifndef slic3r_GUI_Preview_hpp_
|
||||
#define slic3r_GUI_Preview_hpp_
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include "../../libslic3r/Point.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
class wxNotebook;
|
||||
class wxGLCanvas;
|
||||
class wxBoxSizer;
|
||||
class wxStaticText;
|
||||
class wxChoice;
|
||||
class wxComboCtrl;
|
||||
class wxCheckBox;
|
||||
class PrusaDoubleSlider;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Print;
|
||||
class GCodePreviewData;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class Preview : public wxPanel
|
||||
{
|
||||
wxGLCanvas* m_canvas;
|
||||
wxBoxSizer* m_double_slider_sizer;
|
||||
wxStaticText* m_label_view_type;
|
||||
wxChoice* m_choice_view_type;
|
||||
wxStaticText* m_label_show_features;
|
||||
wxComboCtrl* m_combochecklist_features;
|
||||
wxCheckBox* m_checkbox_travel;
|
||||
wxCheckBox* m_checkbox_retractions;
|
||||
wxCheckBox* m_checkbox_unretractions;
|
||||
wxCheckBox* m_checkbox_shells;
|
||||
|
||||
DynamicPrintConfig* m_config;
|
||||
Print* m_print;
|
||||
GCodePreviewData* m_gcode_preview_data;
|
||||
|
||||
unsigned int m_number_extruders;
|
||||
std::string m_preferred_color_mode;
|
||||
|
||||
bool m_loaded;
|
||||
bool m_enabled;
|
||||
bool m_force_sliders_full_range;
|
||||
|
||||
PrusaDoubleSlider* m_slider {nullptr};
|
||||
|
||||
public:
|
||||
Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data);
|
||||
virtual ~Preview();
|
||||
|
||||
wxGLCanvas* get_wxglcanvas() { return m_canvas; }
|
||||
|
||||
void set_number_extruders(unsigned int number_extruders);
|
||||
void reset_gcode_preview_data();
|
||||
void set_canvas_as_dirty();
|
||||
void set_enabled(bool enabled);
|
||||
void set_bed_shape(const Pointfs& shape);
|
||||
void select_view(const std::string& direction);
|
||||
void set_viewport_from_scene(wxGLCanvas* canvas);
|
||||
void set_viewport_into_scene(wxGLCanvas* canvas);
|
||||
void set_drop_target(wxDropTarget* target);
|
||||
|
||||
void load_print();
|
||||
void reload_print(bool force = false);
|
||||
void refresh_print();
|
||||
|
||||
private:
|
||||
bool init(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data);
|
||||
|
||||
void bind_event_handlers();
|
||||
void unbind_event_handlers();
|
||||
|
||||
void show_hide_ui_elements(const std::string& what);
|
||||
|
||||
void reset_sliders();
|
||||
void update_sliders();
|
||||
|
||||
void on_size(wxSizeEvent& evt);
|
||||
void on_choice_view_type(wxCommandEvent& evt);
|
||||
void on_combochecklist_features(wxCommandEvent& evt);
|
||||
void on_checkbox_travel(wxCommandEvent& evt);
|
||||
void on_checkbox_retractions(wxCommandEvent& evt);
|
||||
void on_checkbox_unretractions(wxCommandEvent& evt);
|
||||
void on_checkbox_shells(wxCommandEvent& evt);
|
||||
|
||||
// Create/Update/Reset double slider on 3dPreview
|
||||
void create_double_slider();
|
||||
void update_double_slider(bool force_sliders_full_range);
|
||||
void fill_slider_values(std::vector<std::pair<int, double>> &values,
|
||||
const std::vector<double> &layers_z);
|
||||
void set_double_slider_thumbs( const bool force_sliders_full_range,
|
||||
const std::vector<double> &layers_z,
|
||||
const double z_low,
|
||||
const double z_high);
|
||||
void reset_double_slider();
|
||||
// update DoubleSlider after keyDown in canvas
|
||||
void update_double_slider_from_canvas(wxKeyEvent& event);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GUI_Preview_hpp_
|
||||
57
src/slic3r/GUI/GUI_PreviewIface.cpp
Normal file
57
src/slic3r/GUI/GUI_PreviewIface.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "GUI_PreviewIface.hpp"
|
||||
#include "GUI_Preview.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void PreviewIface::set_number_extruders(unsigned int number_extruders)
|
||||
{
|
||||
m_preview->set_number_extruders(number_extruders);
|
||||
}
|
||||
|
||||
void PreviewIface::reset_gcode_preview_data()
|
||||
{
|
||||
m_preview->reset_gcode_preview_data();
|
||||
}
|
||||
|
||||
void PreviewIface::reload_print(bool force)
|
||||
{
|
||||
m_preview->reload_print(force);
|
||||
}
|
||||
|
||||
void PreviewIface::set_canvas_as_dirty()
|
||||
{
|
||||
m_preview->set_canvas_as_dirty();
|
||||
}
|
||||
|
||||
void PreviewIface::set_enabled(bool enabled)
|
||||
{
|
||||
m_preview->set_enabled(enabled);
|
||||
}
|
||||
|
||||
void PreviewIface::set_bed_shape(const Pointfs& shape)
|
||||
{
|
||||
m_preview->set_bed_shape(shape);
|
||||
}
|
||||
|
||||
void PreviewIface::select_view(const std::string& direction)
|
||||
{
|
||||
m_preview->select_view(direction);
|
||||
}
|
||||
|
||||
void PreviewIface::set_viewport_from_scene(wxGLCanvas* canvas)
|
||||
{
|
||||
m_preview->set_viewport_from_scene(canvas);
|
||||
}
|
||||
|
||||
void PreviewIface::set_viewport_into_scene(wxGLCanvas* canvas)
|
||||
{
|
||||
m_preview->set_viewport_into_scene(canvas);
|
||||
}
|
||||
|
||||
void PreviewIface::set_drop_target(wxDropTarget* target)
|
||||
{
|
||||
m_preview->set_drop_target(target);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
36
src/slic3r/GUI/GUI_PreviewIface.hpp
Normal file
36
src/slic3r/GUI/GUI_PreviewIface.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef slic3r_GUI_PreviewIface_hpp_
|
||||
#define slic3r_GUI_PreviewIface_hpp_
|
||||
|
||||
#include "../../libslic3r/Point.hpp"
|
||||
|
||||
class wxGLCanvas;
|
||||
class wxDropTarget;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
class Preview;
|
||||
} // namespace GUI
|
||||
|
||||
class PreviewIface
|
||||
{
|
||||
GUI::Preview* m_preview;
|
||||
|
||||
public:
|
||||
explicit PreviewIface(GUI::Preview* preview) : m_preview(preview) {}
|
||||
|
||||
void set_number_extruders(unsigned int number_extruders);
|
||||
void reset_gcode_preview_data();
|
||||
void reload_print(bool force = false);
|
||||
void set_canvas_as_dirty();
|
||||
void set_enabled(bool enabled);
|
||||
void set_bed_shape(const Pointfs& shape);
|
||||
void select_view(const std::string& direction);
|
||||
void set_viewport_from_scene(wxGLCanvas* canvas);
|
||||
void set_viewport_into_scene(wxGLCanvas* canvas);
|
||||
void set_drop_target(wxDropTarget* target);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GUI_PreviewIface_hpp_
|
||||
127
src/slic3r/GUI/GUI_Utils.cpp
Normal file
127
src/slic3r/GUI/GUI_Utils.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#include "GUI_Utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include <wx/toplevel.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
CheckboxFileDialog::ExtraPanel::ExtraPanel(wxWindow *parent)
|
||||
: wxPanel(parent, wxID_ANY)
|
||||
{
|
||||
// WARN: wxMSW does some extra shenanigans to calc the extra control size.
|
||||
// It first calls the create function with a dummy empty wxDialog parent and saves its size.
|
||||
// Afterwards, the create function is called again with the real parent.
|
||||
// Additionally there's no way to pass any extra data to the create function (no closure),
|
||||
// which is why we have to this stuff here. Grrr!
|
||||
auto *dlg = dynamic_cast<CheckboxFileDialog*>(parent);
|
||||
const wxString checkbox_label(dlg != nullptr ? dlg->checkbox_label : wxString("String long enough to contain dlg->checkbox_label"));
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
cbox = new wxCheckBox(this, wxID_ANY, checkbox_label);
|
||||
cbox->SetValue(true);
|
||||
sizer->AddSpacer(5);
|
||||
sizer->Add(this->cbox, 0, wxEXPAND | wxALL, 5);
|
||||
SetSizer(sizer);
|
||||
sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
wxWindow* CheckboxFileDialog::ExtraPanel::ctor(wxWindow *parent) {
|
||||
return new ExtraPanel(parent);
|
||||
}
|
||||
|
||||
CheckboxFileDialog::CheckboxFileDialog(wxWindow *parent,
|
||||
const wxString &checkbox_label,
|
||||
bool checkbox_value,
|
||||
const wxString &message,
|
||||
const wxString &default_dir,
|
||||
const wxString &default_file,
|
||||
const wxString &wildcard,
|
||||
long style,
|
||||
const wxPoint &pos,
|
||||
const wxSize &size,
|
||||
const wxString &name
|
||||
)
|
||||
: wxFileDialog(parent, message, default_dir, default_file, wildcard, style, pos, size, name)
|
||||
, checkbox_label(checkbox_label)
|
||||
{
|
||||
if (checkbox_label.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetExtraControlCreator(ExtraPanel::ctor);
|
||||
}
|
||||
|
||||
bool CheckboxFileDialog::get_checkbox_value() const
|
||||
{
|
||||
auto *extra_panel = dynamic_cast<ExtraPanel*>(GetExtraControl());
|
||||
return extra_panel != nullptr ? extra_panel->cbox->GetValue() : false;
|
||||
}
|
||||
|
||||
|
||||
WindowMetrics WindowMetrics::from_window(wxTopLevelWindow *window)
|
||||
{
|
||||
WindowMetrics res;
|
||||
res.rect = window->GetScreenRect();
|
||||
res.maximized = window->IsMaximized();
|
||||
return res;
|
||||
}
|
||||
|
||||
boost::optional<WindowMetrics> WindowMetrics::deserialize(const std::string &str)
|
||||
{
|
||||
std::vector<std::string> metrics_str;
|
||||
metrics_str.reserve(5);
|
||||
|
||||
if (!unescape_strings_cstyle(str, metrics_str) || metrics_str.size() != 5) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
int metrics[5];
|
||||
try {
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
metrics[i] = boost::lexical_cast<int>(metrics_str[i]);
|
||||
}
|
||||
} catch(const boost::bad_lexical_cast &) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
if ((metrics[4] & ~1) != 0) { // Checks if the maximized flag is 1 or 0
|
||||
metrics[4] = 0;
|
||||
}
|
||||
|
||||
WindowMetrics res;
|
||||
res.rect = wxRect(metrics[0], metrics[1], metrics[2], metrics[3]);
|
||||
res.maximized = metrics[4];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void WindowMetrics::sanitize_for_display(const wxRect &screen_rect)
|
||||
{
|
||||
rect = rect.Intersect(screen_rect);
|
||||
}
|
||||
|
||||
std::string WindowMetrics::serialize()
|
||||
{
|
||||
return (boost::format("%1%; %2%; %3%; %4%; %5%")
|
||||
% rect.GetX()
|
||||
% rect.GetY()
|
||||
% rect.GetWidth()
|
||||
% rect.GetHeight()
|
||||
% static_cast<int>(maximized)
|
||||
).str();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
74
src/slic3r/GUI/GUI_Utils.hpp
Normal file
74
src/slic3r/GUI/GUI_Utils.hpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#ifndef slic3r_GUI_Utils_hpp_
|
||||
#define slic3r_GUI_Utils_hpp_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/panel.h>
|
||||
|
||||
class wxCheckBox;
|
||||
class wxTopLevelWindow;
|
||||
class wxRect;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
class CheckboxFileDialog : public wxFileDialog
|
||||
{
|
||||
public:
|
||||
CheckboxFileDialog(wxWindow *parent,
|
||||
const wxString &checkbox_label,
|
||||
bool checkbox_value,
|
||||
const wxString &message = wxFileSelectorPromptStr,
|
||||
const wxString &default_dir = wxEmptyString,
|
||||
const wxString &default_file = wxEmptyString,
|
||||
const wxString &wildcard = wxFileSelectorDefaultWildcardStr,
|
||||
long style = wxFD_DEFAULT_STYLE,
|
||||
const wxPoint &pos = wxDefaultPosition,
|
||||
const wxSize &size = wxDefaultSize,
|
||||
const wxString &name = wxFileDialogNameStr
|
||||
);
|
||||
|
||||
bool get_checkbox_value() const;
|
||||
|
||||
private:
|
||||
struct ExtraPanel : public wxPanel
|
||||
{
|
||||
wxCheckBox *cbox;
|
||||
|
||||
ExtraPanel(wxWindow *parent);
|
||||
static wxWindow* ctor(wxWindow *parent);
|
||||
};
|
||||
|
||||
wxString checkbox_label;
|
||||
};
|
||||
|
||||
|
||||
class WindowMetrics
|
||||
{
|
||||
private:
|
||||
wxRect rect;
|
||||
bool maximized;
|
||||
|
||||
WindowMetrics() : maximized(false) {}
|
||||
public:
|
||||
static WindowMetrics from_window(wxTopLevelWindow *window);
|
||||
static boost::optional<WindowMetrics> deserialize(const std::string &str);
|
||||
|
||||
wxRect get_rect() const { return rect; }
|
||||
bool get_maximized() const { return maximized; }
|
||||
|
||||
void sanitize_for_display(const wxRect &screen_rect);
|
||||
std::string serialize();
|
||||
};
|
||||
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
198
src/slic3r/GUI/LambdaObjectDialog.cpp
Normal file
198
src/slic3r/GUI/LambdaObjectDialog.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#include "LambdaObjectDialog.hpp"
|
||||
|
||||
#include <wx/window.h>
|
||||
#include <wx/button.h>
|
||||
#include "OptionsGroup.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
namespace GUI
|
||||
{
|
||||
|
||||
LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent,
|
||||
const wxString type_name):
|
||||
m_type_name(type_name)
|
||||
{
|
||||
Create(parent, wxID_ANY, _(L("Lambda Object")),
|
||||
parent->GetScreenPosition(), wxDefaultSize,
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
|
||||
// instead of double dim[3] = { 1.0, 1.0, 1.0 };
|
||||
object_parameters.dim[0] = 1.0;
|
||||
object_parameters.dim[1] = 1.0;
|
||||
object_parameters.dim[2] = 1.0;
|
||||
|
||||
sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// modificator options
|
||||
if (m_type_name == wxEmptyString) {
|
||||
m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition,
|
||||
wxDefaultSize, wxCHB_TOP);
|
||||
sizer->Add(m_modificator_options_book, 1, wxEXPAND | wxALL, 10);
|
||||
}
|
||||
else {
|
||||
m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
sizer->Add(m_panel, 1, wxEXPAND | wxALL, 10);
|
||||
}
|
||||
|
||||
ConfigOptionDef def;
|
||||
def.width = 70;
|
||||
auto optgroup = init_modificator_options_page(_(L("Box")));
|
||||
if (optgroup){
|
||||
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
|
||||
int opt_id = opt_key == "l" ? 0 :
|
||||
opt_key == "w" ? 1 :
|
||||
opt_key == "h" ? 2 : -1;
|
||||
if (opt_id < 0) return;
|
||||
object_parameters.dim[opt_id] = boost::any_cast<double>(value);
|
||||
};
|
||||
|
||||
def.type = coFloat;
|
||||
def.default_value = new ConfigOptionFloat{ 1.0 };
|
||||
def.label = L("L");
|
||||
Option option(def, "l");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("W");
|
||||
option = Option(def, "w");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("H");
|
||||
option = Option(def, "h");
|
||||
optgroup->append_single_option_line(option);
|
||||
}
|
||||
|
||||
optgroup = init_modificator_options_page(_(L("Cylinder")));
|
||||
if (optgroup){
|
||||
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
|
||||
int val = boost::any_cast<int>(value);
|
||||
if (opt_key == "cyl_r")
|
||||
object_parameters.cyl_r = val;
|
||||
else if (opt_key == "cyl_h")
|
||||
object_parameters.cyl_h = val;
|
||||
else return;
|
||||
};
|
||||
|
||||
def.type = coInt;
|
||||
def.default_value = new ConfigOptionInt{ 1 };
|
||||
def.label = L("Radius");
|
||||
auto option = Option(def, "cyl_r");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("Height");
|
||||
option = Option(def, "cyl_h");
|
||||
optgroup->append_single_option_line(option);
|
||||
}
|
||||
|
||||
optgroup = init_modificator_options_page(_(L("Sphere")));
|
||||
if (optgroup){
|
||||
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
|
||||
if (opt_key == "sph_rho")
|
||||
object_parameters.sph_rho = boost::any_cast<double>(value);
|
||||
else return;
|
||||
};
|
||||
|
||||
def.type = coFloat;
|
||||
def.default_value = new ConfigOptionFloat{ 1.0 };
|
||||
def.label = L("Rho");
|
||||
auto option = Option(def, "sph_rho");
|
||||
optgroup->append_single_option_line(option);
|
||||
}
|
||||
|
||||
optgroup = init_modificator_options_page(_(L("Slab")));
|
||||
if (optgroup){
|
||||
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
|
||||
double val = boost::any_cast<double>(value);
|
||||
if (opt_key == "slab_z")
|
||||
object_parameters.slab_z = val;
|
||||
else if (opt_key == "slab_h")
|
||||
object_parameters.slab_h = val;
|
||||
else return;
|
||||
};
|
||||
|
||||
def.type = coFloat;
|
||||
def.default_value = new ConfigOptionFloat{ 1.0 };
|
||||
def.label = L("H");
|
||||
auto option = Option(def, "slab_h");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("Initial Z");
|
||||
option = Option(def, "slab_z");
|
||||
optgroup->append_single_option_line(option);
|
||||
}
|
||||
|
||||
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e)
|
||||
{
|
||||
auto page_idx = m_modificator_options_book->GetSelection();
|
||||
if (page_idx < 0) return;
|
||||
switch (page_idx)
|
||||
{
|
||||
case 0:
|
||||
object_parameters.type = LambdaTypeBox;
|
||||
break;
|
||||
case 1:
|
||||
object_parameters.type = LambdaTypeCylinder;
|
||||
break;
|
||||
case 2:
|
||||
object_parameters.type = LambdaTypeSphere;
|
||||
break;
|
||||
case 3:
|
||||
object_parameters.type = LambdaTypeSlab;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}));
|
||||
|
||||
const auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
|
||||
wxButton* btn_OK = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
|
||||
btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
// validate user input
|
||||
if (!CanClose())return;
|
||||
EndModal(wxID_OK);
|
||||
Destroy();
|
||||
});
|
||||
|
||||
wxButton* btn_CANCEL = static_cast<wxButton*>(FindWindowById(wxID_CANCEL, this));
|
||||
btn_CANCEL->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
// validate user input
|
||||
if (!CanClose())return;
|
||||
EndModal(wxID_CANCEL);
|
||||
Destroy();
|
||||
});
|
||||
|
||||
sizer->Add(button_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
|
||||
SetSizer(sizer);
|
||||
sizer->Fit(this);
|
||||
sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
// Called from the constructor.
|
||||
// Create a panel for a rectangular / circular / custom bed shape.
|
||||
ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wxString& title){
|
||||
if (!m_type_name.IsEmpty() && m_type_name != title)
|
||||
return nullptr;
|
||||
|
||||
auto panel = m_type_name.IsEmpty() ? new wxPanel(m_modificator_options_book) : m_panel;
|
||||
|
||||
ConfigOptionsGroupShp optgroup;
|
||||
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Add")) + " " +title + " " +dots);
|
||||
optgroup->label_width = 100;
|
||||
|
||||
m_optgroups.push_back(optgroup);
|
||||
|
||||
if (m_type_name.IsEmpty()) {
|
||||
panel->SetSizerAndFit(optgroup->sizer);
|
||||
m_modificator_options_book->AddPage(panel, title);
|
||||
}
|
||||
else
|
||||
panel->SetSizer(optgroup->sizer);
|
||||
|
||||
return optgroup;
|
||||
}
|
||||
|
||||
|
||||
} //namespace GUI
|
||||
} //namespace Slic3r
|
||||
58
src/slic3r/GUI/LambdaObjectDialog.hpp
Normal file
58
src/slic3r/GUI/LambdaObjectDialog.hpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef slic3r_LambdaObjectDialog_hpp_
|
||||
#define slic3r_LambdaObjectDialog_hpp_
|
||||
|
||||
#include "GUI.hpp"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/choicebk.h>
|
||||
|
||||
class wxPanel;
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
namespace GUI
|
||||
{
|
||||
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;
|
||||
};
|
||||
class ConfigOptionsGroup;
|
||||
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
|
||||
class LambdaObjectDialog : public wxDialog
|
||||
{
|
||||
wxChoicebook* m_modificator_options_book = nullptr;
|
||||
std::vector <ConfigOptionsGroupShp> m_optgroups;
|
||||
wxString m_type_name;
|
||||
wxPanel* m_panel = nullptr;
|
||||
public:
|
||||
LambdaObjectDialog(wxWindow* parent,
|
||||
const wxString type_name = wxEmptyString);
|
||||
~LambdaObjectDialog(){}
|
||||
|
||||
bool CanClose() { return true; } // ???
|
||||
OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; }
|
||||
|
||||
ConfigOptionsGroupShp init_modificator_options_page(const wxString& title);
|
||||
|
||||
// Note whether the window was already closed, so a pending update is not executed.
|
||||
bool m_already_closed = false;
|
||||
OBJECT_PARAMETERS object_parameters;
|
||||
wxBoxSizer* sizer = nullptr;
|
||||
};
|
||||
} //namespace GUI
|
||||
} //namespace Slic3r
|
||||
#endif //slic3r_LambdaObjectDialog_hpp_
|
||||
777
src/slic3r/GUI/MainFrame.cpp
Normal file
777
src/slic3r/GUI/MainFrame.cpp
Normal file
|
|
@ -0,0 +1,777 @@
|
|||
#include "MainFrame.hpp"
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/icon.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/tooltip.h>
|
||||
#include <wx/debug.h>
|
||||
|
||||
#include "Tab.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "../AppController.hpp"
|
||||
#include "ProgressStatusBar.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
MainFrame::MainFrame(const bool no_plater, const bool loaded) :
|
||||
wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE),
|
||||
m_no_plater(no_plater),
|
||||
m_loaded(loaded)
|
||||
{
|
||||
m_appController = new Slic3r::AppController();
|
||||
|
||||
// Load the icon either from the exe, or from the ico file.
|
||||
#if _WIN32
|
||||
{
|
||||
TCHAR szExeFileName[MAX_PATH];
|
||||
GetModuleFileName(nullptr, szExeFileName, MAX_PATH);
|
||||
SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO));
|
||||
}
|
||||
#else
|
||||
SetIcon(wxIcon(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG));
|
||||
#endif // _WIN32
|
||||
|
||||
// initialize tabpanel and menubar
|
||||
init_tabpanel();
|
||||
init_menubar();
|
||||
|
||||
// set default tooltip timer in msec
|
||||
// SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values
|
||||
// (SetAutoPop is not available on GTK.)
|
||||
wxToolTip::SetAutoPop(32767);
|
||||
|
||||
// initialize status bar
|
||||
m_statusbar = new ProgressStatusBar(this);
|
||||
m_statusbar->embed(this);
|
||||
m_statusbar->set_status_text(_(L("Version ")) +
|
||||
SLIC3R_VERSION +
|
||||
_(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases")));
|
||||
|
||||
m_appController->set_model(&m_plater->model());
|
||||
m_appController->set_print(&m_plater->print());
|
||||
|
||||
GUI::set_gui_appctl();
|
||||
|
||||
// Make the global status bar and its progress indicator available in C++
|
||||
m_appController->set_global_progress_indicator(m_statusbar);
|
||||
|
||||
m_loaded = true;
|
||||
|
||||
// initialize layout
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
if (m_tabpanel)
|
||||
sizer->Add(m_tabpanel, 1, wxEXPAND);
|
||||
sizer->SetSizeHints(this);
|
||||
SetSizer(sizer);
|
||||
Fit();
|
||||
SetMinSize(wxSize(760, 490));
|
||||
SetSize(GetMinSize());
|
||||
Layout();
|
||||
|
||||
// declare events
|
||||
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event){
|
||||
if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
// save window size
|
||||
wxGetApp().window_pos_save(this, "mainframe");
|
||||
// Save the slic3r.ini.Usually the ini file is saved from "on idle" callback,
|
||||
// but in rare cases it may not have been called yet.
|
||||
wxGetApp().app_config->save();
|
||||
// if (m_plater)
|
||||
// m_plater->print = undef;
|
||||
_3DScene::remove_all_canvases();
|
||||
// Slic3r::GUI::deregister_on_request_update_callback();
|
||||
// propagate event
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
// NB: Restoring the window position is done in a two-phase manner here,
|
||||
// first the saved position is restored as-is and validation is done after the window is shown
|
||||
// and initial round of events is complete, because on some platforms that is the only way
|
||||
// to get an accurate window position & size.
|
||||
wxGetApp().window_pos_restore(this, "mainframe");
|
||||
Bind(wxEVT_SHOW, [this](wxShowEvent&) {
|
||||
CallAfter([this]() {
|
||||
wxGetApp().window_pos_sanitize(this);
|
||||
});
|
||||
});
|
||||
|
||||
update_ui_from_settings();
|
||||
return;
|
||||
}
|
||||
|
||||
void MainFrame::init_tabpanel()
|
||||
{
|
||||
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
|
||||
|
||||
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&){
|
||||
auto panel = m_tabpanel->GetCurrentPage();
|
||||
// panel->OnActivate(); if panel->can('OnActivate');
|
||||
|
||||
if (panel == nullptr)
|
||||
return;
|
||||
|
||||
for (auto& tab_name : { "print", "filament", "printer" }) {
|
||||
if (tab_name == panel->GetName()) {
|
||||
// On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered
|
||||
// before the MainFrame is fully set up.
|
||||
auto it = m_options_tabs.find(tab_name);
|
||||
assert(it != m_options_tabs.end());
|
||||
if (it != m_options_tabs.end())
|
||||
it->second->OnActivate();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!m_no_plater) {
|
||||
m_plater = new Slic3r::GUI::Plater(m_tabpanel, this);
|
||||
wxGetApp().plater_ = m_plater;
|
||||
m_tabpanel->AddPage(m_plater, _(L("Plater")));
|
||||
}
|
||||
|
||||
// The following event is emited by Tab implementation on config value change.
|
||||
Bind(EVT_TAB_VALUE_CHANGED, &MainFrame::on_value_changed, this);
|
||||
|
||||
// The following event is emited by Tab on preset selection,
|
||||
// or when the preset's "modified" status changes.
|
||||
Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this);
|
||||
|
||||
create_preset_tabs();
|
||||
std::vector<std::string> tab_names = { "print", "filament", "sla_material", "printer" };
|
||||
for (auto tab_name : tab_names)
|
||||
m_options_tabs[tab_name] = get_preset_tab(tab_name.c_str());
|
||||
|
||||
if (m_plater) {
|
||||
// load initial config
|
||||
auto full_config = wxGetApp().preset_bundle->full_config();
|
||||
m_plater->on_config_change(full_config);
|
||||
|
||||
// Show a correct number of filament fields.
|
||||
// nozzle_diameter is undefined when SLA printer is selected
|
||||
if (full_config.has("nozzle_diameter")){
|
||||
m_plater->on_extruders_change(full_config.option<ConfigOptionFloats>("nozzle_diameter")->values.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PresetTab> preset_tabs = {
|
||||
{ "print", nullptr, ptFFF },
|
||||
{ "filament", nullptr, ptFFF },
|
||||
{ "sla_material", nullptr, ptSLA }
|
||||
};
|
||||
|
||||
std::vector<PresetTab>& MainFrame::get_preset_tabs() {
|
||||
return preset_tabs;
|
||||
}
|
||||
|
||||
Tab* MainFrame::get_tab(const std::string& name)
|
||||
{
|
||||
std::vector<PresetTab>::iterator it = std::find_if(preset_tabs.begin(), preset_tabs.end(),
|
||||
[name](PresetTab& tab){ return name == tab.name; });
|
||||
return it != preset_tabs.end() ? it->panel : nullptr;
|
||||
}
|
||||
|
||||
Tab* MainFrame::get_preset_tab(const std::string& name)
|
||||
{
|
||||
Tab* tab = get_tab(name);
|
||||
if (tab) return tab;
|
||||
|
||||
for (size_t i = 0; i < m_tabpanel->GetPageCount(); ++i) {
|
||||
tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(i));
|
||||
if (!tab)
|
||||
continue;
|
||||
if (tab->name() == name) {
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MainFrame::create_preset_tabs()
|
||||
{
|
||||
wxGetApp().update_label_colours_from_appconfig();
|
||||
add_created_tab(new TabPrint(m_tabpanel));
|
||||
add_created_tab(new TabFilament(m_tabpanel));
|
||||
add_created_tab(new TabSLAMaterial(m_tabpanel));
|
||||
add_created_tab(new TabPrinter(m_tabpanel));
|
||||
}
|
||||
|
||||
void MainFrame::add_created_tab(Tab* panel)
|
||||
{
|
||||
panel->create_preset_tab();
|
||||
|
||||
const wxString& tab_name = panel->GetName();
|
||||
bool add_panel = true;
|
||||
|
||||
auto it = std::find_if(preset_tabs.begin(), preset_tabs.end(),
|
||||
[tab_name](PresetTab& tab){return tab.name == tab_name; });
|
||||
if (it != preset_tabs.end()) {
|
||||
it->panel = panel;
|
||||
add_panel = it->technology == wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
}
|
||||
|
||||
if (add_panel)
|
||||
m_tabpanel->AddPage(panel, panel->title());
|
||||
}
|
||||
|
||||
void MainFrame::init_menubar()
|
||||
{
|
||||
// File menu
|
||||
wxMenu* fileMenu = new wxMenu;
|
||||
{
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("Open STL/OBJ/AMF/3MF…\tCtrl+O")), _(L("Open a model")),
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->add(); }, "brick_add.png");
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("&Load Config…\tCtrl+L")), _(L("Load exported configuration file")),
|
||||
[this](wxCommandEvent&){ load_config_file(); }, "plugin_add.png");
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("&Export Config…\tCtrl+E")), _(L("Export current configuration to file")),
|
||||
[this](wxCommandEvent&){ export_config(); }, "plugin_go.png");
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("&Load Config Bundle…")), _(L("Load presets from a bundle")),
|
||||
[this](wxCommandEvent&){ load_configbundle(); }, "lorry_add.png");
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("&Export Config Bundle…")), _(L("Export all presets to file")),
|
||||
[this](wxCommandEvent&){ export_configbundle(); }, "lorry_go.png");
|
||||
fileMenu->AppendSeparator();
|
||||
wxMenuItem* repeat = nullptr;
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("Q&uick Slice…\tCtrl+U")), _(L("Slice a file into a G-code")),
|
||||
[this, repeat](wxCommandEvent&){
|
||||
wxTheApp->CallAfter([this, repeat](){
|
||||
quick_slice();
|
||||
repeat->Enable(is_last_input_file());
|
||||
}); }, "cog_go.png");
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice and Save &As…\tCtrl+Alt+U")), _(L("Slice a file into a G-code, save as")),
|
||||
[this, repeat](wxCommandEvent&){
|
||||
wxTheApp->CallAfter([this, repeat](){
|
||||
quick_slice(qsSaveAs);
|
||||
repeat->Enable(is_last_input_file());
|
||||
}); }, "cog_go.png");
|
||||
repeat = append_menu_item(fileMenu, wxID_ANY, _(L("&Repeat Last Quick Slice\tCtrl+Shift+U")), _(L("Repeat last quick slice")),
|
||||
[this](wxCommandEvent&){
|
||||
wxTheApp->CallAfter([this](){
|
||||
quick_slice(qsReslice);
|
||||
}); }, "cog_go.png");
|
||||
repeat->Enable(0);
|
||||
fileMenu->AppendSeparator();
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("Slice to SV&G…\tCtrl+G")), _(L("Slice file to a multi-layer SVG")),
|
||||
[this](wxCommandEvent&){ quick_slice(qsSaveAs | qsExportSVG); }, "shape_handles.png");
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("Slice to PNG…")), _(L("Slice file to a set of PNG files")),
|
||||
[this](wxCommandEvent&){ slice_to_png(); /*$self->quick_slice(save_as = > 0, export_png = > 1);*/ }, "shape_handles.png");
|
||||
m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(&Re)Slice Now\tCtrl+S")), _(L("Start new slicing process")),
|
||||
[this](wxCommandEvent&){ reslice_now(); }, "shape_handles.png");
|
||||
fileMenu->AppendSeparator();
|
||||
append_menu_item(fileMenu, wxID_ANY, _(L("Repair STL file…")), _(L("Automatically repair an STL file")),
|
||||
[this](wxCommandEvent&){ repair_stl(); }, "wrench.png");
|
||||
fileMenu->AppendSeparator();
|
||||
append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), _(L("Quit Slic3r")),
|
||||
[this](wxCommandEvent&){ Close(false); } );
|
||||
}
|
||||
|
||||
// Plater menu
|
||||
if (m_plater) {
|
||||
m_plater_menu = new wxMenu();
|
||||
append_menu_item(m_plater_menu, wxID_ANY, _(L("Export G-code...")), _(L("Export current plate as G-code")),
|
||||
[this](wxCommandEvent&){ m_plater->export_gcode(); }, "cog_go.png");
|
||||
append_menu_item(m_plater_menu, wxID_ANY, _(L("Export plate as STL...")), _(L("Export current plate as STL")),
|
||||
[this](wxCommandEvent&){ m_plater->export_stl(); }, "brick_go.png");
|
||||
append_menu_item(m_plater_menu, wxID_ANY, _(L("Export plate as AMF...")), _(L("Export current plate as AMF")),
|
||||
[this](wxCommandEvent&){ m_plater->export_amf(); }, "brick_go.png");
|
||||
append_menu_item(m_plater_menu, wxID_ANY, _(L("Export plate as 3MF...")), _(L("Export current plate as 3MF")),
|
||||
[this](wxCommandEvent&){ m_plater->export_3mf(); }, "brick_go.png");
|
||||
}
|
||||
|
||||
// Window menu
|
||||
auto windowMenu = new wxMenu();
|
||||
{
|
||||
size_t tab_offset = 0;
|
||||
if (m_plater) {
|
||||
append_menu_item(windowMenu, wxID_ANY, L("Select &Plater Tab\tCtrl+1"), L("Show the plater"),
|
||||
[this](wxCommandEvent&){ select_tab(0); }, "application_view_tile.png");
|
||||
tab_offset += 1;
|
||||
}
|
||||
if (tab_offset > 0) {
|
||||
windowMenu->AppendSeparator();
|
||||
}
|
||||
append_menu_item(windowMenu, wxID_ANY, L("Select P&rint Settings Tab\tCtrl+2"), L("Show the print settings"),
|
||||
[this, tab_offset](wxCommandEvent&){ select_tab(tab_offset + 0); }, "cog.png");
|
||||
append_menu_item(windowMenu, wxID_ANY, L("Select &Filament Settings Tab\tCtrl+3"), L("Show the filament settings"),
|
||||
[this, tab_offset](wxCommandEvent&){ select_tab(tab_offset + 1); }, "spool.png");
|
||||
append_menu_item(windowMenu, wxID_ANY, L("Select Print&er Settings Tab\tCtrl+4"), L("Show the printer settings"),
|
||||
[this, tab_offset](wxCommandEvent&){ select_tab(tab_offset + 2); }, "printer_empty.png");
|
||||
}
|
||||
|
||||
// View menu
|
||||
if (m_plater) {
|
||||
m_viewMenu = new wxMenu();
|
||||
// \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators,
|
||||
// as the simple numeric accelerators spoil all numeric data entry.
|
||||
// The camera control accelerators are captured by GLCanvas3D::on_char().
|
||||
append_menu_item(m_viewMenu, wxID_ANY, _(L("Iso")) + "\t\xA0" + "0", _(L("Iso View")), [this](wxCommandEvent&){ select_view("iso"); });
|
||||
m_viewMenu->AppendSeparator();
|
||||
append_menu_item(m_viewMenu, wxID_ANY, _(L("Top")) + "\t\xA0" + "1", _(L("Top View")), [this](wxCommandEvent&){ select_view("top"); });
|
||||
append_menu_item(m_viewMenu, wxID_ANY, _(L("Bottom")) + "\t\xA0" + "2", _(L("Bottom View")), [this](wxCommandEvent&){ select_view("bottom"); });
|
||||
append_menu_item(m_viewMenu, wxID_ANY, _(L("Front")) + "\t\xA0" + "3", _(L("Front View")), [this](wxCommandEvent&){ select_view("front"); });
|
||||
append_menu_item(m_viewMenu, wxID_ANY, _(L("Rear")) + "\t\xA0" + "4", _(L("Rear View")), [this](wxCommandEvent&){ select_view("rear"); });
|
||||
append_menu_item(m_viewMenu, wxID_ANY, _(L("Left")) + "\t\xA0" + "5", _(L("Left View")), [this](wxCommandEvent&){ select_view("left"); });
|
||||
append_menu_item(m_viewMenu, wxID_ANY, _(L("Right")) + "\t\xA0" + "6", _(L("Right View")), [this](wxCommandEvent&){ select_view("right"); });
|
||||
}
|
||||
|
||||
// Help menu
|
||||
auto helpMenu = new wxMenu();
|
||||
{
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D Drivers")), _(L("Open the Prusa3D drivers download page in your browser")),
|
||||
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://www.prusa3d.com/drivers/"); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Prusa Edition Releases")), _(L("Open the Prusa Edition releases page in your browser")),
|
||||
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/releases"); });
|
||||
//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
|
||||
//# wxTheApp->check_version(1);
|
||||
//# });
|
||||
//# $versioncheck->Enable(wxTheApp->have_version_check);
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Slic3r &Website")), _(L("Open the Slic3r website in your browser")),
|
||||
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://slic3r.org/"); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Slic3r &Manual")), _(L("Open the Slic3r manual in your browser")),
|
||||
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://manual.slic3r.org/"); });
|
||||
helpMenu->AppendSeparator();
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("System Info")), _(L("Show system information")),
|
||||
[this](wxCommandEvent&){ wxGetApp().system_info(); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")),
|
||||
[this](wxCommandEvent&){ Slic3r::GUI::desktop_open_datadir_folder(); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Report an Issue")), _(L("Report an issue on the Slic3r Prusa Edition")),
|
||||
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("&About Slic3r")), _(L("Show about dialog")),
|
||||
[this](wxCommandEvent&){ Slic3r::GUI::about(); });
|
||||
}
|
||||
|
||||
// menubar
|
||||
// assign menubar to frame after appending items, otherwise special items
|
||||
// will not be handled correctly
|
||||
{
|
||||
auto menubar = new wxMenuBar();
|
||||
menubar->Append(fileMenu, L("&File"));
|
||||
if (m_plater_menu) menubar->Append(m_plater_menu, L("&Plater")) ;
|
||||
menubar->Append(windowMenu, L("&Window"));
|
||||
if (m_viewMenu) menubar->Append(m_viewMenu, L("&View"));
|
||||
// Add additional menus from C++
|
||||
wxGetApp().add_config_menu(menubar);
|
||||
menubar->Append(helpMenu, L("&Help"));
|
||||
SetMenuBar(menubar);
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::slice_to_png(){
|
||||
// m_plater->stop_background_process();
|
||||
// m_plater->async_apply_config();
|
||||
m_appController->print_ctl()->slice_to_png();
|
||||
}
|
||||
|
||||
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
|
||||
void MainFrame::quick_slice(const int qs){
|
||||
// my $progress_dialog;
|
||||
wxString input_file;
|
||||
// eval
|
||||
// {
|
||||
// validate configuration
|
||||
auto config = wxGetApp().preset_bundle->full_config();
|
||||
config.validate();
|
||||
|
||||
// select input file
|
||||
if (!(qs & qsReslice)) {
|
||||
auto dlg = new wxFileDialog(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")),
|
||||
wxGetApp().app_config->get_last_dir(), "",
|
||||
file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg->ShowModal() != wxID_OK) {
|
||||
dlg->Destroy();
|
||||
return;
|
||||
}
|
||||
input_file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
if (!(qs & qsExportSVG))
|
||||
m_qs_last_input_file = input_file;
|
||||
}
|
||||
else {
|
||||
if (m_qs_last_input_file.IsEmpty()) {
|
||||
auto dlg = new wxMessageDialog(this, _(L("No previously sliced file.")),
|
||||
_(L("Error")), wxICON_ERROR | wxOK);
|
||||
dlg->ShowModal();
|
||||
return;
|
||||
}
|
||||
if (std::ifstream(m_qs_last_input_file.char_str())) {
|
||||
auto dlg = new wxMessageDialog(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")),
|
||||
_(L("File Not Found")), wxICON_ERROR | wxOK);
|
||||
dlg->ShowModal();
|
||||
return;
|
||||
}
|
||||
input_file = m_qs_last_input_file;
|
||||
}
|
||||
auto input_file_basename = get_base_name(input_file);
|
||||
wxGetApp().app_config->update_skein_dir(get_dir_name(input_file));
|
||||
|
||||
auto bed_shape = Slic3r::Polygon::new_scale(config.option<ConfigOptionPoints>("bed_shape")->values);
|
||||
// auto print_center = Slic3r::Pointf->new_unscale(bed_shape.bounding_box().center());
|
||||
//
|
||||
// auto sprint = new Slic3r::Print::Simple(
|
||||
// print_center = > print_center,
|
||||
// status_cb = > [](int percent, const wxString& msg){
|
||||
// m_progress_dialog->Update(percent, msg+"…");
|
||||
// });
|
||||
|
||||
// keep model around
|
||||
auto model = Slic3r::Model::read_from_file(input_file.ToStdString());
|
||||
|
||||
// sprint->apply_config(config);
|
||||
// sprint->set_model(model);
|
||||
|
||||
// Copy the names of active presets into the placeholder parser.
|
||||
// wxGetApp().preset_bundle->export_selections(sprint->placeholder_parser);
|
||||
|
||||
// select output file
|
||||
wxString output_file;
|
||||
if (qs & qsReslice) {
|
||||
if (!m_qs_last_output_file.IsEmpty())
|
||||
output_file = m_qs_last_output_file;
|
||||
}
|
||||
else if (qs & qsSaveAs) {
|
||||
// The following line may die if the output_filename_format template substitution fails.
|
||||
// output_file = sprint->output_filepath;
|
||||
// if (export_svg)
|
||||
// output_file = ~s / \.[gG][cC][oO][dD][eE]$ / .svg /;
|
||||
auto dlg = new wxFileDialog(this, _(L("Save ")) + (qs & qsExportSVG ? _(L("SVG")) : _(L("G-code"))) + _(L(" file as:")),
|
||||
wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(input_file),
|
||||
qs & qsExportSVG ? file_wildcards[FT_SVG] : file_wildcards[FT_GCODE],
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if (dlg->ShowModal() != wxID_OK) {
|
||||
dlg->Destroy();
|
||||
return;
|
||||
}
|
||||
output_file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
if (!(qs & qsExportSVG))
|
||||
m_qs_last_output_file = output_file;
|
||||
wxGetApp().app_config->update_last_output_dir(get_dir_name(output_file));
|
||||
}
|
||||
else if (qs & qsExportPNG) {
|
||||
// output_file = sprint->output_filepath;
|
||||
// output_file = ~s / \.[gG][cC][oO][dD][eE]$ / .zip / ;
|
||||
auto dlg = new wxFileDialog(this, _(L("Save zip file as:")),
|
||||
wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)),
|
||||
get_base_name(output_file), "*.zip", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if (dlg->ShowModal() != wxID_OK) {
|
||||
dlg->Destroy();
|
||||
return;
|
||||
}
|
||||
output_file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
}
|
||||
|
||||
// show processbar dialog
|
||||
m_progress_dialog = new wxProgressDialog(_(L("Slicing…")), _(L("Processing ")) + input_file_basename + "…",
|
||||
100, this, 4);
|
||||
m_progress_dialog->Pulse();
|
||||
{
|
||||
// my @warnings = ();
|
||||
// local $SIG{ __WARN__ } = sub{ push @warnings, $_[0] };
|
||||
|
||||
// sprint->output_file(output_file);
|
||||
// if (export_svg) {
|
||||
// sprint->export_svg();
|
||||
// }
|
||||
// else if(export_png) {
|
||||
// sprint->export_png();
|
||||
// }
|
||||
// else {
|
||||
// sprint->export_gcode();
|
||||
// }
|
||||
// sprint->status_cb(undef);
|
||||
// Slic3r::GUI::warning_catcher($self)->($_) for @warnings;
|
||||
}
|
||||
m_progress_dialog->Destroy();
|
||||
m_progress_dialog = nullptr;
|
||||
|
||||
auto message = input_file_basename + _(L(" was successfully sliced."));
|
||||
// wxTheApp->notify(message);
|
||||
wxMessageDialog(this, message, _(L("Slicing Done!")), wxOK | wxICON_INFORMATION).ShowModal();
|
||||
// };
|
||||
// Slic3r::GUI::catch_error(this, [](){ if (m_progress_dialog) m_progress_dialog->Destroy(); });
|
||||
}
|
||||
|
||||
void MainFrame::reslice_now()
|
||||
{
|
||||
if (m_plater)
|
||||
m_plater->reslice();
|
||||
}
|
||||
|
||||
void MainFrame::repair_stl()
|
||||
{
|
||||
wxString input_file;
|
||||
{
|
||||
auto dlg = new wxFileDialog(this, _(L("Select the STL file to repair:")),
|
||||
wxGetApp().app_config->get_last_dir(), "",
|
||||
file_wildcards[FT_STL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg->ShowModal() != wxID_OK) {
|
||||
dlg->Destroy();
|
||||
return;
|
||||
}
|
||||
input_file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
}
|
||||
|
||||
auto output_file = input_file;
|
||||
{
|
||||
// output_file = ~s / \.[sS][tT][lL]$ / _fixed.obj / ;
|
||||
auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"),
|
||||
get_dir_name(output_file), get_base_name(output_file),
|
||||
file_wildcards[FT_OBJ], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if (dlg->ShowModal() != wxID_OK) {
|
||||
dlg->Destroy();
|
||||
return /*undef*/;
|
||||
}
|
||||
output_file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
}
|
||||
|
||||
auto tmesh = new Slic3r::TriangleMesh();
|
||||
tmesh->ReadSTLFile(input_file.char_str());
|
||||
tmesh->repair();
|
||||
tmesh->WriteOBJFile(output_file.char_str());
|
||||
Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair"));
|
||||
}
|
||||
|
||||
void MainFrame::export_config()
|
||||
{
|
||||
// Generate a cummulative configuration for the selected print, filaments and printer.
|
||||
auto config = wxGetApp().preset_bundle->full_config();
|
||||
// Validate the cummulative configuration.
|
||||
auto valid = config.validate();
|
||||
if (!valid.empty()) {
|
||||
// Slic3r::GUI::catch_error(this);
|
||||
return;
|
||||
}
|
||||
// Ask user for the file name for the config file.
|
||||
auto dlg = new wxFileDialog(this, _(L("Save configuration as:")),
|
||||
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
|
||||
!m_last_config.IsEmpty() ? get_base_name(m_last_config) : "config.ini",
|
||||
file_wildcards[FT_INI], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
wxString file;
|
||||
if (dlg->ShowModal() == wxID_OK)
|
||||
file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
if (!file.IsEmpty()) {
|
||||
wxGetApp().app_config->update_config_dir(get_dir_name(file));
|
||||
m_last_config = file;
|
||||
config.save(file.ToStdString());
|
||||
}
|
||||
}
|
||||
|
||||
// Load a config file containing a Print, Filament & Printer preset.
|
||||
void MainFrame::load_config_file(wxString file/* = wxEmptyString*/)
|
||||
{
|
||||
if (file.IsEmpty()) {
|
||||
if (!wxGetApp().check_unsaved_changes())
|
||||
return;
|
||||
auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")),
|
||||
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
|
||||
"config.ini", "INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg->ShowModal() != wxID_OK)
|
||||
return;
|
||||
file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
}
|
||||
try {
|
||||
wxGetApp().preset_bundle->load_config_file(file.ToStdString());
|
||||
} catch (std::exception & /* ex */) {
|
||||
// Dont proceed further if the config file cannot be loaded.
|
||||
// if (Slic3r::GUI::catch_error(this))
|
||||
// return;
|
||||
}
|
||||
for (auto tab : m_options_tabs )
|
||||
tab.second->load_current_preset();
|
||||
wxGetApp().app_config->update_config_dir(get_dir_name(file));
|
||||
m_last_config = file;
|
||||
}
|
||||
|
||||
void MainFrame::export_configbundle()
|
||||
{
|
||||
if (!wxGetApp().check_unsaved_changes())
|
||||
return;
|
||||
// validate current configuration in case it's dirty
|
||||
auto valid = wxGetApp().preset_bundle->full_config().validate();
|
||||
if (!valid.empty()) {
|
||||
// Slic3r::GUI::catch_error(this);
|
||||
return;
|
||||
}
|
||||
// Ask user for a file name.
|
||||
auto dlg = new wxFileDialog(this, _(L("Save presets bundle as:")),
|
||||
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
|
||||
"Slic3r_config_bundle.ini",
|
||||
file_wildcards[FT_INI], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
wxString file;
|
||||
if (dlg->ShowModal() == wxID_OK)
|
||||
file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
if (!file.IsEmpty()) {
|
||||
// Export the config bundle.
|
||||
wxGetApp().app_config->update_config_dir(get_dir_name(file));
|
||||
try {
|
||||
wxGetApp().preset_bundle->export_configbundle(file.ToStdString());
|
||||
} catch (std::exception & /* ex */) {
|
||||
// Slic3r::GUI::catch_error(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loading a config bundle with an external file name used to be used
|
||||
// to auto - install a config bundle on a fresh user account,
|
||||
// but that behavior was not documented and likely buggy.
|
||||
void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/)
|
||||
{
|
||||
if (!wxGetApp().check_unsaved_changes())
|
||||
return;
|
||||
if (file.IsEmpty()) {
|
||||
auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")),
|
||||
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
|
||||
"config.ini", file_wildcards[FT_INI], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg->ShowModal() != wxID_OK)
|
||||
return;
|
||||
file = dlg->GetPath();
|
||||
dlg->Destroy();
|
||||
}
|
||||
|
||||
wxGetApp().app_config->update_config_dir(get_dir_name(file));
|
||||
|
||||
auto presets_imported = 0;
|
||||
try {
|
||||
presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToStdString());
|
||||
} catch (std::exception & /* ex */) {
|
||||
// Slic3r::GUI::catch_error(this) and return;
|
||||
}
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
for (auto tab : m_options_tabs)
|
||||
tab.second->load_current_preset();
|
||||
|
||||
const auto message = wxString::Format(_(L("%d presets successfully imported.")), presets_imported);
|
||||
Slic3r::GUI::show_info(this, message, "Info");
|
||||
}
|
||||
|
||||
// Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset.
|
||||
// Also update the platter with the new presets.
|
||||
void MainFrame::load_config(const DynamicPrintConfig& config)
|
||||
{
|
||||
for (auto tab : m_options_tabs)
|
||||
tab.second->load_config(config);
|
||||
if (m_plater)
|
||||
m_plater->on_config_change(config);
|
||||
}
|
||||
|
||||
void MainFrame::select_tab(size_t tab) const
|
||||
{
|
||||
m_tabpanel->SetSelection(tab);
|
||||
}
|
||||
|
||||
// Set a camera direction, zoom to all objects.
|
||||
void MainFrame::select_view(const std::string& direction)
|
||||
{
|
||||
if (m_plater)
|
||||
m_plater->select_view(direction);
|
||||
}
|
||||
|
||||
void MainFrame::on_presets_changed(SimpleEvent &event)
|
||||
{
|
||||
auto *tab = dynamic_cast<Tab*>(event.GetEventObject());
|
||||
wxASSERT(tab != nullptr);
|
||||
if (tab == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update preset combo boxes(Print settings, Filament, Material, Printer) from their respective tabs.
|
||||
auto presets = tab->get_presets();
|
||||
if (m_plater != nullptr && presets != nullptr) {
|
||||
auto reload_dependent_tabs = tab->get_dependent_tabs();
|
||||
|
||||
// FIXME: The preset type really should be a property of Tab instead
|
||||
Slic3r::Preset::Type preset_type = tab->type();
|
||||
if (preset_type == Slic3r::Preset::TYPE_INVALID){
|
||||
wxASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_plater->sidebar().update_presets(preset_type);
|
||||
|
||||
if (preset_type == Slic3r::Preset::TYPE_PRINTER) {
|
||||
// Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors.
|
||||
// XXX: Do this in a more C++ way
|
||||
for (const auto tab_name_other : { "print", "filament", "sla_material" }) {
|
||||
Tab* cur_tab = m_options_tabs[tab_name_other];
|
||||
// If the printer tells us that the print or filament preset has been switched or invalidated,
|
||||
// refresh the print or filament tab page.Otherwise just refresh the combo box.
|
||||
if (reload_dependent_tabs.empty() ||
|
||||
find(reload_dependent_tabs.begin(), reload_dependent_tabs.end(), tab_name_other) ==
|
||||
reload_dependent_tabs.end() )
|
||||
cur_tab->update_tab_ui();
|
||||
else
|
||||
cur_tab->load_current_preset();
|
||||
}
|
||||
}
|
||||
m_plater->on_config_change(*tab->get_config());
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::on_value_changed(wxCommandEvent& event)
|
||||
{
|
||||
auto *tab = dynamic_cast<Tab*>(event.GetEventObject());
|
||||
wxASSERT(tab != nullptr);
|
||||
if (tab == nullptr)
|
||||
return;
|
||||
|
||||
auto opt_key = event.GetString();
|
||||
if (m_plater) {
|
||||
m_plater->on_config_change(*tab->get_config()); // propagate config change events to the plater
|
||||
if (opt_key == "extruders_count"){
|
||||
auto value = event.GetInt();
|
||||
m_plater->on_extruders_change(value);
|
||||
}
|
||||
}
|
||||
// Don't save while loading for the first time.
|
||||
if (m_loaded) {
|
||||
AppConfig &cfg = *wxGetApp().app_config;
|
||||
if (cfg.get("autosave") == "1")
|
||||
cfg.save();
|
||||
}
|
||||
}
|
||||
|
||||
// Called after the Preferences dialog is closed and the program settings are saved.
|
||||
// Update the UI based on the current preferences.
|
||||
void MainFrame::update_ui_from_settings()
|
||||
{
|
||||
m_menu_item_reslice_now->Enable(wxGetApp().app_config->get("background_processing") == "1");
|
||||
// if (m_plater) m_plater->update_ui_from_settings();
|
||||
std::vector<std::string> tab_names = { "print", "filament", "printer" };
|
||||
for (auto tab_name: tab_names)
|
||||
m_options_tabs[tab_name]->update_ui_from_settings();
|
||||
}
|
||||
|
||||
|
||||
std::string MainFrame::get_base_name(const wxString full_name) const
|
||||
{
|
||||
return boost::filesystem::path(full_name).filename().string();
|
||||
}
|
||||
|
||||
std::string MainFrame::get_dir_name(const wxString full_name) const
|
||||
{
|
||||
return boost::filesystem::path(full_name).parent_path().string();
|
||||
}
|
||||
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
113
src/slic3r/GUI/MainFrame.hpp
Normal file
113
src/slic3r/GUI/MainFrame.hpp
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#ifndef slic3r_MainFrame_hpp_
|
||||
#define slic3r_MainFrame_hpp_
|
||||
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
#include <wx/frame.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "Plater.hpp"
|
||||
#include "Event.hpp"
|
||||
|
||||
class wxMenuBar;
|
||||
class wxNotebook;
|
||||
class wxPanel;
|
||||
class wxMenu;
|
||||
class wxProgressDialog;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ProgressStatusBar;
|
||||
class AppController;
|
||||
|
||||
// #define _(s) Slic3r::GUI::I18N::translate((s))
|
||||
|
||||
namespace GUI
|
||||
{
|
||||
class Tab;
|
||||
|
||||
enum QuickSlice
|
||||
{
|
||||
qsUndef = 0,
|
||||
qsReslice = 1,
|
||||
qsSaveAs = 2,
|
||||
qsExportSVG = 4,
|
||||
qsExportPNG = 8
|
||||
};
|
||||
|
||||
struct PresetTab {
|
||||
std::string name;
|
||||
Tab* panel;
|
||||
PrinterTechnology technology;
|
||||
};
|
||||
|
||||
class MainFrame : public wxFrame
|
||||
{
|
||||
bool m_no_plater;
|
||||
bool m_loaded;
|
||||
int m_lang_ch_event;
|
||||
int m_preferences_event;
|
||||
|
||||
wxString m_qs_last_input_file = wxEmptyString;
|
||||
wxString m_qs_last_output_file = wxEmptyString;
|
||||
wxString m_last_config = wxEmptyString;
|
||||
|
||||
AppController* m_appController { nullptr };
|
||||
std::map<std::string, Tab*> m_options_tabs;
|
||||
|
||||
wxMenuItem* m_menu_item_reslice_now { nullptr };
|
||||
wxMenu* m_plater_menu { nullptr };
|
||||
wxMenu* m_viewMenu{ nullptr };
|
||||
|
||||
std::string get_base_name(const wxString full_name) const ;
|
||||
std::string get_dir_name(const wxString full_name) const ;
|
||||
|
||||
void on_presets_changed(SimpleEvent&);
|
||||
void on_value_changed(wxCommandEvent&);
|
||||
Tab* get_tab(const std::string& name);
|
||||
|
||||
public:
|
||||
MainFrame() {}
|
||||
MainFrame(const bool no_plater, const bool loaded);
|
||||
~MainFrame() {}
|
||||
|
||||
|
||||
void init_tabpanel();
|
||||
const std::map<std::string, Tab*>& options_tabs() const { return m_options_tabs; }
|
||||
Tab* get_preset_tab(const std::string& name);
|
||||
void create_preset_tabs();
|
||||
void add_created_tab(Tab* panel);
|
||||
void init_menubar();
|
||||
|
||||
void update_ui_from_settings();
|
||||
bool is_loaded() const { return m_loaded; }
|
||||
bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); }
|
||||
|
||||
void slice_to_png();
|
||||
void quick_slice(const int qs = qsUndef);
|
||||
void reslice_now();
|
||||
void repair_stl();
|
||||
void export_config();
|
||||
void load_config_file(wxString file = wxEmptyString);
|
||||
void export_configbundle();
|
||||
void load_configbundle(wxString file = wxEmptyString);
|
||||
void load_config(const DynamicPrintConfig& config);
|
||||
void select_tab(size_t tab) const;
|
||||
void select_view(const std::string& direction);
|
||||
|
||||
AppController* app_controller() { return m_appController; }
|
||||
|
||||
std::vector<PresetTab>& get_preset_tabs();
|
||||
|
||||
Plater* m_plater { nullptr };
|
||||
wxNotebook* m_tabpanel { nullptr };
|
||||
wxProgressDialog* m_progress_dialog { nullptr };
|
||||
ProgressStatusBar* m_statusbar { nullptr };
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} //Slic3r
|
||||
|
||||
#endif // slic3r_MainFrame_hpp_
|
||||
88
src/slic3r/GUI/MsgDialog.cpp
Normal file
88
src/slic3r/GUI/MsgDialog.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#include "MsgDialog.hpp"
|
||||
|
||||
#include <wx/settings.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/scrolwin.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id) :
|
||||
MsgDialog(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id)
|
||||
{}
|
||||
|
||||
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) :
|
||||
wxDialog(parent, wxID_ANY, title),
|
||||
boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)),
|
||||
content_sizer(new wxBoxSizer(wxVERTICAL)),
|
||||
btn_sizer(new wxBoxSizer(wxHORIZONTAL))
|
||||
{
|
||||
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
|
||||
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *rightsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto *headtext = new wxStaticText(this, wxID_ANY, headline);
|
||||
headtext->SetFont(boldfont);
|
||||
headtext->Wrap(CONTENT_WIDTH);
|
||||
rightsizer->Add(headtext);
|
||||
rightsizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
rightsizer->Add(content_sizer, 1, wxEXPAND);
|
||||
|
||||
if (button_id != wxID_NONE) {
|
||||
auto *button = new wxButton(this, button_id);
|
||||
button->SetFocus();
|
||||
btn_sizer->Add(button);
|
||||
}
|
||||
|
||||
rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL);
|
||||
|
||||
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap));
|
||||
|
||||
topsizer->Add(logo, 0, wxALL, BORDER);
|
||||
topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER);
|
||||
|
||||
SetSizerAndFit(topsizer);
|
||||
}
|
||||
|
||||
MsgDialog::~MsgDialog() {}
|
||||
|
||||
|
||||
// ErrorDialog
|
||||
|
||||
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) :
|
||||
MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG))
|
||||
{
|
||||
auto *panel = new wxScrolledWindow(this);
|
||||
auto *p_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
panel->SetSizer(p_sizer);
|
||||
|
||||
auto *text = new wxStaticText(panel, wxID_ANY, msg);
|
||||
text->Wrap(CONTENT_WIDTH);
|
||||
p_sizer->Add(text, 1, wxEXPAND);
|
||||
|
||||
panel->SetMinSize(wxSize(CONTENT_WIDTH, 0));
|
||||
panel->SetScrollRate(0, 5);
|
||||
|
||||
content_sizer->Add(panel, 1, wxEXPAND);
|
||||
|
||||
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT));
|
||||
Fit();
|
||||
}
|
||||
|
||||
ErrorDialog::~ErrorDialog() {}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
67
src/slic3r/GUI/MsgDialog.hpp
Normal file
67
src/slic3r/GUI/MsgDialog.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#ifndef slic3r_MsgDialog_hpp_
|
||||
#define slic3r_MsgDialog_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/font.h>
|
||||
#include <wx/bitmap.h>
|
||||
|
||||
#include "slic3r/Utils/Semver.hpp"
|
||||
|
||||
class wxBoxSizer;
|
||||
class wxCheckBox;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
// A message / query dialog with a bitmap on the left and any content on the right
|
||||
// with buttons underneath.
|
||||
struct MsgDialog : wxDialog
|
||||
{
|
||||
MsgDialog(MsgDialog &&) = delete;
|
||||
MsgDialog(const MsgDialog &) = delete;
|
||||
MsgDialog &operator=(MsgDialog &&) = delete;
|
||||
MsgDialog &operator=(const MsgDialog &) = delete;
|
||||
virtual ~MsgDialog();
|
||||
|
||||
// TODO: refactor with CreateStdDialogButtonSizer usage
|
||||
|
||||
protected:
|
||||
enum {
|
||||
CONTENT_WIDTH = 500,
|
||||
CONTENT_MAX_HEIGHT = 600,
|
||||
BORDER = 30,
|
||||
VERT_SPACING = 15,
|
||||
HORIZ_SPACING = 5,
|
||||
};
|
||||
|
||||
// button_id is an id of a button that can be added by default, use wxID_NONE to disable
|
||||
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK);
|
||||
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id = wxID_OK);
|
||||
|
||||
wxFont boldfont;
|
||||
wxBoxSizer *content_sizer;
|
||||
wxBoxSizer *btn_sizer;
|
||||
};
|
||||
|
||||
|
||||
// Generic error dialog, used for displaying exceptions
|
||||
struct ErrorDialog : MsgDialog
|
||||
{
|
||||
ErrorDialog(wxWindow *parent, const wxString &msg);
|
||||
ErrorDialog(ErrorDialog &&) = delete;
|
||||
ErrorDialog(const ErrorDialog &) = delete;
|
||||
ErrorDialog &operator=(ErrorDialog &&) = delete;
|
||||
ErrorDialog &operator=(const ErrorDialog &) = delete;
|
||||
virtual ~ErrorDialog();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
562
src/slic3r/GUI/OptionsGroup.cpp
Normal file
562
src/slic3r/GUI/OptionsGroup.cpp
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
#include "OptionsGroup.hpp"
|
||||
#include "ConfigExceptions.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <wx/numformatter.h>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
const t_field& OptionsGroup::build_field(const Option& opt, wxStaticText* label/* = nullptr*/) {
|
||||
return build_field(opt.opt_id, opt.opt, label);
|
||||
}
|
||||
const t_field& OptionsGroup::build_field(const t_config_option_key& id, wxStaticText* label/* = nullptr*/) {
|
||||
const ConfigOptionDef& opt = m_options.at(id).opt;
|
||||
return build_field(id, opt, label);
|
||||
}
|
||||
|
||||
const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label/* = nullptr*/) {
|
||||
// Check the gui_type field first, fall through
|
||||
// is the normal type.
|
||||
if (opt.gui_type.compare("select") == 0) {
|
||||
} else if (opt.gui_type.compare("select_open") == 0) {
|
||||
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
|
||||
} else if (opt.gui_type.compare("color") == 0) {
|
||||
m_fields.emplace(id, STDMOVE(ColourPicker::Create<ColourPicker>(parent(), opt, id)));
|
||||
} else if (opt.gui_type.compare("f_enum_open") == 0 ||
|
||||
opt.gui_type.compare("i_enum_open") == 0 ||
|
||||
opt.gui_type.compare("i_enum_closed") == 0) {
|
||||
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
|
||||
} else if (opt.gui_type.compare("slider") == 0) {
|
||||
m_fields.emplace(id, STDMOVE(SliderCtrl::Create<SliderCtrl>(parent(), opt, id)));
|
||||
} else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl
|
||||
} else if (opt.gui_type.compare("legend") == 0) { // StaticText
|
||||
m_fields.emplace(id, STDMOVE(StaticText::Create<StaticText>(parent(), opt, id)));
|
||||
} else {
|
||||
switch (opt.type) {
|
||||
case coFloatOrPercent:
|
||||
case coFloat:
|
||||
case coFloats:
|
||||
case coPercent:
|
||||
case coPercents:
|
||||
case coString:
|
||||
case coStrings:
|
||||
m_fields.emplace(id, STDMOVE(TextCtrl::Create<TextCtrl>(parent(), opt, id)));
|
||||
break;
|
||||
case coBool:
|
||||
case coBools:
|
||||
m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(parent(), opt, id)));
|
||||
break;
|
||||
case coInt:
|
||||
case coInts:
|
||||
m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(parent(), opt, id)));
|
||||
break;
|
||||
case coEnum:
|
||||
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
|
||||
break;
|
||||
case coPoints:
|
||||
m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(parent(), opt, id)));
|
||||
break;
|
||||
case coNone: break;
|
||||
default:
|
||||
throw /*//!ConfigGUITypeError("")*/std::logic_error("This control doesn't exist till now"); break;
|
||||
}
|
||||
}
|
||||
// Grab a reference to fields for convenience
|
||||
const t_field& field = m_fields[id];
|
||||
field->m_on_change = [this](std::string opt_id, boost::any value){
|
||||
//! This function will be called from Field.
|
||||
//! Call OptionGroup._on_change(...)
|
||||
if (!m_disabled)
|
||||
this->on_change_OG(opt_id, value);
|
||||
};
|
||||
field->m_on_kill_focus = [this](){
|
||||
//! This function will be called from Field.
|
||||
if (!m_disabled)
|
||||
this->on_kill_focus();
|
||||
};
|
||||
field->m_parent = parent();
|
||||
|
||||
//! Label to change background color, when option is modified
|
||||
field->m_Label = label;
|
||||
field->m_back_to_initial_value = [this](std::string opt_id){
|
||||
if (!m_disabled)
|
||||
this->back_to_initial_value(opt_id);
|
||||
};
|
||||
field->m_back_to_sys_value = [this](std::string opt_id){
|
||||
if (!this->m_disabled)
|
||||
this->back_to_sys_value(opt_id);
|
||||
};
|
||||
|
||||
// assign function objects for callbacks, etc.
|
||||
return field;
|
||||
}
|
||||
|
||||
void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field)
|
||||
{
|
||||
if (!m_show_modified_btns) {
|
||||
field->m_Undo_btn->set_as_hidden();
|
||||
field->m_Undo_to_sys_btn->set_as_hidden();
|
||||
return;
|
||||
}
|
||||
|
||||
sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
|
||||
void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* = nullptr*/) {
|
||||
//! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)){
|
||||
if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width){
|
||||
if (line.sizer != nullptr) {
|
||||
sizer->Add(line.sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 15);
|
||||
return;
|
||||
}
|
||||
if (line.widget != nullptr) {
|
||||
sizer->Add(line.widget(m_parent), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto option_set = line.get_options();
|
||||
for (auto opt : option_set)
|
||||
m_options.emplace(opt.opt_id, opt);
|
||||
|
||||
// add mode value for current line to m_options_mode
|
||||
if (!option_set.empty())
|
||||
m_options_mode.push_back(option_set[0].opt.mode);
|
||||
|
||||
// if we have a single option with no label, no sidetext just add it directly to sizer
|
||||
if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width &&
|
||||
option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr &&
|
||||
line.get_extra_widgets().size() == 0) {
|
||||
wxSizer* tmp_sizer;
|
||||
#ifdef __WXGTK__
|
||||
tmp_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_panel->SetSizer(tmp_sizer);
|
||||
m_panel->Layout();
|
||||
#else
|
||||
tmp_sizer = sizer;
|
||||
#endif /* __WXGTK__ */
|
||||
|
||||
const auto& option = option_set.front();
|
||||
const auto& field = build_field(option);
|
||||
|
||||
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
add_undo_buttuns_to_sizer(btn_sizer, field);
|
||||
tmp_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0);
|
||||
if (is_window_field(field))
|
||||
tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
|
||||
if (is_sizer_field(field))
|
||||
tmp_sizer->Add(field->getSizer(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
|
||||
return;
|
||||
}
|
||||
|
||||
auto grid_sizer = m_grid_sizer;
|
||||
#ifdef __WXGTK__
|
||||
m_panel->SetSizer(m_grid_sizer);
|
||||
m_panel->Layout();
|
||||
#endif /* __WXGTK__ */
|
||||
|
||||
// if we have an extra column, build it
|
||||
if (extra_column)
|
||||
grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3);
|
||||
|
||||
// Build a label if we have it
|
||||
wxStaticText* label=nullptr;
|
||||
if (label_width != 0) {
|
||||
long label_style = staticbox ? 0 : wxALIGN_RIGHT;
|
||||
#ifdef __WXGTK__
|
||||
// workaround for correct text align of the StaticBox on Linux
|
||||
// flags wxALIGN_RIGHT and wxALIGN_CENTRE don't work when Ellipsize flags are _not_ given.
|
||||
// Text is properly aligned only when Ellipsize is checked.
|
||||
label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END;
|
||||
#endif /* __WXGTK__ */
|
||||
label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"),
|
||||
wxDefaultPosition, wxSize(label_width, -1), label_style);
|
||||
label->SetFont(label_font);
|
||||
label->Wrap(label_width); // avoid a Linux/GTK bug
|
||||
if (!line.near_label_widget)
|
||||
grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | 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) | wxALIGN_CENTER_VERTICAL, 5);
|
||||
}
|
||||
if (line.label_tooltip.compare("") != 0)
|
||||
label->SetToolTip(line.label_tooltip);
|
||||
}
|
||||
|
||||
// If there's a widget, build it and add the result to the sizer.
|
||||
if (line.widget != nullptr) {
|
||||
auto wgt = line.widget(parent());
|
||||
// If widget doesn't have label, don't use border
|
||||
grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5);
|
||||
if (colored_Label != nullptr) *colored_Label = label;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're here, we have more than one option or a single option with sidetext
|
||||
// so we need a horizontal sizer to arrange these things
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1);
|
||||
// If we have a single option with no sidetext just add it directly to the grid sizer
|
||||
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
|
||||
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
|
||||
const auto& option = option_set.front();
|
||||
const auto& field = build_field(option, label);
|
||||
|
||||
add_undo_buttuns_to_sizer(sizer, field);
|
||||
if (is_window_field(field))
|
||||
sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) |
|
||||
wxBOTTOM | wxTOP | (option.opt.full_width ? wxEXPAND : wxALIGN_CENTER_VERTICAL), (wxOSX || !staticbox) ? 0 : 2);
|
||||
if (is_sizer_field(field))
|
||||
sizer->Add(field->getSizer(), 1, /*(*/option.opt.full_width ? wxEXPAND : /*0) |*/ wxALIGN_CENTER_VERTICAL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto opt : option_set) {
|
||||
ConfigOptionDef option = opt.opt;
|
||||
wxSizer* sizer_tmp = sizer;
|
||||
// add label if any
|
||||
if (option.label != "") {
|
||||
wxString str_label = _(option.label);
|
||||
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
|
||||
// wxString str_label = (option.label == "Top" || option.label == "Bottom") ?
|
||||
// wxGETTEXT_IN_CONTEXT("Layers", wxString(option.label.c_str()):
|
||||
// L_str(option.label);
|
||||
label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize);
|
||||
label->SetFont(label_font);
|
||||
sizer_tmp->Add(label, 0, /*wxALIGN_RIGHT |*/ wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
|
||||
// add field
|
||||
const Option& opt_ref = opt;
|
||||
auto& field = build_field(opt_ref, label);
|
||||
add_undo_buttuns_to_sizer(sizer_tmp, field);
|
||||
is_sizer_field(field) ?
|
||||
sizer_tmp->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) :
|
||||
sizer_tmp->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
|
||||
// add sidetext if any
|
||||
if (option.sidetext != "") {
|
||||
auto sidetext = new wxStaticText( parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition,
|
||||
wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT);
|
||||
sidetext->SetFont(sidetext_font);
|
||||
sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
|
||||
field->set_side_text_ptr(sidetext);
|
||||
}
|
||||
|
||||
// add side widget if any
|
||||
if (opt.side_widget != nullptr) {
|
||||
sizer_tmp->Add(opt.side_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification
|
||||
}
|
||||
|
||||
if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back())
|
||||
{
|
||||
sizer_tmp->AddSpacer(6);
|
||||
}
|
||||
}
|
||||
// add extra sizers if any
|
||||
for (auto extra_widget : line.get_extra_widgets()) {
|
||||
sizer->Add(extra_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification
|
||||
}
|
||||
}
|
||||
|
||||
Line OptionsGroup::create_single_option_line(const Option& option) const {
|
||||
Line retval{ _(option.opt.label), _(option.opt.tooltip) };
|
||||
Option tmp(option);
|
||||
tmp.opt.label = std::string("");
|
||||
retval.append_option(tmp);
|
||||
return retval;
|
||||
}
|
||||
|
||||
void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) {
|
||||
if (m_on_change != nullptr)
|
||||
m_on_change(opt_id, value);
|
||||
}
|
||||
|
||||
Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/)
|
||||
{
|
||||
if (!m_config->has(opt_key)) {
|
||||
std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.\n";
|
||||
}
|
||||
|
||||
std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index);
|
||||
std::pair<std::string, int> pair(opt_key, opt_index);
|
||||
m_opt_map.emplace(opt_id, pair);
|
||||
|
||||
return Option(*m_config->def()->get(opt_key), opt_id);
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value)
|
||||
{
|
||||
if (!m_opt_map.empty())
|
||||
{
|
||||
auto it = m_opt_map.find(opt_id);
|
||||
if (it == m_opt_map.end())
|
||||
{
|
||||
OptionsGroup::on_change_OG(opt_id, value);
|
||||
return;
|
||||
}
|
||||
|
||||
auto itOption = it->second;
|
||||
std::string opt_key = itOption.first;
|
||||
int opt_index = itOption.second;
|
||||
|
||||
auto option = m_options.at(opt_id).opt;
|
||||
|
||||
// get value
|
||||
//! auto field_value = get_value(opt_id);
|
||||
if (option.gui_flags.compare("serialized")==0) {
|
||||
if (opt_index != -1){
|
||||
// die "Can't set serialized option indexed value" ;
|
||||
}
|
||||
change_opt_value(*m_config, opt_key, value);
|
||||
}
|
||||
else {
|
||||
if (opt_index == -1) {
|
||||
// change_opt_value(*m_config, opt_key, field_value);
|
||||
//!? why field_value?? in this case changed value will be lose! No?
|
||||
change_opt_value(*m_config, opt_key, value);
|
||||
}
|
||||
else {
|
||||
change_opt_value(*m_config, opt_key, value, opt_index);
|
||||
// auto value = m_config->get($opt_key);
|
||||
// $value->[$opt_index] = $field_value;
|
||||
// $self->config->set($opt_key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key)
|
||||
{
|
||||
if (m_get_initial_config == nullptr)
|
||||
return;
|
||||
back_to_config_value(m_get_initial_config(), opt_key);
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::back_to_sys_value(const std::string& opt_key)
|
||||
{
|
||||
if (m_get_sys_config == nullptr)
|
||||
return;
|
||||
if (!have_sys_config())
|
||||
return;
|
||||
back_to_config_value(m_get_sys_config(), opt_key);
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key)
|
||||
{
|
||||
boost::any value;
|
||||
if (opt_key == "extruders_count"){
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
|
||||
value = int(nozzle_diameter->values.size());
|
||||
}
|
||||
else if (m_opt_map.find(opt_key) != m_opt_map.end())
|
||||
{
|
||||
auto opt_id = m_opt_map.find(opt_key)->first;
|
||||
std::string opt_short_key = m_opt_map.at(opt_id).first;
|
||||
int opt_index = m_opt_map.at(opt_id).second;
|
||||
value = get_config_value(config, opt_short_key, opt_index);
|
||||
}
|
||||
else{
|
||||
value = get_config_value(config, opt_key);
|
||||
change_opt_value(*m_config, opt_key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
set_value(opt_key, value);
|
||||
on_change_OG(opt_key, get_value(opt_key));
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::reload_config(){
|
||||
for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
|
||||
auto opt_id = it->first;
|
||||
std::string opt_key = m_opt_map.at(opt_id).first;
|
||||
int opt_index = m_opt_map.at(opt_id).second;
|
||||
auto option = m_options.at(opt_id).opt;
|
||||
set_value(opt_id, config_value(opt_key, opt_index, option.gui_flags.compare("serialized") == 0 ));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ConfigOptionsGroup::update_visibility(ConfigOptionMode mode) {
|
||||
if (m_options_mode.empty())
|
||||
return true;
|
||||
if (m_grid_sizer->GetEffectiveRowsCount() != m_options_mode.size() &&
|
||||
m_options_mode.size() == 1)
|
||||
return m_options_mode[0] <= mode;
|
||||
|
||||
sizer->ShowItems(true);
|
||||
#ifdef __WXGTK__
|
||||
m_panel->Show(true);
|
||||
m_grid_sizer->Show(true);
|
||||
#endif /* __WXGTK__ */
|
||||
|
||||
int coef = 0;
|
||||
int hidden_row_cnt = 0;
|
||||
const int cols = m_grid_sizer->GetCols();
|
||||
for (auto opt_mode : m_options_mode) {
|
||||
const bool show = opt_mode <= mode;
|
||||
if (!show) {
|
||||
hidden_row_cnt++;
|
||||
for (int i = 0; i < cols; ++i)
|
||||
m_grid_sizer->Show(coef + i, show);
|
||||
}
|
||||
coef+= cols;
|
||||
}
|
||||
|
||||
if (hidden_row_cnt == m_options_mode.size()) {
|
||||
sizer->ShowItems(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_index, bool deserialize){
|
||||
|
||||
if (deserialize) {
|
||||
// Want to edit a vector value(currently only multi - strings) in a single edit box.
|
||||
// Aggregate the strings the old way.
|
||||
// Currently used for the post_process config value only.
|
||||
if (opt_index != -1)
|
||||
throw std::out_of_range("Can't deserialize option indexed value");
|
||||
// return join(';', m_config->get(opt_key)});
|
||||
return get_config_value(*m_config, opt_key);
|
||||
}
|
||||
else {
|
||||
// return opt_index == -1 ? m_config->get(opt_key) : m_config->get_at(opt_key, opt_index);
|
||||
return get_config_value(*m_config, opt_key, opt_index);
|
||||
}
|
||||
}
|
||||
|
||||
boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index /*= -1*/)
|
||||
{
|
||||
size_t idx = opt_index == -1 ? 0 : opt_index;
|
||||
|
||||
boost::any ret;
|
||||
wxString text_value = wxString("");
|
||||
const ConfigOptionDef* opt = config.def()->get(opt_key);
|
||||
switch (opt->type){
|
||||
case coFloatOrPercent:{
|
||||
const auto &value = *config.option<ConfigOptionFloatOrPercent>(opt_key);
|
||||
if (value.percent)
|
||||
{
|
||||
text_value = wxString::Format(_T("%i"), int(value.value));
|
||||
text_value += "%";
|
||||
}
|
||||
else
|
||||
text_value = double_to_string(value.value);
|
||||
ret = text_value;
|
||||
break;
|
||||
}
|
||||
case coPercent:{
|
||||
double val = config.option<ConfigOptionPercent>(opt_key)->value;
|
||||
text_value = wxString::Format(_T("%i"), int(val));
|
||||
ret = text_value;// += "%";
|
||||
}
|
||||
break;
|
||||
case coPercents:
|
||||
case coFloats:
|
||||
case coFloat:{
|
||||
double val = opt->type == coFloats ?
|
||||
config.opt_float(opt_key, idx) :
|
||||
opt->type == coFloat ? config.opt_float(opt_key) :
|
||||
config.option<ConfigOptionPercents>(opt_key)->get_at(idx);
|
||||
ret = double_to_string(val);
|
||||
}
|
||||
break;
|
||||
case coString:
|
||||
ret = static_cast<wxString>(config.opt_string(opt_key));
|
||||
break;
|
||||
case coStrings:
|
||||
if (opt_key.compare("compatible_printers") == 0){
|
||||
ret = config.option<ConfigOptionStrings>(opt_key)->values;
|
||||
break;
|
||||
}
|
||||
if (config.option<ConfigOptionStrings>(opt_key)->values.empty())
|
||||
ret = text_value;
|
||||
else if (opt->gui_flags.compare("serialized") == 0){
|
||||
std::vector<std::string> values = config.option<ConfigOptionStrings>(opt_key)->values;
|
||||
if (!values.empty() && values[0].compare("") != 0)
|
||||
for (auto el : values)
|
||||
text_value += el + ";";
|
||||
ret = text_value;
|
||||
}
|
||||
else
|
||||
ret = static_cast<wxString>(config.opt_string(opt_key, static_cast<unsigned int>(idx)));
|
||||
break;
|
||||
case coBool:
|
||||
ret = config.opt_bool(opt_key);
|
||||
break;
|
||||
case coBools:
|
||||
ret = config.opt_bool(opt_key, idx);
|
||||
break;
|
||||
case coInt:
|
||||
ret = config.opt_int(opt_key);
|
||||
break;
|
||||
case coInts:
|
||||
ret = config.opt_int(opt_key, idx);
|
||||
break;
|
||||
case coEnum:{
|
||||
if (opt_key.compare("external_fill_pattern") == 0 ||
|
||||
opt_key.compare("fill_pattern") == 0 ){
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<InfillPattern>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("gcode_flavor") == 0 ){
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<GCodeFlavor>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("support_material_pattern") == 0){
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<SupportMaterialPattern>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("seam_position") == 0){
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<SeamPosition>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("host_type") == 0){
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<PrintHostType>>(opt_key)->value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case coPoints:
|
||||
if (opt_key.compare("bed_shape") == 0)
|
||||
ret = config.option<ConfigOptionPoints>(opt_key)->values;
|
||||
else
|
||||
ret = config.option<ConfigOptionPoints>(opt_key)->get_at(idx);
|
||||
break;
|
||||
case coNone:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int opt_index){
|
||||
Field* field = get_field(opt_key);
|
||||
if (field != nullptr)
|
||||
return field;
|
||||
std::string opt_id = "";
|
||||
for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
|
||||
if (opt_key == m_opt_map.at(it->first).first && opt_index == m_opt_map.at(it->first).second){
|
||||
opt_id = it->first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return opt_id.empty() ? nullptr : get_field(opt_id);
|
||||
}
|
||||
|
||||
void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
|
||||
{
|
||||
SetLabel(value);
|
||||
if (wrap) Wrap(400);
|
||||
GetParent()->Layout();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
269
src/slic3r/GUI/OptionsGroup.hpp
Normal file
269
src/slic3r/GUI/OptionsGroup.hpp
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
#ifndef slic3r_OptionsGroup_hpp_
|
||||
#define slic3r_OptionsGroup_hpp_
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/settings.h>
|
||||
//#include <wx/window.h>
|
||||
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include "Field.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
// Translate the ifdef
|
||||
#ifdef __WXOSX__
|
||||
#define wxOSX true
|
||||
#else
|
||||
#define wxOSX false
|
||||
#endif
|
||||
|
||||
#define BORDER(a, b) ((wxOSX ? a : b))
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
/// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window).
|
||||
using widget_t = std::function<wxSizer*(wxWindow*)>;//!std::function<wxWindow*(wxWindow*)>;
|
||||
|
||||
//auto default_label_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
|
||||
//auto modified_label_clr = *new wxColour(254, 189, 101);
|
||||
|
||||
/// Wraps a ConfigOptionDef and adds function object for creating a side_widget.
|
||||
struct Option {
|
||||
ConfigOptionDef opt { ConfigOptionDef() };
|
||||
t_config_option_key opt_id;//! {""};
|
||||
widget_t side_widget {nullptr};
|
||||
bool readonly {false};
|
||||
|
||||
Option(const ConfigOptionDef& _opt, t_config_option_key id) :
|
||||
opt(_opt), opt_id(id) {}
|
||||
};
|
||||
using t_option = std::unique_ptr<Option>; //!
|
||||
|
||||
/// Represents option lines
|
||||
class Line {
|
||||
public:
|
||||
wxString label {wxString("")};
|
||||
wxString label_tooltip {wxString("")};
|
||||
size_t full_width {0};
|
||||
wxSizer* sizer {nullptr};
|
||||
widget_t widget {nullptr};
|
||||
std::function<wxWindow*(wxWindow*)> near_label_widget{ nullptr };
|
||||
|
||||
void append_option(const Option& option) {
|
||||
m_options.push_back(option);
|
||||
}
|
||||
void append_widget(const widget_t widget) {
|
||||
m_extra_widgets.push_back(widget);
|
||||
}
|
||||
Line(wxString label, wxString tooltip) :
|
||||
label(label), label_tooltip(tooltip) {}
|
||||
|
||||
const std::vector<widget_t>& get_extra_widgets() const {return m_extra_widgets;}
|
||||
const std::vector<Option>& get_options() const { return m_options; }
|
||||
|
||||
private:
|
||||
std::vector<Option> m_options;//! {std::vector<Option>()};
|
||||
std::vector<widget_t> m_extra_widgets;//! {std::vector<widget_t>()};
|
||||
};
|
||||
|
||||
using column_t = std::function<wxWindow*(wxWindow* parent, const Line&)>;//std::function<wxSizer*(const Line&)>;
|
||||
|
||||
using t_optionfield_map = std::map<t_config_option_key, t_field>;
|
||||
using t_opt_map = std::map< std::string, std::pair<std::string, int> >;
|
||||
|
||||
class OptionsGroup {
|
||||
wxStaticBox* stb;
|
||||
public:
|
||||
const bool staticbox {true};
|
||||
const wxString title {wxString("")};
|
||||
size_t label_width {200};
|
||||
wxSizer* sizer {nullptr};
|
||||
column_t extra_column {nullptr};
|
||||
t_change m_on_change {nullptr};
|
||||
std::function<DynamicPrintConfig()> m_get_initial_config{ nullptr };
|
||||
std::function<DynamicPrintConfig()> m_get_sys_config{ nullptr };
|
||||
std::function<bool()> have_sys_config{ nullptr };
|
||||
|
||||
wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
|
||||
wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
|
||||
int sidetext_width{ -1 };
|
||||
|
||||
/// Returns a copy of the pointer of the parent wxWindow.
|
||||
/// Accessor function is because users are not allowed to change the parent
|
||||
/// but defining it as const means a lot of const_casts to deal with wx functions.
|
||||
inline wxWindow* parent() const {
|
||||
#ifdef __WXGTK__
|
||||
return m_panel;
|
||||
#else
|
||||
return m_parent;
|
||||
#endif /* __WXGTK__ */
|
||||
}
|
||||
#ifdef __WXGTK__
|
||||
wxWindow* get_parent() const {
|
||||
return m_parent;
|
||||
}
|
||||
#endif /* __WXGTK__ */
|
||||
|
||||
void append_line(const Line& line, wxStaticText** colored_Label = nullptr);
|
||||
Line create_single_option_line(const Option& option) const;
|
||||
void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); }
|
||||
|
||||
// return a non-owning pointer reference
|
||||
inline Field* get_field(const t_config_option_key& id) const{
|
||||
if (m_fields.find(id) == m_fields.end()) return nullptr;
|
||||
return m_fields.at(id).get();
|
||||
}
|
||||
bool set_value(const t_config_option_key& id, const boost::any& value, bool change_event = false) {
|
||||
if (m_fields.find(id) == m_fields.end()) return false;
|
||||
m_fields.at(id)->set_value(value, change_event);
|
||||
return true;
|
||||
}
|
||||
boost::any get_value(const t_config_option_key& id) {
|
||||
boost::any out;
|
||||
if (m_fields.find(id) == m_fields.end()) ;
|
||||
else
|
||||
out = m_fields.at(id)->get_value();
|
||||
return out;
|
||||
}
|
||||
|
||||
bool set_side_text(const t_config_option_key& opt_key, const wxString& side_text) {
|
||||
if (m_fields.find(opt_key) == m_fields.end()) return false;
|
||||
auto st = m_fields.at(opt_key)->m_side_text;
|
||||
if (!st) return false;
|
||||
st->SetLabel(side_text);
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_name(const wxString& new_name) {
|
||||
stb->SetLabel(new_name);
|
||||
}
|
||||
|
||||
inline void enable() { for (auto& field : m_fields) field.second->enable(); }
|
||||
inline void disable() { for (auto& field : m_fields) field.second->disable(); }
|
||||
void set_grid_vgap(int gap) { m_grid_sizer->SetVGap(gap); }
|
||||
|
||||
void set_show_modified_btns_val(bool show) {
|
||||
m_show_modified_btns = show;
|
||||
}
|
||||
|
||||
OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false,
|
||||
column_t extra_clmn = nullptr) :
|
||||
m_parent(_parent), title(title),
|
||||
m_show_modified_btns(is_tab_opt),
|
||||
staticbox(title!=""), extra_column(extra_clmn){
|
||||
if (staticbox) {
|
||||
stb = new wxStaticBox(_parent, wxID_ANY, title);
|
||||
stb->SetFont(wxGetApp().bold_font());
|
||||
}
|
||||
sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL));
|
||||
auto num_columns = 1U;
|
||||
if (label_width != 0) num_columns++;
|
||||
if (extra_column != nullptr) num_columns++;
|
||||
m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0);
|
||||
static_cast<wxFlexGridSizer*>(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/);
|
||||
static_cast<wxFlexGridSizer*>(m_grid_sizer)->AddGrowableCol(label_width == 0 ? 0 : !extra_column ? 1 : 2 );
|
||||
#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};
|
||||
std::vector<ConfigOptionMode> m_options_mode;
|
||||
|
||||
/// Field list, contains unique_ptrs of the derived type.
|
||||
/// using types that need to know what it is beyond the public interface
|
||||
/// need to cast based on the related ConfigOptionDef.
|
||||
t_optionfield_map m_fields;
|
||||
bool m_disabled {false};
|
||||
wxGridSizer* m_grid_sizer {nullptr};
|
||||
// "true" if option is created in preset tabs
|
||||
bool m_show_modified_btns{ false };
|
||||
|
||||
// This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox
|
||||
// Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel
|
||||
// inside it before you insert the other controls.
|
||||
#ifdef __WXGTK__
|
||||
wxPanel* m_panel {nullptr};
|
||||
#endif /* __WXGTK__ */
|
||||
|
||||
/// Generate a wxSizer or wxWindow from a configuration option
|
||||
/// Precondition: opt resolves to a known ConfigOption
|
||||
/// Postcondition: fields contains a wx gui object.
|
||||
const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr);
|
||||
const t_field& build_field(const t_config_option_key& id, wxStaticText* label = nullptr);
|
||||
const t_field& build_field(const Option& opt, wxStaticText* label = nullptr);
|
||||
void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field);
|
||||
|
||||
virtual void on_kill_focus (){};
|
||||
virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
|
||||
virtual void back_to_initial_value(const std::string& opt_key){}
|
||||
virtual void back_to_sys_value(const std::string& opt_key){}
|
||||
};
|
||||
|
||||
class ConfigOptionsGroup: public OptionsGroup {
|
||||
public:
|
||||
ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr,
|
||||
bool is_tab_opt = false, column_t extra_clmn = nullptr) :
|
||||
OptionsGroup(parent, title, is_tab_opt, extra_clmn), m_config(_config) {}
|
||||
|
||||
/// reference to libslic3r config, non-owning pointer (?).
|
||||
DynamicPrintConfig* m_config {nullptr};
|
||||
bool m_full_labels {0};
|
||||
t_opt_map m_opt_map;
|
||||
|
||||
void set_config(DynamicPrintConfig* config){ m_config = config; }
|
||||
Option get_option(const std::string& opt_key, int opt_index = -1);
|
||||
Line create_single_option_line(const std::string& title, int idx = -1) /*const*/{
|
||||
Option option = get_option(title, idx);
|
||||
return OptionsGroup::create_single_option_line(option);
|
||||
}
|
||||
void append_single_option_line(const Option& option) {
|
||||
OptionsGroup::append_single_option_line(option);
|
||||
}
|
||||
void append_single_option_line(const std::string title, int idx = -1)
|
||||
{
|
||||
Option option = get_option(title, idx);
|
||||
append_single_option_line(option);
|
||||
}
|
||||
|
||||
void on_change_OG(const t_config_option_key& opt_id, const boost::any& value) override;
|
||||
void back_to_initial_value(const std::string& opt_key) override;
|
||||
void back_to_sys_value(const std::string& opt_key) override;
|
||||
void back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key);
|
||||
void on_kill_focus() override{ reload_config();}
|
||||
void reload_config();
|
||||
// return value shows visibility : false => all options are hidden
|
||||
bool update_visibility(ConfigOptionMode mode);
|
||||
boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize);
|
||||
// return option value from config
|
||||
boost::any get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1);
|
||||
Field* get_fieldc(const t_config_option_key& opt_key, int opt_index);
|
||||
};
|
||||
|
||||
// Static text shown among the options.
|
||||
class ogStaticText :public wxStaticText{
|
||||
public:
|
||||
ogStaticText() {}
|
||||
ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){}
|
||||
~ogStaticText(){}
|
||||
|
||||
void SetText(const wxString& value, bool wrap = true);
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif /* slic3r_OptionsGroup_hpp_ */
|
||||
2577
src/slic3r/GUI/Plater.cpp
Normal file
2577
src/slic3r/GUI/Plater.cpp
Normal file
File diff suppressed because it is too large
Load diff
140
src/slic3r/GUI/Plater.hpp
Normal file
140
src/slic3r/GUI/Plater.hpp
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#ifndef slic3r_Plater_hpp_
|
||||
#define slic3r_Plater_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
|
||||
#include "Preset.hpp"
|
||||
|
||||
class wxButton;
|
||||
class wxBoxSizer;
|
||||
class wxGLCanvas;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class Print;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class MainFrame;
|
||||
class ConfigOptionsGroup;
|
||||
class ObjectManipulation;
|
||||
class ObjectList;
|
||||
|
||||
using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
|
||||
|
||||
class Plater;
|
||||
|
||||
class PresetComboBox : public wxBitmapComboBox
|
||||
{
|
||||
public:
|
||||
PresetComboBox(wxWindow *parent, Preset::Type preset_type);
|
||||
~PresetComboBox();
|
||||
|
||||
void set_label_marker(int item);
|
||||
void set_extruder_idx(const int extr_idx) { extruder_idx = extr_idx; }
|
||||
int get_extruder_idx() const { return extruder_idx; }
|
||||
|
||||
private:
|
||||
typedef std::size_t Marker;
|
||||
enum { LABEL_ITEM_MARKER = 0x4d };
|
||||
|
||||
Preset::Type preset_type;
|
||||
int last_selected;
|
||||
int extruder_idx = -1;
|
||||
};
|
||||
|
||||
enum ButtonAction
|
||||
{
|
||||
baUndef,
|
||||
baReslice,
|
||||
baExportGcode,
|
||||
baSendGcode
|
||||
};
|
||||
|
||||
class Sidebar : public wxPanel
|
||||
{
|
||||
public:
|
||||
Sidebar(Plater *parent);
|
||||
Sidebar(Sidebar &&) = delete;
|
||||
Sidebar(const Sidebar &) = delete;
|
||||
Sidebar &operator=(Sidebar &&) = delete;
|
||||
Sidebar &operator=(const Sidebar &) = delete;
|
||||
~Sidebar();
|
||||
|
||||
void init_filament_combo(PresetComboBox **combo, const int extr_idx);
|
||||
void remove_unused_filament_combos(const int current_extruder_count);
|
||||
void update_presets(Slic3r::Preset::Type preset_type);
|
||||
|
||||
ObjectManipulation* obj_manipul();
|
||||
ObjectList* obj_list();
|
||||
|
||||
ConfigOptionsGroup* og_freq_chng_params();
|
||||
wxButton* get_wiping_dialog_button();
|
||||
void update_objects_list_extruder_column(int extruders_count);
|
||||
void show_info_sizers(const bool show);
|
||||
void show_buttons(const bool show);
|
||||
void show_button(ButtonAction but_action, bool show);
|
||||
void enable_buttons(bool enable);
|
||||
bool is_multifilament();
|
||||
|
||||
std::vector<PresetComboBox*>& combos_filament();
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
class Plater: public wxPanel
|
||||
{
|
||||
public:
|
||||
Plater(wxWindow *parent, MainFrame *main_frame);
|
||||
Plater(Plater &&) = delete;
|
||||
Plater(const Plater &) = delete;
|
||||
Plater &operator=(Plater &&) = delete;
|
||||
Plater &operator=(const Plater &) = delete;
|
||||
~Plater();
|
||||
|
||||
Sidebar& sidebar();
|
||||
Model& model();
|
||||
Print& print();
|
||||
|
||||
void add();
|
||||
|
||||
void load_files(const std::vector<boost::filesystem::path> &input_files);
|
||||
|
||||
void update(bool force_autocenter = false);
|
||||
void select_view(const std::string& direction);
|
||||
|
||||
void remove(size_t obj_idx);
|
||||
void remove_selected();
|
||||
void increase_instances(size_t num = 1);
|
||||
void decrease_instances(size_t num = 1);
|
||||
void set_number_of_copies(/*size_t num*/);
|
||||
|
||||
// Note: empty path means "use the default"
|
||||
void export_gcode(boost::filesystem::path output_path = boost::filesystem::path());
|
||||
void export_stl();
|
||||
void export_amf();
|
||||
void export_3mf();
|
||||
void reslice();
|
||||
void changed_object_settings(int obj_idx);
|
||||
void send_gcode();
|
||||
|
||||
void on_extruders_change(int extruders_count);
|
||||
void on_config_change(const DynamicPrintConfig &config);
|
||||
|
||||
wxGLCanvas* canvas3D();
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
130
src/slic3r/GUI/Preferences.cpp
Normal file
130
src/slic3r/GUI/Preferences.cpp
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#include "Preferences.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "OptionsGroup.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
PreferencesDialog::PreferencesDialog(wxWindow* parent) :
|
||||
wxDialog(parent, wxID_ANY, _(L("Preferences")), wxDefaultPosition, wxDefaultSize) {
|
||||
build();
|
||||
}
|
||||
|
||||
void PreferencesDialog::build()
|
||||
{
|
||||
auto app_config = get_app_config();
|
||||
m_optgroup = std::make_shared<ConfigOptionsGroup>(this, _(L("General")));
|
||||
m_optgroup->label_width = 400;
|
||||
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
|
||||
};
|
||||
|
||||
// TODO
|
||||
// $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
// opt_id = > 'version_check',
|
||||
// type = > 'bool',
|
||||
// label = > 'Check for updates',
|
||||
// tooltip = > 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
|
||||
// default = > $app_config->get("version_check") // 1,
|
||||
// readonly = > !wxTheApp->have_version_check,
|
||||
// ));
|
||||
|
||||
ConfigOptionDef def;
|
||||
def.label = L("Remember output directory");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If this is enabled, Slic3r will prompt the last output directory "
|
||||
"instead of the one containing the input files.");
|
||||
def.default_value = new ConfigOptionBool{ app_config->has("remember_output_path") ? app_config->get("remember_output_path")[0] == '1' : true }; // 1;
|
||||
Option option(def, "remember_output_path");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("Auto-center parts");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If this is enabled, Slic3r will auto-center objects "
|
||||
"around the print bed center.");
|
||||
def.default_value = new ConfigOptionBool{ app_config->get("autocenter")[0] == '1' }; // 1;
|
||||
option = Option (def,"autocenter");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("Background processing");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If this is enabled, Slic3r will pre-process objects as soon "
|
||||
"as they\'re loaded in order to save time when exporting G-code.");
|
||||
def.default_value = new ConfigOptionBool{ app_config->get("background_processing")[0] == '1' }; // 1;
|
||||
option = Option (def,"background_processing");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
// Please keep in sync with ConfigWizard
|
||||
def.label = L("Check for application updates");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.");
|
||||
def.default_value = new ConfigOptionBool(app_config->get("version_check") == "1");
|
||||
option = Option (def, "version_check");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
// Please keep in sync with ConfigWizard
|
||||
def.label = L("Update built-in Presets automatically");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.");
|
||||
def.default_value = new ConfigOptionBool(app_config->get("preset_update") == "1");
|
||||
option = Option (def, "preset_update");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("Suppress \" - default - \" presets");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("Suppress \" - default - \" presets in the Print / Filament / Printer "
|
||||
"selections once there are any other valid presets available.");
|
||||
def.default_value = new ConfigOptionBool{ app_config->get("no_defaults")[0] == '1' }; // 1;
|
||||
option = Option (def,"no_defaults");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("Show incompatible print and filament presets");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("When checked, the print and filament presets are shown in the preset editor "
|
||||
"even if they are marked as incompatible with the active printer");
|
||||
def.default_value = new ConfigOptionBool{ app_config->get("show_incompatible_presets")[0] == '1' }; // 1;
|
||||
option = Option (def,"show_incompatible_presets");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("Use legacy OpenGL 1.1 rendering");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, "
|
||||
"you may try to check this checkbox. This will disable the layer height "
|
||||
"editing and anti aliasing, so it is likely better to upgrade your graphics driver.");
|
||||
def.default_value = new ConfigOptionBool{ app_config->get("use_legacy_opengl")[0] == '1' }; // 1;
|
||||
option = Option (def,"use_legacy_opengl");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
|
||||
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
|
||||
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
|
||||
SetSizer(sizer);
|
||||
sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
void PreferencesDialog::accept()
|
||||
{
|
||||
if (m_values.find("no_defaults") != m_values.end()||
|
||||
m_values.find("use_legacy_opengl")!= m_values.end()) {
|
||||
warning_catcher(this, _(L("You need to restart Slic3r to make the changes effective.")));
|
||||
}
|
||||
|
||||
auto app_config = get_app_config();
|
||||
for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it) {
|
||||
app_config->set(it->first, it->second);
|
||||
}
|
||||
|
||||
EndModal(wxID_OK);
|
||||
Close(); // needed on Linux
|
||||
|
||||
// Nothify the UI to update itself from the ini file.
|
||||
wxGetApp().update_ui_from_settings();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
30
src/slic3r/GUI/Preferences.hpp
Normal file
30
src/slic3r/GUI/Preferences.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef slic3r_Preferences_hpp_
|
||||
#define slic3r_Preferences_hpp_
|
||||
|
||||
#include "GUI.hpp"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <map>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class ConfigOptionsGroup;
|
||||
|
||||
class PreferencesDialog : public wxDialog
|
||||
{
|
||||
std::map<std::string, std::string> m_values;
|
||||
std::shared_ptr<ConfigOptionsGroup> m_optgroup;
|
||||
public:
|
||||
PreferencesDialog(wxWindow* parent);
|
||||
~PreferencesDialog(){ }
|
||||
|
||||
void build();
|
||||
void accept();
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
|
||||
#endif /* slic3r_Preferences_hpp_ */
|
||||
1021
src/slic3r/GUI/Preset.cpp
Normal file
1021
src/slic3r/GUI/Preset.cpp
Normal file
File diff suppressed because it is too large
Load diff
445
src/slic3r/GUI/Preset.hpp
Normal file
445
src/slic3r/GUI/Preset.hpp
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
#ifndef slic3r_Preset_hpp_
|
||||
#define slic3r_Preset_hpp_
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/property_tree/ptree_fwd.hpp>
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/Utils/Semver.hpp"
|
||||
|
||||
class wxBitmap;
|
||||
class wxChoice;
|
||||
class wxBitmapComboBox;
|
||||
class wxItemContainer;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig;
|
||||
class PresetBundle;
|
||||
|
||||
namespace GUI {
|
||||
class BitmapCache;
|
||||
class PresetComboBox;
|
||||
}
|
||||
|
||||
enum ConfigFileType
|
||||
{
|
||||
CONFIG_FILE_TYPE_UNKNOWN,
|
||||
CONFIG_FILE_TYPE_APP_CONFIG,
|
||||
CONFIG_FILE_TYPE_CONFIG,
|
||||
CONFIG_FILE_TYPE_CONFIG_BUNDLE,
|
||||
};
|
||||
|
||||
extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree);
|
||||
|
||||
class VendorProfile
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
std::string id;
|
||||
Semver config_version;
|
||||
std::string config_update_url;
|
||||
|
||||
struct PrinterVariant {
|
||||
PrinterVariant() {}
|
||||
PrinterVariant(const std::string &name) : name(name) {}
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct PrinterModel {
|
||||
PrinterModel() {}
|
||||
std::string id;
|
||||
std::string name;
|
||||
PrinterTechnology technology;
|
||||
std::vector<PrinterVariant> variants;
|
||||
PrinterVariant* variant(const std::string &name) {
|
||||
for (auto &v : this->variants)
|
||||
if (v.name == name)
|
||||
return &v;
|
||||
return nullptr;
|
||||
}
|
||||
const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
|
||||
};
|
||||
std::vector<PrinterModel> models;
|
||||
|
||||
VendorProfile() {}
|
||||
VendorProfile(std::string id) : id(std::move(id)) {}
|
||||
|
||||
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
|
||||
static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true);
|
||||
|
||||
size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; }
|
||||
|
||||
bool operator< (const VendorProfile &rhs) const { return this->id < rhs.id; }
|
||||
bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; }
|
||||
};
|
||||
|
||||
class Preset
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
TYPE_INVALID,
|
||||
TYPE_PRINT,
|
||||
TYPE_FILAMENT,
|
||||
TYPE_SLA_MATERIAL,
|
||||
TYPE_PRINTER,
|
||||
};
|
||||
|
||||
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
|
||||
|
||||
Type type = TYPE_INVALID;
|
||||
|
||||
// The preset represents a "default" set of properties,
|
||||
// pulled from the default values of the PrintConfig (see PrintConfigDef for their definitions).
|
||||
bool is_default;
|
||||
// External preset points to a configuration, which has been loaded but not imported
|
||||
// into the Slic3r default configuration location.
|
||||
bool is_external = false;
|
||||
// System preset is read-only.
|
||||
bool is_system = false;
|
||||
// Preset is visible, if it is associated with a printer model / variant that is enabled in the AppConfig
|
||||
// or if it has no printer model / variant association.
|
||||
// Also the "default" preset is only visible, if it is the only preset in the list.
|
||||
bool is_visible = true;
|
||||
// Has this preset been modified?
|
||||
bool is_dirty = false;
|
||||
// Is this preset compatible with the currently active printer?
|
||||
bool is_compatible = true;
|
||||
|
||||
// Name of the preset, usually derived form the file name.
|
||||
std::string name;
|
||||
// File name of the preset. This could be a Print / Filament / Printer preset,
|
||||
// or a Configuration file bundling the Print + Filament + Printer presets (in that case is_external and possibly is_system will be true),
|
||||
// or it could be a G-code (again, is_external will be true).
|
||||
std::string file;
|
||||
// If this is a system profile, then there should be a vendor data available to display at the UI.
|
||||
const VendorProfile *vendor = nullptr;
|
||||
|
||||
// Has this profile been loaded?
|
||||
bool loaded = false;
|
||||
|
||||
// Configuration data, loaded from a file, or set from the defaults.
|
||||
DynamicPrintConfig config;
|
||||
|
||||
// Load this profile for the following keys only.
|
||||
DynamicPrintConfig& load(const std::vector<std::string> &keys, const StaticPrintConfig &defaults);
|
||||
|
||||
void save();
|
||||
|
||||
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
|
||||
std::string label() const;
|
||||
|
||||
// Set the is_dirty flag if the provided config is different from the active one.
|
||||
void set_dirty(const DynamicPrintConfig &config) { this->is_dirty = ! this->config.diff(config).empty(); }
|
||||
void set_dirty(bool dirty = true) { this->is_dirty = dirty; }
|
||||
void reset_dirty() { this->is_dirty = false; }
|
||||
|
||||
bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const;
|
||||
bool is_compatible_with_printer(const Preset &active_printer) const;
|
||||
|
||||
// Returns the name of the preset, from which this preset inherits.
|
||||
static std::string& inherits(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("inherits", true)->value; }
|
||||
std::string& inherits() { return Preset::inherits(this->config); }
|
||||
const std::string& inherits() const { return Preset::inherits(const_cast<Preset*>(this)->config); }
|
||||
|
||||
// Returns the "compatible_printers_condition".
|
||||
static std::string& compatible_printers_condition(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("compatible_printers_condition", true)->value; }
|
||||
std::string& compatible_printers_condition() { return Preset::compatible_printers_condition(this->config); }
|
||||
const std::string& compatible_printers_condition() const { return Preset::compatible_printers_condition(const_cast<Preset*>(this)->config); }
|
||||
|
||||
static PrinterTechnology& printer_technology(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value; }
|
||||
PrinterTechnology& printer_technology() { return Preset::printer_technology(this->config); }
|
||||
const PrinterTechnology& printer_technology() const { return Preset::printer_technology(const_cast<Preset*>(this)->config); }
|
||||
|
||||
// Mark this preset as compatible if it is compatible with active_printer.
|
||||
bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config);
|
||||
|
||||
// Set is_visible according to application config
|
||||
void set_visible_from_appconfig(const AppConfig &app_config);
|
||||
|
||||
// Resize the extruder specific fields, initialize them with the content of the 1st extruder.
|
||||
void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }
|
||||
|
||||
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
|
||||
bool operator<(const Preset &other) const { return this->name < other.name; }
|
||||
|
||||
static const std::vector<std::string>& print_options();
|
||||
static const std::vector<std::string>& filament_options();
|
||||
// Printer options contain the nozzle options.
|
||||
static const std::vector<std::string>& printer_options();
|
||||
// Nozzle options of the printer options.
|
||||
static const std::vector<std::string>& nozzle_options();
|
||||
|
||||
static const std::vector<std::string>& sla_printer_options();
|
||||
static const std::vector<std::string>& sla_material_options();
|
||||
|
||||
static void update_suffix_modified();
|
||||
static void normalize(DynamicPrintConfig &config);
|
||||
|
||||
protected:
|
||||
friend class PresetCollection;
|
||||
friend class PresetBundle;
|
||||
// Resize the extruder specific vectors ()
|
||||
static void set_num_extruders(DynamicPrintConfig &config, unsigned int n);
|
||||
static const std::string& suffix_modified();
|
||||
static std::string remove_suffix_modified(const std::string &name);
|
||||
};
|
||||
|
||||
// Collections of presets of the same type (one of the Print, Filament or Printer type).
|
||||
class PresetCollection
|
||||
{
|
||||
public:
|
||||
// Initialize the PresetCollection with the "- default -" preset.
|
||||
PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -");
|
||||
~PresetCollection();
|
||||
|
||||
typedef std::deque<Preset>::iterator Iterator;
|
||||
typedef std::deque<Preset>::const_iterator ConstIterator;
|
||||
Iterator begin() { return m_presets.begin() + m_num_default_presets; }
|
||||
ConstIterator begin() const { return m_presets.begin() + m_num_default_presets; }
|
||||
Iterator end() { return m_presets.end(); }
|
||||
ConstIterator end() const { return m_presets.end(); }
|
||||
|
||||
void reset(bool delete_files);
|
||||
|
||||
Preset::Type type() const { return m_type; }
|
||||
std::string name() const;
|
||||
const std::deque<Preset>& operator()() const { return m_presets; }
|
||||
|
||||
// Add default preset at the start of the collection, increment the m_default_preset counter.
|
||||
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
|
||||
|
||||
// Load ini files of the particular type from the provided directory path.
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir);
|
||||
|
||||
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
|
||||
// and select it, losing previous modifications.
|
||||
Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true);
|
||||
Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true);
|
||||
|
||||
Preset& load_external_preset(
|
||||
// Path to the profile source file (a G-code, an AMF or 3MF file, a config file)
|
||||
const std::string &path,
|
||||
// Name of the profile, derived from the source file name.
|
||||
const std::string &name,
|
||||
// Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored.
|
||||
const std::string &original_name,
|
||||
// Config to initialize the preset from.
|
||||
const DynamicPrintConfig &config,
|
||||
// Select the preset after loading?
|
||||
bool select = true);
|
||||
|
||||
// Save the preset under a new name. If the name is different from the old one,
|
||||
// a new preset is stored into the list of presets.
|
||||
// All presets are marked as not modified and the new preset is activated.
|
||||
void save_current_preset(const std::string &new_name);
|
||||
|
||||
// Delete the current preset, activate the first visible preset.
|
||||
void delete_current_preset();
|
||||
|
||||
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
|
||||
bool load_bitmap_default(const std::string &file_name);
|
||||
|
||||
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
|
||||
void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; }
|
||||
void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; }
|
||||
void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; }
|
||||
void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; }
|
||||
|
||||
// Enable / disable the "- default -" preset.
|
||||
void set_default_suppressed(bool default_suppressed);
|
||||
bool is_default_suppressed() const { return m_default_suppressed; }
|
||||
|
||||
// Select a preset. If an invalid index is provided, the first visible preset is selected.
|
||||
Preset& select_preset(size_t idx);
|
||||
// Return the selected preset, without the user modifications applied.
|
||||
Preset& get_selected_preset() { return m_presets[m_idx_selected]; }
|
||||
const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; }
|
||||
int get_selected_idx() const { return m_idx_selected; }
|
||||
// Returns the name of the selected preset, or an empty string if no preset is selected.
|
||||
std::string get_selected_preset_name() const { return (m_idx_selected == -1) ? std::string() : this->get_selected_preset().name; }
|
||||
// For the current edited preset, return the parent preset if there is one.
|
||||
// If there is no parent preset, nullptr is returned.
|
||||
// The parent preset may be a system preset or a user preset, which will be
|
||||
// reflected by the UI.
|
||||
const Preset* get_selected_preset_parent() const;
|
||||
// get parent preset for some child preset
|
||||
const Preset* get_preset_parent(const Preset& child) const;
|
||||
// Return the selected preset including the user modifications.
|
||||
Preset& get_edited_preset() { return m_edited_preset; }
|
||||
const Preset& get_edited_preset() const { return m_edited_preset; }
|
||||
|
||||
// used to update preset_choice from Tab
|
||||
const std::deque<Preset>& get_presets() { return m_presets; }
|
||||
int get_idx_selected() { return m_idx_selected; }
|
||||
static const std::string& get_suffix_modified();
|
||||
|
||||
// Return a preset possibly with modifications.
|
||||
Preset& default_preset() { return m_presets.front(); }
|
||||
const Preset& default_preset() const { return m_presets.front(); }
|
||||
// Return a preset by an index. If the preset is active, a temporary copy is returned.
|
||||
Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; }
|
||||
const Preset& preset(size_t idx) const { return const_cast<PresetCollection*>(this)->preset(idx); }
|
||||
void discard_current_changes() { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; }
|
||||
|
||||
// Return a preset by its name. If the preset is active, a temporary copy is returned.
|
||||
// If a preset is not found by its name, null is returned.
|
||||
Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false);
|
||||
const Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false) const
|
||||
{ return const_cast<PresetCollection*>(this)->find_preset(name, first_visible_if_not_found); }
|
||||
|
||||
size_t first_visible_idx() const;
|
||||
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
|
||||
// If one of the prefered_alternates is compatible, select it.
|
||||
template<typename PreferedCondition>
|
||||
size_t first_compatible_idx(PreferedCondition prefered_condition) const
|
||||
{
|
||||
size_t i = m_default_suppressed ? m_num_default_presets : 0;
|
||||
size_t n = this->m_presets.size();
|
||||
size_t i_compatible = n;
|
||||
for (; i < n; ++ i)
|
||||
if (m_presets[i].is_compatible) {
|
||||
if (prefered_condition(m_presets[i].name))
|
||||
return i;
|
||||
if (i_compatible == n)
|
||||
// Store the first compatible profile into i_compatible.
|
||||
i_compatible = i;
|
||||
}
|
||||
return (i_compatible == n) ? 0 : i_compatible;
|
||||
}
|
||||
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
|
||||
size_t first_compatible_idx() const { return this->first_compatible_idx([](const std::string&){return true;}); }
|
||||
|
||||
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
|
||||
// Return the first visible preset. Certainly at least the '- default -' preset shall be visible.
|
||||
Preset& first_visible() { return this->preset(this->first_visible_idx()); }
|
||||
const Preset& first_visible() const { return this->preset(this->first_visible_idx()); }
|
||||
Preset& first_compatible() { return this->preset(this->first_compatible_idx()); }
|
||||
template<typename PreferedCondition>
|
||||
Preset& first_compatible(PreferedCondition prefered_condition) { return this->preset(this->first_compatible_idx(prefered_condition)); }
|
||||
const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); }
|
||||
|
||||
// Return number of presets including the "- default -" preset.
|
||||
size_t size() const { return m_presets.size(); }
|
||||
bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; }
|
||||
|
||||
// For Print / Filament presets, disable those, which are not compatible with the printer.
|
||||
template<typename PreferedCondition>
|
||||
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible, PreferedCondition prefered_condition)
|
||||
{
|
||||
if (this->update_compatible_with_printer_internal(active_printer, select_other_if_incompatible) == (size_t)-1)
|
||||
// Find some other compatible preset, or the "-- default --" preset.
|
||||
this->select_preset(this->first_compatible_idx(prefered_condition));
|
||||
}
|
||||
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible)
|
||||
{ this->update_compatible_with_printer(active_printer, select_other_if_incompatible, [](const std::string&){return true;}); }
|
||||
|
||||
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
|
||||
|
||||
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
|
||||
bool current_is_dirty() const { return ! this->current_dirty_options().empty(); }
|
||||
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
|
||||
std::vector<std::string> current_dirty_options(const bool deep_compare = false) const
|
||||
{ return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); }
|
||||
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
|
||||
std::vector<std::string> current_different_from_parent_options(const bool deep_compare = false) const
|
||||
{ return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); }
|
||||
|
||||
// Update the choice UI from the list of presets.
|
||||
// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
|
||||
// If an incompatible preset is selected, it is shown as well.
|
||||
size_t update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible);
|
||||
// Update the choice UI from the list of presets.
|
||||
// Only the compatible presets are shown.
|
||||
// If an incompatible preset is selected, it is shown as well.
|
||||
void update_platter_ui(GUI::PresetComboBox *ui);
|
||||
|
||||
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
|
||||
// Return true if the dirty flag changed.
|
||||
bool update_dirty_ui(wxBitmapComboBox *ui);
|
||||
|
||||
// Select a profile by its name. Return true if the selection changed.
|
||||
// Without force, the selection is only updated if the index changes.
|
||||
// With force, the changes are reverted if the new index is the same as the old index.
|
||||
bool select_preset_by_name(const std::string &name, bool force);
|
||||
|
||||
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
|
||||
std::string path_from_name(const std::string &new_name) const;
|
||||
|
||||
protected:
|
||||
// Select a preset, if it exists. If it does not exist, select an invalid (-1) index.
|
||||
// This is a temporary state, which shall be fixed immediately by the following step.
|
||||
bool select_preset_by_name_strict(const std::string &name);
|
||||
|
||||
// Merge one vendor's presets with the other vendor's presets, report duplicates.
|
||||
std::vector<std::string> merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors);
|
||||
|
||||
private:
|
||||
PresetCollection();
|
||||
PresetCollection(const PresetCollection &other);
|
||||
PresetCollection& operator=(const PresetCollection &other);
|
||||
|
||||
// Find a preset position in the sorted list of presets.
|
||||
// The "-- default -- " preset is always the first, so it needs
|
||||
// to be handled differently.
|
||||
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
|
||||
std::deque<Preset>::iterator find_preset_internal(const std::string &name)
|
||||
{
|
||||
Preset key(m_type, name);
|
||||
auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
|
||||
if (it == m_presets.end() || it->name != name) {
|
||||
// Preset has not been not found in the sorted list of non-default presets. Try the defaults.
|
||||
for (size_t i = 0; i < m_num_default_presets; ++ i)
|
||||
if (m_presets[i].name == name) {
|
||||
it = m_presets.begin() + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return it;
|
||||
}
|
||||
std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const
|
||||
{ return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
|
||||
|
||||
size_t update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible);
|
||||
|
||||
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
|
||||
|
||||
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
|
||||
Preset::Type m_type;
|
||||
// List of presets, starting with the "- default -" preset.
|
||||
// Use deque to force the container to allocate an object per each entry,
|
||||
// so that the addresses of the presets don't change during resizing of the container.
|
||||
std::deque<Preset> m_presets;
|
||||
// Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user.
|
||||
Preset m_edited_preset;
|
||||
// Selected preset.
|
||||
int m_idx_selected;
|
||||
// Is the "- default -" preset suppressed?
|
||||
bool m_default_suppressed = true;
|
||||
size_t m_num_default_presets = 0;
|
||||
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter.
|
||||
// These bitmaps are not owned by PresetCollection, but by a PresetBundle.
|
||||
const wxBitmap *m_bitmap_compatible = nullptr;
|
||||
const wxBitmap *m_bitmap_incompatible = nullptr;
|
||||
const wxBitmap *m_bitmap_lock = nullptr;
|
||||
const wxBitmap *m_bitmap_lock_open = nullptr;
|
||||
// Marks placed at the wxBitmapComboBox of a MainFrame.
|
||||
// These bitmaps are owned by PresetCollection.
|
||||
wxBitmap *m_bitmap_main_frame;
|
||||
// Path to the directory to store the config files into.
|
||||
std::string m_dir_path;
|
||||
|
||||
// Caching color bitmaps for the filament combo box.
|
||||
GUI::BitmapCache *m_bitmap_cache = nullptr;
|
||||
|
||||
// to access select_preset_by_name_strict()
|
||||
friend class PresetBundle;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Preset_hpp_ */
|
||||
1403
src/slic3r/GUI/PresetBundle.cpp
Normal file
1403
src/slic3r/GUI/PresetBundle.cpp
Normal file
File diff suppressed because it is too large
Load diff
173
src/slic3r/GUI/PresetBundle.hpp
Normal file
173
src/slic3r/GUI/PresetBundle.hpp
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#ifndef slic3r_PresetBundle_hpp_
|
||||
#define slic3r_PresetBundle_hpp_
|
||||
|
||||
#include "AppConfig.hpp"
|
||||
#include "Preset.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
class BitmapCache;
|
||||
};
|
||||
|
||||
class PlaceholderParser;
|
||||
|
||||
// Bundle of Print + Filament + Printer presets.
|
||||
class PresetBundle
|
||||
{
|
||||
public:
|
||||
PresetBundle();
|
||||
~PresetBundle();
|
||||
|
||||
// Remove all the presets but the "-- default --".
|
||||
// Optionally remove all the files referenced by the presets from the user profile directory.
|
||||
void reset(bool delete_files);
|
||||
|
||||
void setup_directories();
|
||||
|
||||
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
void load_presets(const AppConfig &config);
|
||||
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
void export_selections(AppConfig &config);
|
||||
// Export selections (current print, current filaments, current printer) into a placeholder parser.
|
||||
void export_selections(PlaceholderParser &pp);
|
||||
|
||||
PresetCollection prints;
|
||||
PresetCollection filaments;
|
||||
PresetCollection sla_materials;
|
||||
PresetCollection printers;
|
||||
// Filament preset names for a multi-extruder or multi-material print.
|
||||
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
|
||||
std::vector<std::string> filament_presets;
|
||||
|
||||
// The project configuration values are kept separated from the print/filament/printer preset,
|
||||
// they are being serialized / deserialized from / to the .amf, .3mf, .config, .gcode,
|
||||
// and they are being used by slicing core.
|
||||
DynamicPrintConfig project_config;
|
||||
|
||||
// There will be an entry for each system profile loaded,
|
||||
// and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors.
|
||||
std::set<VendorProfile> vendors;
|
||||
|
||||
struct ObsoletePresets {
|
||||
std::vector<std::string> prints;
|
||||
std::vector<std::string> filaments;
|
||||
std::vector<std::string> sla_materials;
|
||||
std::vector<std::string> printers;
|
||||
};
|
||||
ObsoletePresets obsolete_presets;
|
||||
|
||||
bool has_defauls_only() const
|
||||
{ return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); }
|
||||
|
||||
DynamicPrintConfig full_config() const;
|
||||
|
||||
// Load user configuration and store it into the user profiles.
|
||||
// This method is called by the configuration wizard.
|
||||
void load_config(const std::string &name, DynamicPrintConfig config)
|
||||
{ this->load_config_file_config(name, false, std::move(config)); }
|
||||
|
||||
// Load configuration that comes from a model file containing configuration, such as 3MF et al.
|
||||
// This method is called by the Plater.
|
||||
void load_config_model(const std::string &name, DynamicPrintConfig config)
|
||||
{ this->load_config_file_config(name, true, std::move(config)); }
|
||||
|
||||
// Load an external config file containing the print, filament and printer presets.
|
||||
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
|
||||
// In the future the configuration will likely be read from an AMF file as well.
|
||||
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
||||
void load_config_file(const std::string &path);
|
||||
|
||||
// Load an external config source containing the print, filament and printer presets.
|
||||
// The given string must contain the full set of parameters (same as those exported to gcode).
|
||||
// If the string is parsed successfully, its print / filament / printer profiles will be activated.
|
||||
void load_config_string(const char* str, const char* source_filename = nullptr);
|
||||
|
||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||
// of the local configuration directory.
|
||||
// Load settings into the provided settings instance.
|
||||
// Activate the presets stored in the config bundle.
|
||||
// Returns the number of presets loaded successfully.
|
||||
enum {
|
||||
// Save the profiles, which have been loaded.
|
||||
LOAD_CFGBNDLE_SAVE = 1,
|
||||
// Delete all old config profiles before loading.
|
||||
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2,
|
||||
// Load a system config bundle.
|
||||
LOAD_CFGBNDLE_SYSTEM = 4,
|
||||
LOAD_CFGBUNDLE_VENDOR_ONLY = 8,
|
||||
};
|
||||
// Load the config bundle, store it to the user profile directory by default.
|
||||
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
|
||||
|
||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||
void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings);
|
||||
|
||||
// Update a filament selection combo box on the platter for an idx_extruder.
|
||||
void update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui);
|
||||
|
||||
// Enable / disable the "- default -" preset.
|
||||
void set_default_suppressed(bool default_suppressed);
|
||||
|
||||
// Set the filament preset name. As the name could come from the UI selection box,
|
||||
// an optional "(modified)" suffix will be removed from the filament name.
|
||||
void set_filament_preset(size_t idx, const std::string &name);
|
||||
|
||||
// Read out the number of extruders from an active printer preset,
|
||||
// update size and content of filament_presets.
|
||||
void update_multi_material_filament_presets();
|
||||
|
||||
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
|
||||
// as compatible with the currently selected printer.
|
||||
// Also updates the is_visible flag of each preset.
|
||||
// If select_other_if_incompatible is true, then the print or filament preset is switched to some compatible
|
||||
// preset if the current print or filament preset is not compatible.
|
||||
void update_compatible_with_printer(bool select_other_if_incompatible);
|
||||
|
||||
static bool parse_color(const std::string &scolor, unsigned char *rgb_out);
|
||||
|
||||
private:
|
||||
std::string load_system_presets();
|
||||
// Merge one vendor's presets with the other vendor's presets, report duplicates.
|
||||
std::vector<std::string> merge_presets(PresetBundle &&other);
|
||||
|
||||
// Set the "enabled" flag for printer vendors, printer models and printer variants
|
||||
// based on the user configuration.
|
||||
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
|
||||
void load_installed_printers(const AppConfig &config);
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
void load_selections(const AppConfig &config);
|
||||
|
||||
// Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path.
|
||||
// and the external config is just referenced, not stored into user profile directory.
|
||||
// If it is not an external config, then the config will be stored into the user profile directory.
|
||||
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
|
||||
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
|
||||
bool load_compatible_bitmaps();
|
||||
|
||||
DynamicPrintConfig full_fff_config() const;
|
||||
DynamicPrintConfig full_sla_config() const;
|
||||
|
||||
// Indicator, that the preset is compatible with the selected printer.
|
||||
wxBitmap *m_bitmapCompatible;
|
||||
// Indicator, that the preset is NOT compatible with the selected printer.
|
||||
wxBitmap *m_bitmapIncompatible;
|
||||
// Indicator, that the preset is system and not modified.
|
||||
wxBitmap *m_bitmapLock;
|
||||
// Indicator, that the preset is system and user modified.
|
||||
wxBitmap *m_bitmapLockOpen;
|
||||
// Caching color bitmaps for the filament combo box.
|
||||
GUI::BitmapCache *m_bitmapCache;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_PresetBundle_hpp_ */
|
||||
278
src/slic3r/GUI/PresetHints.cpp
Normal file
278
src/slic3r/GUI/PresetHints.cpp
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
//#undef NDEBUG
|
||||
#include <cassert>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
#include "PresetHints.hpp"
|
||||
#include "Flow.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <wx/intl.h>
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "GUI.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#define MIN_BUF_LENGTH 4096
|
||||
std::string PresetHints::cooling_description(const Preset &preset)
|
||||
{
|
||||
std::string out;
|
||||
char buf[MIN_BUF_LENGTH/*4096*/];
|
||||
if (preset.config.opt_bool("cooling", 0)) {
|
||||
int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0);
|
||||
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
|
||||
int max_fan_speed = preset.config.opt_int("max_fan_speed", 0);
|
||||
int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5);
|
||||
int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0);
|
||||
sprintf(buf, _CHB(L("If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).")),
|
||||
slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed);
|
||||
out += buf;
|
||||
if (fan_below_layer_time > slowdown_below_layer_time) {
|
||||
sprintf(buf, _CHB(L("\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.")),
|
||||
fan_below_layer_time, max_fan_speed, min_fan_speed);
|
||||
out += buf;
|
||||
}
|
||||
out += _CHB(L("\nDuring the other layers, fan "));
|
||||
} else {
|
||||
out = _CHB(L("Fan "));
|
||||
}
|
||||
if (preset.config.opt_bool("fan_always_on", 0)) {
|
||||
int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0);
|
||||
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
|
||||
sprintf(buf, _CHB(L("will always run at %d%% ")), min_fan_speed);
|
||||
out += buf;
|
||||
if (disable_fan_first_layers > 1) {
|
||||
sprintf(buf, _CHB(L("except for the first %d layers")), disable_fan_first_layers);
|
||||
out += buf;
|
||||
}
|
||||
else if (disable_fan_first_layers == 1)
|
||||
out += _CHB(L("except for the first layer"));
|
||||
} else
|
||||
out += _CHB(L("will be turned off."));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static const ConfigOptionFloatOrPercent& first_positive(const ConfigOptionFloatOrPercent *v1, const ConfigOptionFloatOrPercent &v2, const ConfigOptionFloatOrPercent &v3)
|
||||
{
|
||||
return (v1 != nullptr && v1->value > 0) ? *v1 : ((v2.value > 0) ? v2 : v3);
|
||||
}
|
||||
|
||||
std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle &preset_bundle)
|
||||
{
|
||||
// Find out, to which nozzle index is the current filament profile assigned.
|
||||
int idx_extruder = 0;
|
||||
int num_extruders = (int)preset_bundle.filament_presets.size();
|
||||
for (; idx_extruder < num_extruders; ++ idx_extruder)
|
||||
if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset().name)
|
||||
break;
|
||||
if (idx_extruder == num_extruders)
|
||||
// The current filament preset is not active for any extruder.
|
||||
idx_extruder = -1;
|
||||
|
||||
const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config;
|
||||
const DynamicPrintConfig &filament_config = preset_bundle.filaments.get_edited_preset().config;
|
||||
const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config;
|
||||
|
||||
// Current printer values.
|
||||
float nozzle_diameter = (float)printer_config.opt_float("nozzle_diameter", idx_extruder);
|
||||
|
||||
// Print config values
|
||||
double layer_height = print_config.opt_float("layer_height");
|
||||
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
|
||||
double support_material_speed = print_config.opt_float("support_material_speed");
|
||||
double support_material_interface_speed = print_config.get_abs_value("support_material_interface_speed", support_material_speed);
|
||||
double bridge_speed = print_config.opt_float("bridge_speed");
|
||||
double bridge_flow_ratio = print_config.opt_float("bridge_flow_ratio");
|
||||
double perimeter_speed = print_config.opt_float("perimeter_speed");
|
||||
double external_perimeter_speed = print_config.get_abs_value("external_perimeter_speed", perimeter_speed);
|
||||
double gap_fill_speed = print_config.opt_float("gap_fill_speed");
|
||||
double infill_speed = print_config.opt_float("infill_speed");
|
||||
double small_perimeter_speed = print_config.get_abs_value("small_perimeter_speed", perimeter_speed);
|
||||
double solid_infill_speed = print_config.get_abs_value("solid_infill_speed", infill_speed);
|
||||
double top_solid_infill_speed = print_config.get_abs_value("top_solid_infill_speed", solid_infill_speed);
|
||||
// Maximum print speed when auto-speed is enabled by setting any of the above speed values to zero.
|
||||
double max_print_speed = print_config.opt_float("max_print_speed");
|
||||
// Maximum volumetric speed allowed for the print profile.
|
||||
double max_volumetric_speed = print_config.opt_float("max_volumetric_speed");
|
||||
|
||||
const auto &extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("extrusion_width");
|
||||
const auto &external_perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width");
|
||||
const auto &first_layer_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
|
||||
const auto &infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("infill_extrusion_width");
|
||||
const auto &perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
|
||||
const auto &solid_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("solid_infill_extrusion_width");
|
||||
const auto &support_material_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("support_material_extrusion_width");
|
||||
const auto &top_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("top_infill_extrusion_width");
|
||||
const auto &first_layer_speed = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_speed");
|
||||
|
||||
// Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print.
|
||||
// If different from idx_extruder, it will not be taken into account for this hint.
|
||||
auto feature_extruder_active = [idx_extruder, num_extruders](int i) {
|
||||
return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1;
|
||||
};
|
||||
bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("perimeter_extruder"));
|
||||
bool infill_extruder_active = feature_extruder_active(print_config.opt_int("infill_extruder"));
|
||||
bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_extruder"));
|
||||
bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_material_extruder"));
|
||||
bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_material_interface_extruder"));
|
||||
|
||||
// Current filament values
|
||||
double filament_diameter = filament_config.opt_float("filament_diameter", 0);
|
||||
double filament_crossection = M_PI * 0.25 * filament_diameter * filament_diameter;
|
||||
double extrusion_multiplier = filament_config.opt_float("extrusion_multiplier", 0);
|
||||
// The following value will be annotated by this hint, so it does not take part in the calculation.
|
||||
// double filament_max_volumetric_speed = filament_config.opt_float("filament_max_volumetric_speed", 0);
|
||||
|
||||
std::string out;
|
||||
for (size_t idx_type = (first_layer_extrusion_width.value == 0) ? 1 : 0; idx_type < 3; ++ idx_type) {
|
||||
// First test the maximum volumetric extrusion speed for non-bridging extrusions.
|
||||
bool first_layer = idx_type == 0;
|
||||
bool bridging = idx_type == 2;
|
||||
const ConfigOptionFloatOrPercent *first_layer_extrusion_width_ptr = (first_layer && first_layer_extrusion_width.value > 0) ?
|
||||
&first_layer_extrusion_width : nullptr;
|
||||
const float lh = float(first_layer ? first_layer_height : layer_height);
|
||||
const float bfr = bridging ? bridge_flow_ratio : 0.f;
|
||||
double max_flow = 0.;
|
||||
std::string max_flow_extrusion_type;
|
||||
auto limit_by_first_layer_speed = [&first_layer_speed, first_layer](double speed_normal, double speed_max) {
|
||||
if (first_layer && first_layer_speed.value > 0)
|
||||
// Apply the first layer limit.
|
||||
speed_normal = first_layer_speed.get_abs_value(speed_normal);
|
||||
return (speed_normal > 0.) ? speed_normal : speed_max;
|
||||
};
|
||||
if (perimeter_extruder_active) {
|
||||
double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter,
|
||||
first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() *
|
||||
(bridging ? bridge_speed :
|
||||
limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
|
||||
if (max_flow < external_perimeter_rate) {
|
||||
max_flow = external_perimeter_rate;
|
||||
max_flow_extrusion_type = _CHB(L("external perimeters"));
|
||||
}
|
||||
double perimeter_rate = Flow::new_from_config_width(frPerimeter,
|
||||
first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() *
|
||||
(bridging ? bridge_speed :
|
||||
limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
|
||||
if (max_flow < perimeter_rate) {
|
||||
max_flow = perimeter_rate;
|
||||
max_flow_extrusion_type = _CHB(L("perimeters"));
|
||||
}
|
||||
}
|
||||
if (! bridging && infill_extruder_active) {
|
||||
double infill_rate = Flow::new_from_config_width(frInfill,
|
||||
first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed);
|
||||
if (max_flow < infill_rate) {
|
||||
max_flow = infill_rate;
|
||||
max_flow_extrusion_type = _CHB(L("infill"));
|
||||
}
|
||||
}
|
||||
if (solid_infill_extruder_active) {
|
||||
double solid_infill_rate = Flow::new_from_config_width(frInfill,
|
||||
first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, 0).mm3_per_mm() *
|
||||
(bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed));
|
||||
if (max_flow < solid_infill_rate) {
|
||||
max_flow = solid_infill_rate;
|
||||
max_flow_extrusion_type = _CHB(L("solid infill"));
|
||||
}
|
||||
if (! bridging) {
|
||||
double top_solid_infill_rate = Flow::new_from_config_width(frInfill,
|
||||
first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
|
||||
if (max_flow < top_solid_infill_rate) {
|
||||
max_flow = top_solid_infill_rate;
|
||||
max_flow_extrusion_type = _CHB(L("top solid infill"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (support_material_extruder_active) {
|
||||
double support_material_rate = Flow::new_from_config_width(frSupportMaterial,
|
||||
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() *
|
||||
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed));
|
||||
if (max_flow < support_material_rate) {
|
||||
max_flow = support_material_rate;
|
||||
max_flow_extrusion_type = _CHB(L("support"));
|
||||
}
|
||||
}
|
||||
if (support_material_interface_extruder_active) {
|
||||
double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface,
|
||||
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() *
|
||||
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed));
|
||||
if (max_flow < support_material_interface_rate) {
|
||||
max_flow = support_material_interface_rate;
|
||||
max_flow_extrusion_type = _CHB(L("support interface"));
|
||||
}
|
||||
}
|
||||
//FIXME handle gap_fill_speed
|
||||
if (! out.empty())
|
||||
out += "\n";
|
||||
out += (first_layer ? _CHB(L("First layer volumetric")) : (bridging ? _CHB(L("Bridging volumetric")) : _CHB(L("Volumetric"))));
|
||||
out += _CHB(L(" flow rate is maximized "));
|
||||
bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow;
|
||||
out += (limited_by_max_volumetric_speed ?
|
||||
_CHB(L("by the print profile maximum")) :
|
||||
(_CHB(L("when printing ")) + max_flow_extrusion_type))
|
||||
+ _CHB(L(" with a volumetric rate "));
|
||||
if (limited_by_max_volumetric_speed)
|
||||
max_flow = max_volumetric_speed;
|
||||
char buf[MIN_BUF_LENGTH/*2048*/];
|
||||
sprintf(buf, _CHB(L("%3.2f mm³/s")), max_flow);
|
||||
out += buf;
|
||||
sprintf(buf, _CHB(L(" at filament speed %3.2f mm/s.")), max_flow / filament_crossection);
|
||||
out += buf;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &preset_bundle)
|
||||
{
|
||||
const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config;
|
||||
const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config;
|
||||
|
||||
float layer_height = float(print_config.opt_float("layer_height"));
|
||||
int num_perimeters = print_config.opt_int("perimeters");
|
||||
bool thin_walls = print_config.opt_bool("thin_walls");
|
||||
float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0));
|
||||
|
||||
std::string out;
|
||||
if (layer_height <= 0.f){
|
||||
out += _CHB(L("Recommended object thin wall thickness: Not available due to invalid layer height."));
|
||||
return out;
|
||||
}
|
||||
|
||||
Flow external_perimeter_flow = Flow::new_from_config_width(
|
||||
frExternalPerimeter,
|
||||
*print_config.opt<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width"),
|
||||
nozzle_diameter, layer_height, false);
|
||||
Flow perimeter_flow = Flow::new_from_config_width(
|
||||
frPerimeter,
|
||||
*print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"),
|
||||
nozzle_diameter, layer_height, false);
|
||||
|
||||
|
||||
if (num_perimeters > 0) {
|
||||
int num_lines = std::min(num_perimeters * 2, 10);
|
||||
char buf[MIN_BUF_LENGTH/*256*/];
|
||||
sprintf(buf, _CHB(L("Recommended object thin wall thickness for layer height %.2f and ")), layer_height);
|
||||
out += buf;
|
||||
// Start with the width of two closely spaced
|
||||
double width = external_perimeter_flow.width + external_perimeter_flow.spacing();
|
||||
for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) {
|
||||
if (i > 2)
|
||||
out += ", ";
|
||||
sprintf(buf, _CHB(L("%d lines: %.2lf mm")), i, width);
|
||||
out += buf;
|
||||
width += perimeter_flow.spacing() * (thin_walls ? 1.f : 2.f);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
30
src/slic3r/GUI/PresetHints.hpp
Normal file
30
src/slic3r/GUI/PresetHints.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef slic3r_PresetHints_hpp_
|
||||
#define slic3r_PresetHints_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// GUI utility functions to produce hint messages from the current profile.
|
||||
class PresetHints
|
||||
{
|
||||
public:
|
||||
// Produce a textual description of the cooling logic of a currently active filament.
|
||||
static std::string cooling_description(const Preset &preset);
|
||||
|
||||
// Produce a textual description of the maximum flow achived for the current configuration
|
||||
// (the current printer, filament and print settigns).
|
||||
// This description will be useful for getting a gut feeling for the maximum volumetric
|
||||
// print speed achievable with the extruder.
|
||||
static std::string maximum_volumetric_flow_description(const PresetBundle &preset_bundle);
|
||||
|
||||
// Produce a textual description of a recommended thin wall thickness
|
||||
// from the provided number of perimeters and the external / internal perimeter width.
|
||||
static std::string recommended_thin_wall_thickness(const PresetBundle &preset_bundle);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_PresetHints_hpp_ */
|
||||
70
src/slic3r/GUI/ProgressIndicator.hpp
Normal file
70
src/slic3r/GUI/ProgressIndicator.hpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#ifndef IPROGRESSINDICATOR_HPP
|
||||
#define IPROGRESSINDICATOR_HPP
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/**
|
||||
* @brief Generic progress indication interface.
|
||||
*/
|
||||
class ProgressIndicator {
|
||||
public:
|
||||
using CancelFn = std::function<void(void)>; // Cancel function signature.
|
||||
|
||||
private:
|
||||
float m_state = .0f, m_max = 1.f, m_step;
|
||||
CancelFn m_cancelfunc = [](){};
|
||||
|
||||
public:
|
||||
|
||||
inline virtual ~ProgressIndicator() {}
|
||||
|
||||
/// Get the maximum of the progress range.
|
||||
float max() const { return m_max; }
|
||||
|
||||
/// Get the current progress state
|
||||
float state() const { return m_state; }
|
||||
|
||||
/// Set the maximum of the progress range
|
||||
virtual void max(float maxval) { m_max = maxval; }
|
||||
|
||||
/// Set the current state of the progress.
|
||||
virtual void state(float val) { m_state = val; }
|
||||
|
||||
/**
|
||||
* @brief Number of states int the progress. Can be used instead of giving a
|
||||
* maximum value.
|
||||
*/
|
||||
virtual void states(unsigned statenum) {
|
||||
m_step = m_max / statenum;
|
||||
}
|
||||
|
||||
/// Message shown on the next status update.
|
||||
virtual void message(const std::string&) = 0;
|
||||
|
||||
/// Title of the operation.
|
||||
virtual void title(const std::string&) = 0;
|
||||
|
||||
/// Formatted message for the next status update. Works just like sprintf.
|
||||
virtual void message_fmt(const std::string& fmt, ...);
|
||||
|
||||
/// Set up a cancel callback for the operation if feasible.
|
||||
virtual void on_cancel(CancelFn func = CancelFn()) { m_cancelfunc = func; }
|
||||
|
||||
/**
|
||||
* Explicitly shut down the progress indicator and call the associated
|
||||
* callback.
|
||||
*/
|
||||
virtual void cancel() { m_cancelfunc(); }
|
||||
|
||||
/// Convenience function to call message and status update in one function.
|
||||
void update(float st, const std::string& msg) {
|
||||
message(msg); state(st);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // IPROGRESSINDICATOR_HPP
|
||||
162
src/slic3r/GUI/ProgressStatusBar.cpp
Normal file
162
src/slic3r/GUI/ProgressStatusBar.cpp
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#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_App.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id):
|
||||
self(new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe,
|
||||
id == -1? wxID_ANY : id)),
|
||||
m_timer(new wxTimer(self)),
|
||||
m_prog (new wxGauge(self,
|
||||
wxGA_HORIZONTAL,
|
||||
100,
|
||||
wxDefaultPosition,
|
||||
wxDefaultSize)),
|
||||
m_cancelbutton(new wxButton(self,
|
||||
-1,
|
||||
"Cancel",
|
||||
wxDefaultPosition,
|
||||
wxDefaultSize))
|
||||
{
|
||||
m_prog->Hide();
|
||||
m_cancelbutton->Hide();
|
||||
|
||||
self->SetFieldsCount(3);
|
||||
int w[] = {-1, 150, 155};
|
||||
self->SetStatusWidths(3, w);
|
||||
|
||||
self->Bind(wxEVT_TIMER, [this](const wxTimerEvent&) {
|
||||
if (m_prog->IsShown()) m_timer->Stop();
|
||||
if(is_busy()) m_prog->Pulse();
|
||||
});
|
||||
|
||||
self->Bind(wxEVT_SIZE, [this](wxSizeEvent& event){
|
||||
wxRect rect;
|
||||
self->GetFieldRect(1, rect);
|
||||
auto offset = 0;
|
||||
m_cancelbutton->Move(rect.GetX() + offset, rect.GetY() + offset);
|
||||
m_cancelbutton->SetSize(rect.GetWidth() - offset, rect.GetHeight());
|
||||
|
||||
self->GetFieldRect(2, rect);
|
||||
m_prog->Move(rect.GetX() + offset, rect.GetY() + offset);
|
||||
m_prog->SetSize(rect.GetWidth() - offset, rect.GetHeight());
|
||||
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
m_cancelbutton->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) {
|
||||
if (m_cancel_cb)
|
||||
m_cancel_cb();
|
||||
m_cancelbutton->Hide();
|
||||
});
|
||||
}
|
||||
|
||||
ProgressStatusBar::~ProgressStatusBar() {
|
||||
if(m_timer->IsRunning()) m_timer->Stop();
|
||||
}
|
||||
|
||||
int ProgressStatusBar::get_progress() const
|
||||
{
|
||||
return m_prog->GetValue();
|
||||
}
|
||||
|
||||
void ProgressStatusBar::set_progress(int val)
|
||||
{
|
||||
if(!m_prog->IsShown()) show_progress(true);
|
||||
|
||||
if(val == m_prog->GetRange()) {
|
||||
m_prog->SetValue(0);
|
||||
show_progress(false);
|
||||
} else {
|
||||
m_prog->SetValue(val);
|
||||
}
|
||||
}
|
||||
|
||||
int ProgressStatusBar::get_range() const
|
||||
{
|
||||
return m_prog->GetRange();
|
||||
}
|
||||
|
||||
void ProgressStatusBar::set_range(int val)
|
||||
{
|
||||
if(val != m_prog->GetRange()) {
|
||||
m_prog->SetRange(val);
|
||||
}
|
||||
}
|
||||
|
||||
void ProgressStatusBar::show_progress(bool show)
|
||||
{
|
||||
m_prog->Show(show);
|
||||
m_prog->Pulse();
|
||||
}
|
||||
|
||||
void ProgressStatusBar::start_busy(int rate)
|
||||
{
|
||||
m_busy = true;
|
||||
show_progress(true);
|
||||
if (!m_timer->IsRunning()) {
|
||||
m_timer->Start(rate);
|
||||
}
|
||||
}
|
||||
|
||||
void ProgressStatusBar::stop_busy()
|
||||
{
|
||||
m_timer->Stop();
|
||||
show_progress(false);
|
||||
m_prog->SetValue(0);
|
||||
m_busy = false;
|
||||
}
|
||||
|
||||
void ProgressStatusBar::set_cancel_callback(ProgressStatusBar::CancelFn ccb) {
|
||||
m_cancel_cb = ccb;
|
||||
if(ccb) m_cancelbutton->Show();
|
||||
else m_cancelbutton->Hide();
|
||||
}
|
||||
|
||||
void ProgressStatusBar::run(int rate)
|
||||
{
|
||||
if(!m_timer->IsRunning()) {
|
||||
m_timer->Start(rate);
|
||||
}
|
||||
}
|
||||
|
||||
void ProgressStatusBar::embed(wxFrame *frame)
|
||||
{
|
||||
wxFrame* mf = frame ? frame : GUI::wxGetApp().mainframe;
|
||||
mf->SetStatusBar(self);
|
||||
}
|
||||
|
||||
void ProgressStatusBar::set_status_text(const wxString& txt)
|
||||
{
|
||||
self->SetStatusText(txt);
|
||||
}
|
||||
|
||||
void ProgressStatusBar::set_status_text(const std::string& txt)
|
||||
{
|
||||
this->set_status_text(txt.c_str());
|
||||
}
|
||||
|
||||
void ProgressStatusBar::set_status_text(const char *txt)
|
||||
{
|
||||
this->set_status_text(wxString::FromUTF8(txt));
|
||||
}
|
||||
|
||||
void ProgressStatusBar::show_cancel_button()
|
||||
{
|
||||
m_cancelbutton->Show();
|
||||
}
|
||||
|
||||
void ProgressStatusBar::hide_cancel_button()
|
||||
{
|
||||
m_cancelbutton->Hide();
|
||||
}
|
||||
|
||||
}
|
||||
68
src/slic3r/GUI/ProgressStatusBar.hpp
Normal file
68
src/slic3r/GUI/ProgressStatusBar.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#ifndef PROGRESSSTATUSBAR_HPP
|
||||
#define PROGRESSSTATUSBAR_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
class wxTimer;
|
||||
class wxGauge;
|
||||
class wxButton;
|
||||
class wxTimerEvent;
|
||||
class wxStatusBar;
|
||||
class wxWindow;
|
||||
class wxFrame;
|
||||
class wxString;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/**
|
||||
* @brief The ProgressStatusBar class is the widgets occupying the lower area
|
||||
* of the Slicer main window. It consists of a message area to the left and a
|
||||
* progress indication area to the right with an optional cancel button.
|
||||
*/
|
||||
class ProgressStatusBar
|
||||
{
|
||||
wxStatusBar *self; // we cheat! It should be the base class but: perl!
|
||||
wxTimer *m_timer;
|
||||
wxGauge *m_prog;
|
||||
wxButton *m_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 m_busy; }
|
||||
void set_cancel_callback(CancelFn = CancelFn());
|
||||
inline void reset_cancel_callback() { set_cancel_callback(); }
|
||||
void run(int rate);
|
||||
void embed(wxFrame *frame = nullptr);
|
||||
void set_status_text(const wxString& txt);
|
||||
void set_status_text(const std::string& txt);
|
||||
void set_status_text(const char *txt);
|
||||
|
||||
// Temporary methods to satisfy Perl side
|
||||
void show_cancel_button();
|
||||
void hide_cancel_button();
|
||||
|
||||
private:
|
||||
bool m_busy = false;
|
||||
CancelFn m_cancel_cb;
|
||||
};
|
||||
|
||||
namespace GUI {
|
||||
using Slic3r::ProgressStatusBar;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // PROGRESSSTATUSBAR_HPP
|
||||
279
src/slic3r/GUI/RammingChart.cpp
Normal file
279
src/slic3r/GUI/RammingChart.cpp
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
#include <algorithm>
|
||||
#include <wx/dcbuffer.h>
|
||||
|
||||
#include "RammingChart.hpp"
|
||||
#include "GUI.hpp"
|
||||
|
||||
|
||||
wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent);
|
||||
|
||||
|
||||
void Chart::draw() {
|
||||
wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win
|
||||
|
||||
dc.SetBrush(GetBackgroundColour());
|
||||
dc.SetPen(GetBackgroundColour());
|
||||
dc.DrawRectangle(GetClientRect()); // otherwise the background would end up black on windows
|
||||
|
||||
dc.SetPen(*wxBLACK_PEN);
|
||||
dc.SetBrush(*wxWHITE_BRUSH);
|
||||
dc.DrawRectangle(m_rect);
|
||||
|
||||
if (visible_area.m_width < 0.499) {
|
||||
dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,m_rect.GetBottom()-m_rect.GetHeight()/2));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!m_line_to_draw.empty()) {
|
||||
for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
|
||||
int color = 510*((m_rect.GetBottom()-(m_line_to_draw)[i])/double(m_rect.GetHeight()));
|
||||
dc.SetPen( wxPen( wxColor(std::min(255,color),255-std::max(color-255,0),0), 1 ) );
|
||||
dc.DrawLine(m_rect.GetLeft()+1+i,(m_line_to_draw)[i],m_rect.GetLeft()+1+i,m_rect.GetBottom());
|
||||
}
|
||||
dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
|
||||
for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
|
||||
if (splines)
|
||||
dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
|
||||
else {
|
||||
dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i]);
|
||||
dc.DrawLine(m_rect.GetLeft()+i+1,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw draggable buttons
|
||||
dc.SetBrush(*wxBLUE_BRUSH);
|
||||
dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
|
||||
for (auto& button : m_buttons)
|
||||
//dc.DrawRectangle(math_to_screen(button.get_pos())-wxPoint(side/2.,side/2.), wxSize(side,side));
|
||||
dc.DrawCircle(math_to_screen(button.get_pos()),side/2.);
|
||||
//dc.DrawRectangle(math_to_screen(button.get_pos()-wxPoint2DDouble(0.125,0))-wxPoint(0,5),wxSize(50,10));
|
||||
|
||||
// draw x-axis:
|
||||
float last_mark = -10000;
|
||||
for (float math_x=int(visible_area.m_x*10)/10 ; math_x < (visible_area.m_x+visible_area.m_width) ; math_x+=0.1) {
|
||||
int x = math_to_screen(wxPoint2DDouble(math_x,visible_area.m_y)).x;
|
||||
int y = m_rect.GetBottom();
|
||||
if (x-last_mark < 50) continue;
|
||||
dc.DrawLine(x,y+3,x,y-3);
|
||||
dc.DrawText(wxString().Format(wxT("%.1f"), math_x),wxPoint(x-10,y+7));
|
||||
last_mark = x;
|
||||
}
|
||||
|
||||
// draw y-axis:
|
||||
last_mark=10000;
|
||||
for (int math_y=visible_area.m_y ; math_y < (visible_area.m_y+visible_area.m_height) ; math_y+=1) {
|
||||
int y = math_to_screen(wxPoint2DDouble(visible_area.m_x,math_y)).y;
|
||||
int x = m_rect.GetLeft();
|
||||
if (last_mark-y < 50) continue;
|
||||
dc.DrawLine(x-3,y,x+3,y);
|
||||
dc.DrawText(wxString()<<math_y,wxPoint(x-25,y-2/*7*/));
|
||||
last_mark = y;
|
||||
}
|
||||
|
||||
// axis labels:
|
||||
wxString label = _(L("Time")) + " ("+_(L("s"))+")";
|
||||
int text_width = 0;
|
||||
int text_height = 0;
|
||||
dc.GetTextExtent(label,&text_width,&text_height);
|
||||
dc.DrawText(label,wxPoint(0.5*(m_rect.GetRight()+m_rect.GetLeft())-text_width/2.f, m_rect.GetBottom()+25));
|
||||
label = _(L("Volumetric speed")) + " (" + _(L("mm")) + wxString("³/", wxConvUTF8) + _(L("s")) + ")";
|
||||
dc.GetTextExtent(label,&text_width,&text_height);
|
||||
dc.DrawRotatedText(label,wxPoint(0,0.5*(m_rect.GetBottom()+m_rect.GetTop())+text_width/2.f),90);
|
||||
}
|
||||
|
||||
void Chart::mouse_right_button_clicked(wxMouseEvent& event) {
|
||||
if (!manual_points_manipulation)
|
||||
return;
|
||||
wxPoint point = event.GetPosition();
|
||||
int button_index = which_button_is_clicked(point);
|
||||
if (button_index != -1 && m_buttons.size()>2) {
|
||||
m_buttons.erase(m_buttons.begin()+button_index);
|
||||
recalculate_line();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart::mouse_clicked(wxMouseEvent& event) {
|
||||
wxPoint point = event.GetPosition();
|
||||
int button_index = which_button_is_clicked(point);
|
||||
if ( button_index != -1) {
|
||||
m_dragged = &m_buttons[button_index];
|
||||
m_previous_mouse = point;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart::mouse_moved(wxMouseEvent& event) {
|
||||
if (!event.Dragging() || !m_dragged) return;
|
||||
wxPoint pos = event.GetPosition();
|
||||
wxRect rect = m_rect;
|
||||
rect.Deflate(side/2.);
|
||||
if (!(rect.Contains(pos))) { // the mouse left chart area
|
||||
mouse_left_window(event);
|
||||
return;
|
||||
}
|
||||
int delta_x = pos.x - m_previous_mouse.x;
|
||||
int delta_y = pos.y - m_previous_mouse.y;
|
||||
m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height);
|
||||
m_previous_mouse = pos;
|
||||
recalculate_line();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart::mouse_double_clicked(wxMouseEvent& event) {
|
||||
if (!manual_points_manipulation)
|
||||
return;
|
||||
wxPoint point = event.GetPosition();
|
||||
if (!m_rect.Contains(point)) // the click is outside the chart
|
||||
return;
|
||||
m_buttons.push_back(screen_to_math(point));
|
||||
std::sort(m_buttons.begin(),m_buttons.end());
|
||||
recalculate_line();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Chart::recalculate_line() {
|
||||
std::vector<wxPoint> points;
|
||||
for (auto& but : m_buttons) {
|
||||
points.push_back(wxPoint(math_to_screen(but.get_pos())));
|
||||
if (points.size()>1 && points.back().x==points[points.size()-2].x) points.pop_back();
|
||||
if (points.size()>1 && points.back().x > m_rect.GetRight()) {
|
||||
points.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::sort(points.begin(),points.end(),[](wxPoint& a,wxPoint& b) { return a.x < b.x; });
|
||||
|
||||
m_line_to_draw.clear();
|
||||
m_total_volume = 0.f;
|
||||
|
||||
|
||||
// Cubic spline interpolation: see https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation#Methods
|
||||
const bool boundary_first_derivative = true; // true - first derivative is 0 at the leftmost and rightmost point
|
||||
// false - second ---- || -------
|
||||
const int N = points.size()-1; // last point can be accessed as N, we have N+1 total points
|
||||
std::vector<float> diag(N+1);
|
||||
std::vector<float> mu(N+1);
|
||||
std::vector<float> lambda(N+1);
|
||||
std::vector<float> h(N+1);
|
||||
std::vector<float> rhs(N+1);
|
||||
|
||||
// let's fill in inner equations
|
||||
for (int i=1;i<=N;++i) h[i] = points[i].x-points[i-1].x;
|
||||
std::fill(diag.begin(),diag.end(),2.f);
|
||||
for (int i=1;i<=N-1;++i) {
|
||||
mu[i] = h[i]/(h[i]+h[i+1]);
|
||||
lambda[i] = 1.f - mu[i];
|
||||
rhs[i] = 6 * ( float(points[i+1].y-points[i].y )/(h[i+1]*(points[i+1].x-points[i-1].x)) -
|
||||
float(points[i].y -points[i-1].y)/(h[i] *(points[i+1].x-points[i-1].x)) );
|
||||
}
|
||||
|
||||
// now fill in the first and last equations, according to boundary conditions:
|
||||
if (boundary_first_derivative) {
|
||||
const float endpoints_derivative = 0;
|
||||
lambda[0] = 1;
|
||||
mu[N] = 1;
|
||||
rhs[0] = (6.f/h[1]) * (float(points[0].y-points[1].y)/(points[0].x-points[1].x) - endpoints_derivative);
|
||||
rhs[N] = (6.f/h[N]) * (endpoints_derivative - float(points[N-1].y-points[N].y)/(points[N-1].x-points[N].x));
|
||||
}
|
||||
else {
|
||||
lambda[0] = 0;
|
||||
mu[N] = 0;
|
||||
rhs[0] = 0;
|
||||
rhs[N] = 0;
|
||||
}
|
||||
|
||||
// the trilinear system is ready to be solved:
|
||||
for (int i=1;i<=N;++i) {
|
||||
float multiple = mu[i]/diag[i-1]; // let's subtract proper multiple of above equation
|
||||
diag[i]-= multiple * lambda[i-1];
|
||||
rhs[i] -= multiple * rhs[i-1];
|
||||
}
|
||||
// now the back substitution (vector mu contains invalid values from now on):
|
||||
rhs[N] = rhs[N]/diag[N];
|
||||
for (int i=N-1;i>=0;--i)
|
||||
rhs[i] = (rhs[i]-lambda[i]*rhs[i+1])/diag[i];
|
||||
|
||||
|
||||
|
||||
|
||||
unsigned int i=1;
|
||||
float y=0.f;
|
||||
for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) {
|
||||
if (splines) {
|
||||
if (i<points.size()-1 && points[i].x < x ) {
|
||||
++i;
|
||||
}
|
||||
if (points[0].x > x)
|
||||
y = points[0].y;
|
||||
else
|
||||
if (points[N].x < x)
|
||||
y = points[N].y;
|
||||
else
|
||||
y = (rhs[i-1]*pow(points[i].x-x,3)+rhs[i]*pow(x-points[i-1].x,3)) / (6*h[i]) +
|
||||
(points[i-1].y-rhs[i-1]*h[i]*h[i]/6.f) * (points[i].x-x)/h[i] +
|
||||
(points[i].y -rhs[i] *h[i]*h[i]/6.f) * (x-points[i-1].x)/h[i];
|
||||
m_line_to_draw.push_back(y);
|
||||
}
|
||||
else {
|
||||
float x_math = screen_to_math(wxPoint(x,0)).m_x;
|
||||
if (i+2<=points.size() && m_buttons[i+1].get_pos().m_x-0.125 < x_math)
|
||||
++i;
|
||||
m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y);
|
||||
}
|
||||
|
||||
|
||||
m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1);
|
||||
m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1);
|
||||
m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight());
|
||||
}
|
||||
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(EVT_WIPE_TOWER_CHART_CHANGED));
|
||||
Refresh();
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<float> Chart::get_ramming_speed(float sampling) const {
|
||||
std::vector<float> speeds_out;
|
||||
|
||||
const int number_of_samples = std::round( visible_area.m_width / sampling);
|
||||
if (number_of_samples>0) {
|
||||
const int dx = (m_line_to_draw.size()-1) / number_of_samples;
|
||||
for (int j=0;j<number_of_samples;++j) {
|
||||
float left = screen_to_math(wxPoint(0,m_line_to_draw[j*dx])).m_y;
|
||||
float right = screen_to_math(wxPoint(0,m_line_to_draw[(j+1)*dx])).m_y;
|
||||
speeds_out.push_back((left+right)/2.f);
|
||||
}
|
||||
}
|
||||
return speeds_out;
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::pair<float,float>> Chart::get_buttons() const {
|
||||
std::vector<std::pair<float, float>> buttons_out;
|
||||
for (const auto& button : m_buttons)
|
||||
buttons_out.push_back(std::make_pair(float(button.get_pos().m_x),float(button.get_pos().m_y)));
|
||||
return buttons_out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
BEGIN_EVENT_TABLE(Chart, wxWindow)
|
||||
EVT_MOTION(Chart::mouse_moved)
|
||||
EVT_LEFT_DOWN(Chart::mouse_clicked)
|
||||
EVT_LEFT_UP(Chart::mouse_released)
|
||||
EVT_LEFT_DCLICK(Chart::mouse_double_clicked)
|
||||
EVT_RIGHT_DOWN(Chart::mouse_right_button_clicked)
|
||||
EVT_LEAVE_WINDOW(Chart::mouse_left_window)
|
||||
EVT_PAINT(Chart::paint_event)
|
||||
END_EVENT_TABLE()
|
||||
115
src/slic3r/GUI/RammingChart.hpp
Normal file
115
src/slic3r/GUI/RammingChart.hpp
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#ifndef RAMMING_CHART_H_
|
||||
#define RAMMING_CHART_H_
|
||||
|
||||
#include <vector>
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
|
||||
wxDECLARE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent);
|
||||
|
||||
|
||||
class Chart : public wxWindow {
|
||||
|
||||
public:
|
||||
Chart(wxWindow* parent, wxRect rect,const std::vector<std::pair<float,float>>& initial_buttons,int ramming_speed_size, float sampling) :
|
||||
wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize())
|
||||
{
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
m_rect = wxRect(wxPoint(50,0),rect.GetSize()-wxSize(50,50));
|
||||
visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.);
|
||||
m_buttons.clear();
|
||||
if (initial_buttons.size()>0)
|
||||
for (const auto& pair : initial_buttons)
|
||||
m_buttons.push_back(wxPoint2DDouble(pair.first,pair.second));
|
||||
recalculate_line();
|
||||
}
|
||||
void set_xy_range(float x,float y) {
|
||||
x = int(x/0.5) * 0.5;
|
||||
if (x>=0) visible_area.SetRight(x);
|
||||
if (y>=0) visible_area.SetBottom(y);
|
||||
recalculate_line();
|
||||
}
|
||||
float get_volume() const { return m_total_volume; }
|
||||
float get_time() const { return visible_area.m_width; }
|
||||
|
||||
std::vector<float> get_ramming_speed(float sampling) const; //returns sampled ramming speed
|
||||
std::vector<std::pair<float,float>> get_buttons() const; // returns buttons position
|
||||
|
||||
void draw();
|
||||
|
||||
void mouse_clicked(wxMouseEvent& event);
|
||||
void mouse_right_button_clicked(wxMouseEvent& event);
|
||||
void mouse_moved(wxMouseEvent& event);
|
||||
void mouse_double_clicked(wxMouseEvent& event);
|
||||
void mouse_left_window(wxMouseEvent&) { m_dragged = nullptr; }
|
||||
void mouse_released(wxMouseEvent&) { m_dragged = nullptr; }
|
||||
void paint_event(wxPaintEvent&) { draw(); }
|
||||
DECLARE_EVENT_TABLE()
|
||||
|
||||
|
||||
|
||||
private:
|
||||
static const bool fixed_x = true;
|
||||
static const bool splines = true;
|
||||
static const bool manual_points_manipulation = false;
|
||||
static const int side = 10; // side of draggable button
|
||||
|
||||
class ButtonToDrag {
|
||||
public:
|
||||
bool operator<(const ButtonToDrag& a) const { return m_pos.m_x < a.m_pos.m_x; }
|
||||
ButtonToDrag(wxPoint2DDouble pos) : m_pos{pos} {};
|
||||
wxPoint2DDouble get_pos() const { return m_pos; }
|
||||
void move(double x,double y) { m_pos.m_x+=x; m_pos.m_y+=y; }
|
||||
private:
|
||||
wxPoint2DDouble m_pos; // position in math coordinates
|
||||
};
|
||||
|
||||
|
||||
|
||||
wxPoint math_to_screen(const wxPoint2DDouble& math) const {
|
||||
wxPoint screen;
|
||||
screen.x = (math.m_x-visible_area.m_x) * (m_rect.GetWidth() / visible_area.m_width );
|
||||
screen.y = (math.m_y-visible_area.m_y) * (m_rect.GetHeight() / visible_area.m_height );
|
||||
screen.y *= -1;
|
||||
screen += m_rect.GetLeftBottom();
|
||||
return screen;
|
||||
}
|
||||
wxPoint2DDouble screen_to_math(const wxPoint& screen) const {
|
||||
wxPoint2DDouble math = screen;
|
||||
math -= m_rect.GetLeftBottom();
|
||||
math.m_y *= -1;
|
||||
math.m_x *= visible_area.m_width / m_rect.GetWidth(); // scales to [0;1]x[0,1]
|
||||
math.m_y *= visible_area.m_height / m_rect.GetHeight();
|
||||
return (math+visible_area.GetLeftTop());
|
||||
}
|
||||
|
||||
int which_button_is_clicked(const wxPoint& point) const {
|
||||
if (!m_rect.Contains(point))
|
||||
return -1;
|
||||
for (unsigned int i=0;i<m_buttons.size();++i) {
|
||||
wxRect rect(math_to_screen(m_buttons[i].get_pos())-wxPoint(side/2.,side/2.),wxSize(side,side)); // bounding rectangle of this button
|
||||
if ( rect.Contains(point) )
|
||||
return i;
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
|
||||
|
||||
void recalculate_line();
|
||||
void recalculate_volume();
|
||||
|
||||
|
||||
wxRect m_rect; // rectangle on screen the chart is mapped into (screen coordinates)
|
||||
wxPoint m_previous_mouse;
|
||||
std::vector<ButtonToDrag> m_buttons;
|
||||
std::vector<int> m_line_to_draw;
|
||||
wxRect2DDouble visible_area;
|
||||
ButtonToDrag* m_dragged = nullptr;
|
||||
float m_total_volume = 0.f;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // RAMMING_CHART_H_
|
||||
148
src/slic3r/GUI/SysInfoDialog.cpp
Normal file
148
src/slic3r/GUI/SysInfoDialog.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#include "SysInfoDialog.hpp"
|
||||
// #include "AboutDialog.hpp"
|
||||
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/platinfo.h>
|
||||
|
||||
// #include "../../libslic3r/Utils.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "GUI.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
std::string get_main_info(bool format_as_html)
|
||||
{
|
||||
std::stringstream out;
|
||||
|
||||
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";
|
||||
|
||||
if (!format_as_html)
|
||||
out << b_start << SLIC3R_FORK_NAME << b_end << line_end;
|
||||
out << b_start << "Version: " << b_end << SLIC3R_VERSION << line_end;
|
||||
out << b_start << "Build: " << b_end << SLIC3R_BUILD << line_end;
|
||||
out << line_end;
|
||||
out << b_start << "Operating System: " << b_end << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << line_end;
|
||||
out << b_start << "System Architecture: " << b_end << wxPlatformInfo::Get().GetArchName() << line_end;
|
||||
out << b_start <<
|
||||
#if defined _WIN32
|
||||
"Windows Version: "
|
||||
#else
|
||||
// Hopefully some kind of unix / linux.
|
||||
"System Version: "
|
||||
#endif
|
||||
<< b_end << wxPlatformInfo::Get().GetOperatingSystemDescription() << line_end;
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
SysInfoDialog::SysInfoDialog()
|
||||
: wxDialog(NULL, wxID_ANY, _(L("Slic3r Prusa Edition - System Information")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
|
||||
{
|
||||
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
SetBackgroundColour(bgr_clr);
|
||||
wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
hsizer->SetMinSize(wxSize(600, -1));
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
// logo
|
||||
wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_128px.png")), wxBITMAP_TYPE_PNG);
|
||||
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp));
|
||||
hsizer->Add(logo, 0, wxEXPAND | wxTOP | wxBOTTOM, 15);
|
||||
|
||||
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
hsizer->Add(vsizer, 1, wxEXPAND|wxLEFT, 20);
|
||||
|
||||
// title
|
||||
{
|
||||
wxStaticText* title = new wxStaticText(this, wxID_ANY, SLIC3R_FORK_NAME, wxDefaultPosition, wxDefaultSize);
|
||||
wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
title_font.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
title_font.SetFamily(wxFONTFAMILY_ROMAN);
|
||||
title_font.SetPointSize(14);
|
||||
title->SetFont(title_font);
|
||||
vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 10);
|
||||
}
|
||||
|
||||
// main_info_text
|
||||
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[] = { static_cast<int>(fs*1.5), static_cast<int>(fs*1.4), static_cast<int>(fs*1.3), fs, fs, fs, fs };
|
||||
|
||||
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER);
|
||||
{
|
||||
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
html->SetBorders(2);
|
||||
const auto text = wxString::Format(
|
||||
"<html>"
|
||||
"<body bgcolor= %s link= %s>"
|
||||
"<font color=%s>"
|
||||
"%s"
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
|
||||
get_main_info(true));
|
||||
html->SetPage(text);
|
||||
vsizer->Add(html, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
|
||||
// opengl_info
|
||||
wxHtmlWindow* opengl_info_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
|
||||
{
|
||||
opengl_info_html->SetMinSize(wxSize(-1, 200));
|
||||
opengl_info_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
opengl_info_html->SetBorders(10);
|
||||
const auto text = wxString::Format(
|
||||
"<html>"
|
||||
"<body bgcolor= %s link= %s>"
|
||||
"<font color=%s>"
|
||||
"%s"
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
|
||||
_3DScene::get_gl_info(true, true));
|
||||
opengl_info_html->SetPage(text);
|
||||
main_sizer->Add(opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15);
|
||||
}
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK);
|
||||
auto btn_copy_to_clipboard = new wxButton(this, wxID_ANY, "Copy to Clipboard", wxDefaultPosition, wxDefaultSize);
|
||||
buttons->Insert(0, btn_copy_to_clipboard, 0, wxLEFT, 5);
|
||||
btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this);
|
||||
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
this->Bind(wxEVT_BUTTON, &SysInfoDialog::onCloseDialog, this, wxID_OK);
|
||||
main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
|
||||
this->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
|
||||
logo->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
void SysInfoDialog::onCopyToClipboard(wxEvent &)
|
||||
{
|
||||
wxTheClipboard->Open();
|
||||
const auto text = get_main_info(false)+"\n"+_3DScene::get_gl_info(false, true);
|
||||
wxTheClipboard->SetData(new wxTextDataObject(text));
|
||||
wxTheClipboard->Close();
|
||||
}
|
||||
|
||||
void SysInfoDialog::onCloseDialog(wxEvent &)
|
||||
{
|
||||
this->EndModal(wxID_CLOSE);
|
||||
this->Close();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
24
src/slic3r/GUI/SysInfoDialog.hpp
Normal file
24
src/slic3r/GUI/SysInfoDialog.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef slic3r_GUI_SysInfoDialog_hpp_
|
||||
#define slic3r_GUI_SysInfoDialog_hpp_
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class SysInfoDialog : public wxDialog
|
||||
{
|
||||
wxString text_info {wxEmptyString};
|
||||
public:
|
||||
SysInfoDialog();
|
||||
|
||||
private:
|
||||
void onCopyToClipboard(wxEvent &);
|
||||
void onCloseDialog(wxEvent &);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
2943
src/slic3r/GUI/Tab.cpp
Normal file
2943
src/slic3r/GUI/Tab.cpp
Normal file
File diff suppressed because it is too large
Load diff
384
src/slic3r/GUI/Tab.hpp
Normal file
384
src/slic3r/GUI/Tab.hpp
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
#ifndef slic3r_Tab_hpp_
|
||||
#define slic3r_Tab_hpp_
|
||||
|
||||
// The "Expert" tab at the right of the main tabbed window.
|
||||
//
|
||||
// This file implements following packages:
|
||||
// Slic3r::GUI::Tab;
|
||||
// Slic3r::GUI::Tab::Print;
|
||||
// Slic3r::GUI::Tab::Filament;
|
||||
// Slic3r::GUI::Tab::Printer;
|
||||
// Slic3r::GUI::Tab::Page
|
||||
// - Option page: For example, the Slic3r::GUI::Tab::Print has option pages "Layers and perimeters", "Infill", "Skirt and brim" ...
|
||||
// Slic3r::GUI::SavePresetWindow
|
||||
// - Dialog to select a new preset name to store the configuration.
|
||||
// Slic3r::GUI::Tab::Preset;
|
||||
// - Single preset item: name, file is default or external.
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <wx/bmpbuttn.h>
|
||||
#include <wx/treectrl.h>
|
||||
#include <wx/imaglist.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "BedShapeDialog.hpp"
|
||||
#include "Event.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
typedef std::pair<wxBitmap*, std::string> t_icon_description;
|
||||
typedef std::vector<std::pair<wxBitmap*, std::string>> t_icon_descriptions;
|
||||
|
||||
// Single Tab page containing a{ vsizer } of{ optgroups }
|
||||
// package Slic3r::GUI::Tab::Page;
|
||||
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
|
||||
class Page : public wxScrolledWindow
|
||||
{
|
||||
wxWindow* m_parent;
|
||||
wxString m_title;
|
||||
size_t m_iconID;
|
||||
wxBoxSizer* m_vsizer;
|
||||
bool m_show = true;
|
||||
public:
|
||||
Page(wxWindow* parent, const wxString title, const int iconID) :
|
||||
m_parent(parent),
|
||||
m_title(title),
|
||||
m_iconID(iconID)
|
||||
{
|
||||
Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
m_vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_item_color = &wxGetApp().get_label_clr_default();
|
||||
SetSizer(m_vsizer);
|
||||
}
|
||||
~Page(){}
|
||||
|
||||
bool m_is_modified_values{ false };
|
||||
bool m_is_nonsys_values{ true };
|
||||
|
||||
public:
|
||||
std::vector <ConfigOptionsGroupShp> m_optgroups;
|
||||
DynamicPrintConfig* m_config;
|
||||
|
||||
wxBoxSizer* vsizer() const { return m_vsizer; }
|
||||
wxWindow* parent() const { return m_parent; }
|
||||
wxString title() const { return m_title; }
|
||||
size_t iconID() const { return m_iconID; }
|
||||
void set_config(DynamicPrintConfig* config_in) { m_config = config_in; }
|
||||
void reload_config();
|
||||
void update_visibility(ConfigOptionMode mode);
|
||||
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;
|
||||
}
|
||||
bool get_show() const { return m_show; }
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
wxDECLARE_EVENT(EVT_TAB_VALUE_CHANGED, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_TAB_PRESETS_CHANGED, SimpleEvent);
|
||||
|
||||
|
||||
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:
|
||||
Preset::Type m_type;
|
||||
std::string m_name;
|
||||
const wxString m_title;
|
||||
wxBitmapComboBox* m_presets_choice;
|
||||
wxBitmapButton* m_btn_save_preset;
|
||||
wxBitmapButton* m_btn_delete_preset;
|
||||
wxBitmapButton* m_btn_hide_incompatible_presets;
|
||||
wxBoxSizer* m_hsizer;
|
||||
wxBoxSizer* m_left_sizer;
|
||||
wxTreeCtrl* m_treectrl;
|
||||
wxImageList* m_icons;
|
||||
wxCheckBox* m_compatible_printers_checkbox;
|
||||
wxButton* m_compatible_printers_btn;
|
||||
wxButton* m_undo_btn;
|
||||
wxButton* m_undo_to_sys_btn;
|
||||
wxButton* m_question_btn;
|
||||
wxImageList* m_preset_icons;
|
||||
|
||||
// Cached bitmaps.
|
||||
// A "flag" icon to be displayned next to the preset name in the Tab's combo box.
|
||||
wxBitmap m_bmp_show_incompatible_presets;
|
||||
wxBitmap m_bmp_hide_incompatible_presets;
|
||||
// Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field.
|
||||
wxBitmap m_bmp_value_lock;
|
||||
wxBitmap m_bmp_value_unlock;
|
||||
wxBitmap m_bmp_white_bullet;
|
||||
// The following bitmap points to either m_bmp_value_unlock or m_bmp_white_bullet, depending on whether the current preset has a parent preset.
|
||||
wxBitmap *m_bmp_non_system;
|
||||
// Bitmaps to be shown on the "Undo user changes" button next to each input field.
|
||||
wxBitmap m_bmp_value_revert;
|
||||
// wxBitmap m_bmp_value_unmodified;
|
||||
wxBitmap m_bmp_question;
|
||||
|
||||
// Colors for ui "decoration"
|
||||
wxColour m_sys_label_clr;
|
||||
wxColour m_modified_label_clr;
|
||||
wxColour m_default_text_clr;
|
||||
|
||||
// Tooltip text for reset buttons (for whole options group)
|
||||
wxString m_ttg_value_lock;
|
||||
wxString m_ttg_value_unlock;
|
||||
wxString m_ttg_white_bullet_ns;
|
||||
// The following text points to either m_ttg_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset.
|
||||
wxString *m_ttg_non_system;
|
||||
// Tooltip text to be shown on the "Undo user changes" button next to each input field.
|
||||
wxString m_ttg_white_bullet;
|
||||
wxString m_ttg_value_revert;
|
||||
|
||||
// Tooltip text for reset buttons (for each option in group)
|
||||
wxString m_tt_value_lock;
|
||||
wxString m_tt_value_unlock;
|
||||
// The following text points to either m_tt_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset.
|
||||
wxString *m_tt_non_system;
|
||||
// Tooltip text to be shown on the "Undo user changes" button next to each input field.
|
||||
wxString m_tt_white_bullet;
|
||||
wxString m_tt_value_revert;
|
||||
|
||||
int m_icon_count;
|
||||
std::map<std::string, size_t> m_icon_index; // Map from an icon file name to its index
|
||||
std::vector<PageShp> m_pages;
|
||||
bool m_disable_tree_sel_changed_event;
|
||||
bool m_show_incompatible_presets;
|
||||
|
||||
std::vector<std::string> m_reload_dependent_tabs = {};
|
||||
enum OptStatus { osSystemValue = 1, osInitValue = 2 };
|
||||
std::map<std::string, int> m_options_list;
|
||||
int m_opt_status_value = 0;
|
||||
|
||||
t_icon_descriptions m_icon_descriptions = {};
|
||||
|
||||
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 };
|
||||
|
||||
void set_type();
|
||||
|
||||
public:
|
||||
PresetBundle* m_preset_bundle;
|
||||
bool m_show_btn_incompatible_presets = false;
|
||||
PresetCollection* m_presets;
|
||||
DynamicPrintConfig* m_config;
|
||||
ogStaticText* m_parent_preset_description_line;
|
||||
wxStaticText* m_colored_Label = nullptr;
|
||||
|
||||
public:
|
||||
Tab() {}
|
||||
Tab(wxNotebook* parent, const wxString& title, const char* name) :
|
||||
m_parent(parent), m_title(title), m_name(name) {
|
||||
Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name);
|
||||
set_type();
|
||||
wxGetApp().tabs_list.push_back(this);
|
||||
}
|
||||
~Tab(){
|
||||
wxGetApp().delete_tab_from_list(this);
|
||||
}
|
||||
|
||||
wxWindow* parent() const { return m_parent; }
|
||||
wxString title() const { return m_title; }
|
||||
std::string name() const { return m_name; }
|
||||
Preset::Type type() const { return m_type; }
|
||||
|
||||
void create_preset_tab();
|
||||
void load_current_preset();
|
||||
void rebuild_page_tree(bool tree_sel_change_event = false);
|
||||
void update_page_tree_visibility();
|
||||
void select_preset(std::string preset_name = "");
|
||||
bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = "");
|
||||
wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn);
|
||||
|
||||
void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false);
|
||||
void reload_compatible_printers_widget();
|
||||
|
||||
void OnTreeSelChange(wxTreeEvent& event);
|
||||
void OnKeyDown(wxKeyEvent& event);
|
||||
|
||||
void save_preset(std::string name = "");
|
||||
void delete_preset();
|
||||
void toggle_show_hide_incompatible();
|
||||
void update_show_hide_incompatible_button();
|
||||
void update_ui_from_settings();
|
||||
void update_labels_colour();
|
||||
void update_changed_ui();
|
||||
void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page);
|
||||
void update_changed_tree_ui();
|
||||
void update_undo_buttons();
|
||||
|
||||
void on_roll_back_value(const bool to_sys = false);
|
||||
|
||||
PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false);
|
||||
|
||||
virtual void OnActivate();
|
||||
virtual void on_preset_loaded(){}
|
||||
virtual void build() = 0;
|
||||
virtual void update() = 0;
|
||||
virtual void init_options_list();
|
||||
void load_initial_data();
|
||||
void update_dirty();
|
||||
void update_tab_ui();
|
||||
void load_config(const DynamicPrintConfig& config);
|
||||
virtual void reload_config();
|
||||
void update_visibility(ConfigOptionMode mode);
|
||||
Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const;
|
||||
bool set_value(const t_config_option_key& opt_key, const boost::any& value);
|
||||
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText);
|
||||
bool current_preset_is_dirty();
|
||||
|
||||
DynamicPrintConfig* get_config() { return m_config; }
|
||||
PresetCollection* get_presets() { return m_presets; }
|
||||
std::vector<std::string> get_dependent_tabs() { return m_reload_dependent_tabs; }
|
||||
size_t get_selected_preset_item() { return m_selected_preset_item; }
|
||||
|
||||
void on_value_change(const std::string& opt_key, const boost::any& value);
|
||||
|
||||
void update_wiping_button_visibility();
|
||||
protected:
|
||||
void on_presets_changed();
|
||||
void update_preset_description_line();
|
||||
void update_frequently_changed_parameters();
|
||||
void fill_icon_descriptions();
|
||||
void set_tooltips_text();
|
||||
};
|
||||
|
||||
//Slic3r::GUI::Tab::Print;
|
||||
class TabPrint : public Tab
|
||||
{
|
||||
public:
|
||||
TabPrint() {}
|
||||
TabPrint(wxNotebook* parent) :
|
||||
Tab(parent, _(L("Print Settings")), "print") {}
|
||||
~TabPrint(){}
|
||||
|
||||
ogStaticText* m_recommended_thin_wall_thickness_description_line;
|
||||
bool m_support_material_overhangs_queried = false;
|
||||
|
||||
void build() override;
|
||||
void reload_config() override;
|
||||
void update() override;
|
||||
void OnActivate() override;
|
||||
};
|
||||
|
||||
//Slic3r::GUI::Tab::Filament;
|
||||
class TabFilament : public Tab
|
||||
{
|
||||
ogStaticText* m_volumetric_speed_description_line;
|
||||
ogStaticText* m_cooling_description_line;
|
||||
public:
|
||||
TabFilament() {}
|
||||
TabFilament(wxNotebook* parent) :
|
||||
Tab(parent, _(L("Filament Settings")), "filament") {}
|
||||
~TabFilament(){}
|
||||
|
||||
void build() override;
|
||||
void reload_config() override;
|
||||
void update() override;
|
||||
void OnActivate() override;
|
||||
};
|
||||
|
||||
//Slic3r::GUI::Tab::Printer;
|
||||
class TabPrinter : public Tab
|
||||
{
|
||||
bool m_has_single_extruder_MM_page = false;
|
||||
bool m_use_silent_mode = false;
|
||||
void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key);
|
||||
bool m_rebuild_kinematics_page = false;
|
||||
|
||||
std::vector<PageShp> m_pages_fff;
|
||||
std::vector<PageShp> m_pages_sla;
|
||||
public:
|
||||
wxButton* m_serial_test_btn;
|
||||
wxButton* m_print_host_test_btn;
|
||||
wxButton* m_printhost_browse_btn;
|
||||
|
||||
size_t m_extruders_count;
|
||||
size_t m_extruders_count_old = 0;
|
||||
size_t m_initial_extruders_count;
|
||||
size_t m_sys_extruders_count;
|
||||
|
||||
PrinterTechnology m_printer_technology = ptFFF;
|
||||
|
||||
TabPrinter() {}
|
||||
TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Printer Settings")), "printer") {}
|
||||
~TabPrinter(){}
|
||||
|
||||
void build() override;
|
||||
void build_fff();
|
||||
void build_sla();
|
||||
void update() override;
|
||||
void update_fff();
|
||||
void update_sla();
|
||||
void update_pages(); // update m_pages according to printer technology
|
||||
void update_serial_ports();
|
||||
void extruders_count_changed(size_t extruders_count);
|
||||
PageShp build_kinematics_page();
|
||||
void build_extruder_pages();
|
||||
void on_preset_loaded() override;
|
||||
void init_options_list() override;
|
||||
};
|
||||
|
||||
class TabSLAMaterial : public Tab
|
||||
{
|
||||
public:
|
||||
TabSLAMaterial() {}
|
||||
TabSLAMaterial(wxNotebook* parent) :
|
||||
Tab(parent, _(L("SLA Material Settings")), "sla_material") {}
|
||||
~TabSLAMaterial(){}
|
||||
|
||||
void build() override;
|
||||
void update() override;
|
||||
void init_options_list() override;
|
||||
};
|
||||
|
||||
class SavePresetWindow :public wxDialog
|
||||
{
|
||||
public:
|
||||
SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _(L("Save preset"))){}
|
||||
~SavePresetWindow(){}
|
||||
|
||||
std::string m_chosen_name;
|
||||
wxComboBox* m_combo;
|
||||
|
||||
void build(const wxString& title, const std::string& default_name, std::vector<std::string> &values);
|
||||
void accept();
|
||||
std::string get_name() { return m_chosen_name; }
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* slic3r_Tab_hpp_ */
|
||||
20
src/slic3r/GUI/TabIface.cpp
Normal file
20
src/slic3r/GUI/TabIface.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#include "TabIface.hpp"
|
||||
#include "Tab.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void TabIface::load_current_preset() { m_tab->load_current_preset(); }
|
||||
void TabIface::update_tab_ui() { m_tab->update_tab_ui(); }
|
||||
void TabIface::update_ui_from_settings() { m_tab->update_ui_from_settings();}
|
||||
void TabIface::select_preset(char* name) { m_tab->select_preset(name);}
|
||||
void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*config);}
|
||||
void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast<std::string>(value)); }
|
||||
bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();}
|
||||
void TabIface::OnActivate() { return m_tab->OnActivate();}
|
||||
size_t TabIface::get_selected_preset_item() { return m_tab->get_selected_preset_item(); }
|
||||
std::string TabIface::title() { return m_tab->title().ToUTF8().data(); }
|
||||
DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); }
|
||||
PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; }
|
||||
std::vector<std::string> TabIface::get_dependent_tabs() { return m_tab->get_dependent_tabs(); }
|
||||
|
||||
}; // namespace Slic3r
|
||||
41
src/slic3r/GUI/TabIface.hpp
Normal file
41
src/slic3r/GUI/TabIface.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef slic3r_TabIface_hpp_
|
||||
#define slic3r_TabIface_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
class DynamicPrintConfig;
|
||||
class PresetCollection;
|
||||
|
||||
namespace GUI {
|
||||
class Tab;
|
||||
}
|
||||
|
||||
class TabIface {
|
||||
public:
|
||||
TabIface() : m_tab(nullptr) {}
|
||||
TabIface(GUI::Tab *tab) : m_tab(tab) {}
|
||||
// TabIface(const TabIface &rhs) : m_tab(rhs.m_tab) {}
|
||||
|
||||
void load_current_preset();
|
||||
void update_tab_ui();
|
||||
void update_ui_from_settings();
|
||||
void select_preset(char* name);
|
||||
std::string title();
|
||||
void load_config(DynamicPrintConfig* config);
|
||||
void load_key_value(char* opt_key, char* value);
|
||||
bool current_preset_is_dirty();
|
||||
void OnActivate();
|
||||
DynamicPrintConfig* get_config();
|
||||
PresetCollection* get_presets();
|
||||
std::vector<std::string> get_dependent_tabs();
|
||||
size_t get_selected_preset_item();
|
||||
|
||||
protected:
|
||||
GUI::Tab *m_tab;
|
||||
}; // namespace GUI
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_TabIface_hpp_ */
|
||||
196
src/slic3r/GUI/UpdateDialogs.cpp
Normal file
196
src/slic3r/GUI/UpdateDialogs.cpp
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
#include "UpdateDialogs.hpp"
|
||||
|
||||
#include <wx/settings.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/hyperlink.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
static const std::string CONFIG_UPDATE_WIKI_URL("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update");
|
||||
|
||||
|
||||
// MsgUpdateSlic3r
|
||||
|
||||
MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online) :
|
||||
MsgDialog(nullptr, _(L("Update available")), _(L("New version of Slic3r PE is available"))),
|
||||
ver_current(ver_current),
|
||||
ver_online(ver_online)
|
||||
{
|
||||
const auto url = wxString::Format("https://github.com/prusa3d/Slic3r/releases/tag/version_%s", ver_online.to_string());
|
||||
auto *link = new wxHyperlinkCtrl(this, wxID_ANY, url, url);
|
||||
|
||||
auto *text = new wxStaticText(this, wxID_ANY, _(L("To download, follow the link below.")));
|
||||
const auto link_width = link->GetSize().GetWidth();
|
||||
text->Wrap(CONTENT_WIDTH > link_width ? CONTENT_WIDTH : link_width);
|
||||
content_sizer->Add(text);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
|
||||
versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:"))));
|
||||
versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string()));
|
||||
versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:"))));
|
||||
versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string()));
|
||||
content_sizer->Add(versions);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
content_sizer->Add(link);
|
||||
content_sizer->AddSpacer(2*VERT_SPACING);
|
||||
|
||||
cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more")));
|
||||
content_sizer->Add(cbox);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
Fit();
|
||||
}
|
||||
|
||||
MsgUpdateSlic3r::~MsgUpdateSlic3r() {}
|
||||
|
||||
bool MsgUpdateSlic3r::disable_version_check() const
|
||||
{
|
||||
return cbox->GetValue();
|
||||
}
|
||||
|
||||
|
||||
// MsgUpdateConfig
|
||||
|
||||
MsgUpdateConfig::MsgUpdateConfig(const std::unordered_map<std::string, std::string> &updates) :
|
||||
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE)
|
||||
{
|
||||
auto *text = new wxStaticText(this, wxID_ANY, _(L(
|
||||
"Would you like to install it?\n\n"
|
||||
"Note that a full configuration snapshot will be created first. It can then be restored at any time "
|
||||
"should there be a problem with the new version.\n\n"
|
||||
"Updated configuration bundles:"
|
||||
)));
|
||||
text->Wrap(CONTENT_WIDTH);
|
||||
content_sizer->Add(text);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
|
||||
for (const auto &update : updates) {
|
||||
auto *text_vendor = new wxStaticText(this, wxID_ANY, update.first);
|
||||
text_vendor->SetFont(boldfont);
|
||||
versions->Add(text_vendor);
|
||||
versions->Add(new wxStaticText(this, wxID_ANY, update.second));
|
||||
}
|
||||
|
||||
content_sizer->Add(versions);
|
||||
content_sizer->AddSpacer(2*VERT_SPACING);
|
||||
|
||||
auto *btn_cancel = new wxButton(this, wxID_CANCEL);
|
||||
btn_sizer->Add(btn_cancel);
|
||||
btn_sizer->AddSpacer(HORIZ_SPACING);
|
||||
auto *btn_ok = new wxButton(this, wxID_OK);
|
||||
btn_sizer->Add(btn_ok);
|
||||
btn_ok->SetFocus();
|
||||
|
||||
Fit();
|
||||
}
|
||||
|
||||
MsgUpdateConfig::~MsgUpdateConfig() {}
|
||||
|
||||
|
||||
// MsgDataIncompatible
|
||||
|
||||
MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats) :
|
||||
MsgDialog(nullptr, _(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), wxID_NONE)
|
||||
{
|
||||
auto *text = new wxStaticText(this, wxID_ANY, _(L(
|
||||
"This version of Slic3r PE is not compatible with currently installed configuration bundles.\n"
|
||||
"This probably happened as a result of running an older Slic3r PE after using a newer one.\n\n"
|
||||
|
||||
"You may either exit Slic3r and try again with a newer version, or you may re-run the initial configuration. "
|
||||
"Doing so will create a backup snapshot of the existing configuration before installing files compatible with this Slic3r.\n"
|
||||
)));
|
||||
text->Wrap(CONTENT_WIDTH);
|
||||
content_sizer->Add(text);
|
||||
|
||||
auto *text2 = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("This Slic3r PE version: %s")), SLIC3R_VERSION));
|
||||
text2->Wrap(CONTENT_WIDTH);
|
||||
content_sizer->Add(text2);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
auto *text3 = new wxStaticText(this, wxID_ANY, _(L("Incompatible bundles:")));
|
||||
text3->Wrap(CONTENT_WIDTH);
|
||||
content_sizer->Add(text3);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
|
||||
for (const auto &incompat : incompats) {
|
||||
auto *text_vendor = new wxStaticText(this, wxID_ANY, incompat.first);
|
||||
text_vendor->SetFont(boldfont);
|
||||
versions->Add(text_vendor);
|
||||
versions->Add(new wxStaticText(this, wxID_ANY, incompat.second));
|
||||
}
|
||||
|
||||
content_sizer->Add(versions);
|
||||
content_sizer->AddSpacer(2*VERT_SPACING);
|
||||
|
||||
auto *btn_exit = new wxButton(this, wxID_EXIT, _(L("Exit Slic3r")));
|
||||
btn_sizer->Add(btn_exit);
|
||||
btn_sizer->AddSpacer(HORIZ_SPACING);
|
||||
auto *btn_reconf = new wxButton(this, wxID_REPLACE, _(L("Re-configure")));
|
||||
btn_sizer->Add(btn_reconf);
|
||||
btn_exit->SetFocus();
|
||||
|
||||
auto exiter = [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); };
|
||||
btn_exit->Bind(wxEVT_BUTTON, exiter);
|
||||
btn_reconf->Bind(wxEVT_BUTTON, exiter);
|
||||
|
||||
Fit();
|
||||
}
|
||||
|
||||
MsgDataIncompatible::~MsgDataIncompatible() {}
|
||||
|
||||
|
||||
// MsgDataLegacy
|
||||
|
||||
MsgDataLegacy::MsgDataLegacy() :
|
||||
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update")))
|
||||
{
|
||||
auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(
|
||||
_(L(
|
||||
"Slic3r PE now uses an updated configuration structure.\n\n"
|
||||
|
||||
"So called 'System presets' have been introduced, which hold the built-in default settings for various "
|
||||
"printers. These System presets cannot be modified, instead, users now may create their "
|
||||
"own presets inheriting settings from one of the System presets.\n"
|
||||
"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n"
|
||||
|
||||
"Please proceed with the %s that follows to set up the new presets "
|
||||
"and to choose whether to enable automatic preset updates."
|
||||
)),
|
||||
ConfigWizard::name()
|
||||
));
|
||||
text->Wrap(CONTENT_WIDTH);
|
||||
content_sizer->Add(text);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
auto *text2 = new wxStaticText(this, wxID_ANY, _(L("For more information please visit our wiki page:")));
|
||||
static const wxString url("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update");
|
||||
// The wiki page name is intentionally not localized:
|
||||
auto *link = new wxHyperlinkCtrl(this, wxID_ANY, "Slic3r PE 1.40 configuration update", CONFIG_UPDATE_WIKI_URL);
|
||||
content_sizer->Add(text2);
|
||||
content_sizer->Add(link);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
Fit();
|
||||
}
|
||||
|
||||
MsgDataLegacy::~MsgDataLegacy() {}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
81
src/slic3r/GUI/UpdateDialogs.hpp
Normal file
81
src/slic3r/GUI/UpdateDialogs.hpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#ifndef slic3r_UpdateDialogs_hpp_
|
||||
#define slic3r_UpdateDialogs_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "slic3r/Utils/Semver.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
|
||||
class wxBoxSizer;
|
||||
class wxCheckBox;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
// A confirmation dialog listing configuration updates
|
||||
class MsgUpdateSlic3r : public MsgDialog
|
||||
{
|
||||
public:
|
||||
MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online);
|
||||
MsgUpdateSlic3r(MsgUpdateSlic3r &&) = delete;
|
||||
MsgUpdateSlic3r(const MsgUpdateSlic3r &) = delete;
|
||||
MsgUpdateSlic3r &operator=(MsgUpdateSlic3r &&) = delete;
|
||||
MsgUpdateSlic3r &operator=(const MsgUpdateSlic3r &) = delete;
|
||||
virtual ~MsgUpdateSlic3r();
|
||||
|
||||
// Tells whether the user checked the "don't bother me again" checkbox
|
||||
bool disable_version_check() const;
|
||||
|
||||
private:
|
||||
const Semver &ver_current;
|
||||
const Semver &ver_online;
|
||||
wxCheckBox *cbox;
|
||||
};
|
||||
|
||||
|
||||
// Confirmation dialog informing about configuration update. Lists updated bundles & their versions.
|
||||
class MsgUpdateConfig : public MsgDialog
|
||||
{
|
||||
public:
|
||||
// updates is a map of "vendor name" -> "version (comment)"
|
||||
MsgUpdateConfig(const std::unordered_map<std::string, std::string> &updates);
|
||||
MsgUpdateConfig(MsgUpdateConfig &&) = delete;
|
||||
MsgUpdateConfig(const MsgUpdateConfig &) = delete;
|
||||
MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete;
|
||||
MsgUpdateConfig &operator=(const MsgUpdateConfig &) = delete;
|
||||
~MsgUpdateConfig();
|
||||
};
|
||||
|
||||
// Informs about currently installed bundles not being compatible with the running Slic3r. Asks about action.
|
||||
class MsgDataIncompatible : public MsgDialog
|
||||
{
|
||||
public:
|
||||
// incompats is a map of "vendor name" -> "version restrictions"
|
||||
MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats);
|
||||
MsgDataIncompatible(MsgDataIncompatible &&) = delete;
|
||||
MsgDataIncompatible(const MsgDataIncompatible &) = delete;
|
||||
MsgDataIncompatible &operator=(MsgDataIncompatible &&) = delete;
|
||||
MsgDataIncompatible &operator=(const MsgDataIncompatible &) = delete;
|
||||
~MsgDataIncompatible();
|
||||
};
|
||||
|
||||
// Informs about a legacy data directory - an update from Slic3r PE < 1.40
|
||||
class MsgDataLegacy : public MsgDialog
|
||||
{
|
||||
public:
|
||||
MsgDataLegacy();
|
||||
MsgDataLegacy(MsgDataLegacy &&) = delete;
|
||||
MsgDataLegacy(const MsgDataLegacy &) = delete;
|
||||
MsgDataLegacy &operator=(MsgDataLegacy &&) = delete;
|
||||
MsgDataLegacy &operator=(const MsgDataLegacy &) = delete;
|
||||
~MsgDataLegacy();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
16
src/slic3r/GUI/Widget.hpp
Normal file
16
src/slic3r/GUI/Widget.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef WIDGET_HPP
|
||||
#define WIDGET_HPP
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOM
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
|
||||
class Widget {
|
||||
protected:
|
||||
wxSizer* _sizer;
|
||||
public:
|
||||
Widget(): _sizer(nullptr) { }
|
||||
bool valid() const { return _sizer != nullptr; }
|
||||
wxSizer* sizer() const { return _sizer; }
|
||||
};
|
||||
#endif
|
||||
338
src/slic3r/GUI/WipeTowerDialog.cpp
Normal file
338
src/slic3r/GUI/WipeTowerDialog.cpp
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include "WipeTowerDialog.hpp"
|
||||
#include "GUI.hpp"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
|
||||
RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters)
|
||||
: wxDialog(parent, wxID_ANY, _(L("Ramming customization")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
|
||||
{
|
||||
m_panel_ramming = new RammingPanel(this,parameters);
|
||||
|
||||
// Not found another way of getting the background colours of RammingDialog, RammingPanel and Chart correct than setting
|
||||
// them all explicitely. Reading the parent colour yielded colour that didn't really match it, no wxSYS_COLOUR_... matched
|
||||
// colour used for the dialog. Same issue (and "solution") here : https://forums.wxwidgets.org/viewtopic.php?f=1&t=39608
|
||||
// Whoever can fix this, feel free to do so.
|
||||
this-> SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
|
||||
m_panel_ramming->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
|
||||
m_panel_ramming->Show(true);
|
||||
this->Show();
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(m_panel_ramming, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);
|
||||
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, 10);
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); });
|
||||
|
||||
this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) {
|
||||
m_output_data = m_panel_ramming->get_parameters();
|
||||
EndModal(wxID_OK);
|
||||
},wxID_OK);
|
||||
this->Show();
|
||||
wxMessageDialog(this,_(L("Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to "
|
||||
"properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself "
|
||||
"be reinserted later. This phase is important and different materials can require different extrusion speeds to get "
|
||||
"the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level "
|
||||
"setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc.")),_(L("Warning")),wxOK|wxICON_EXCLAMATION).ShowModal();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/)
|
||||
{
|
||||
auto sizer_chart = new wxBoxSizer(wxVERTICAL);
|
||||
auto sizer_param = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
std::stringstream stream{ parameters };
|
||||
stream >> m_ramming_line_width_multiplicator >> m_ramming_step_multiplicator;
|
||||
int ramming_speed_size = 0;
|
||||
float dummy = 0.f;
|
||||
while (stream >> dummy)
|
||||
++ramming_speed_size;
|
||||
stream.clear();
|
||||
stream.get();
|
||||
|
||||
std::vector<std::pair<float, float>> buttons;
|
||||
float x = 0.f;
|
||||
float y = 0.f;
|
||||
while (stream >> x >> y)
|
||||
buttons.push_back(std::make_pair(x, y));
|
||||
|
||||
m_chart = new Chart(this, wxRect(10, 10, 480, 360), buttons, ramming_speed_size, 0.25f);
|
||||
m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in RammingDialog constructor
|
||||
sizer_chart->Add(m_chart, 0, wxALL, 5);
|
||||
|
||||
m_widget_time = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0.,5.0,3.,0.5);
|
||||
m_widget_volume = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0,10000,0);
|
||||
m_widget_ramming_line_width_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100);
|
||||
m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100);
|
||||
|
||||
auto gsizer_param = new wxFlexGridSizer(2, 5, 15);
|
||||
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time")) + " (" + _(L("s")) + "):")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
gsizer_param->Add(m_widget_time);
|
||||
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total rammed volume")) + " (" + _(L("mm")) + wxString("³):", wxConvUTF8))), 0, wxALIGN_CENTER_VERTICAL);
|
||||
gsizer_param->Add(m_widget_volume);
|
||||
gsizer_param->AddSpacer(20);
|
||||
gsizer_param->AddSpacer(20);
|
||||
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line width")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
gsizer_param->Add(m_widget_ramming_line_width_multiplicator);
|
||||
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
gsizer_param->Add(m_widget_ramming_step_multiplicator);
|
||||
|
||||
sizer_param->Add(gsizer_param, 0, wxTOP, 100);
|
||||
|
||||
m_widget_time->SetValue(m_chart->get_time());
|
||||
m_widget_time->SetDigits(2);
|
||||
m_widget_volume->SetValue(m_chart->get_volume());
|
||||
m_widget_volume->Disable();
|
||||
m_widget_ramming_line_width_multiplicator->SetValue(m_ramming_line_width_multiplicator);
|
||||
m_widget_ramming_step_multiplicator->SetValue(m_ramming_step_multiplicator);
|
||||
|
||||
m_widget_ramming_step_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); });
|
||||
m_widget_ramming_line_width_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); });
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(sizer_chart, 0, wxALL, 5);
|
||||
sizer->Add(sizer_param, 0, wxALL, 10);
|
||||
|
||||
sizer->SetSizeHints(this);
|
||||
SetSizer(sizer);
|
||||
|
||||
m_widget_time->Bind(wxEVT_TEXT,[this](wxCommandEvent&) {m_chart->set_xy_range(m_widget_time->GetValue(),-1);});
|
||||
m_widget_time->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value
|
||||
m_widget_volume->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value
|
||||
Bind(EVT_WIPE_TOWER_CHART_CHANGED,[this](wxCommandEvent&) {m_widget_volume->SetValue(m_chart->get_volume()); m_widget_time->SetValue(m_chart->get_time());} );
|
||||
Refresh(this);
|
||||
}
|
||||
|
||||
void RammingPanel::line_parameters_changed() {
|
||||
m_ramming_line_width_multiplicator = m_widget_ramming_line_width_multiplicator->GetValue();
|
||||
m_ramming_step_multiplicator = m_widget_ramming_step_multiplicator->GetValue();
|
||||
}
|
||||
|
||||
std::string RammingPanel::get_parameters()
|
||||
{
|
||||
std::vector<float> speeds = m_chart->get_ramming_speed(0.25f);
|
||||
std::vector<std::pair<float,float>> buttons = m_chart->get_buttons();
|
||||
std::stringstream stream;
|
||||
stream << m_ramming_line_width_multiplicator << " " << m_ramming_step_multiplicator;
|
||||
for (const float& speed_value : speeds)
|
||||
stream << " " << speed_value;
|
||||
stream << "|";
|
||||
for (const auto& button : buttons)
|
||||
stream << " " << button.first << " " << button.second;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
|
||||
#define ITEM_WIDTH 60
|
||||
// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode:
|
||||
WipingDialog::WipingDialog(wxWindow* parent,const std::vector<float>& matrix, const std::vector<float>& extruders)
|
||||
: wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
|
||||
{
|
||||
auto widget_button = new wxButton(this,wxID_ANY,"-",wxPoint(0,0),wxDefaultSize);
|
||||
m_panel_wiping = new WipingPanel(this,matrix,extruders, widget_button);
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// set min sizer width according to extruders count
|
||||
const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH);
|
||||
main_sizer->SetMinSize(wxSize(sizer_width, -1));
|
||||
|
||||
main_sizer->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5);
|
||||
main_sizer->Add(widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5);
|
||||
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); });
|
||||
|
||||
this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { // if OK button is clicked..
|
||||
m_output_matrix = m_panel_wiping->read_matrix_values(); // ..query wiping panel and save returned values
|
||||
m_output_extruders = m_panel_wiping->read_extruders_values(); // so they can be recovered later by calling get_...()
|
||||
EndModal(wxID_OK);
|
||||
},wxID_OK);
|
||||
|
||||
this->Show();
|
||||
}
|
||||
|
||||
// This function allows to "play" with sizers parameters (like align or border)
|
||||
void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift/*=0*/)
|
||||
{
|
||||
sizer->Add(new wxStaticText(page, wxID_ANY, info,wxDefaultPosition,wxSize(0,50)), 0, wxEXPAND | wxLEFT, 15);
|
||||
auto table_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift);
|
||||
table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50);
|
||||
table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP, 10);
|
||||
}
|
||||
|
||||
// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers)
|
||||
WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, wxButton* widget_button)
|
||||
: wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/)
|
||||
{
|
||||
m_widget_button = widget_button; // pointer to the button in parent dialog
|
||||
m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){ toggle_advanced(true); });
|
||||
|
||||
m_number_of_extruders = (int)(sqrt(matrix.size())+0.001);
|
||||
|
||||
// Create two switched panels with their own sizers
|
||||
m_sizer_simple = new wxBoxSizer(wxVERTICAL);
|
||||
m_sizer_advanced = new wxBoxSizer(wxVERTICAL);
|
||||
m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
m_page_advanced = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
m_page_simple->SetSizer(m_sizer_simple);
|
||||
m_page_advanced->SetSizer(m_sizer_advanced);
|
||||
|
||||
auto gridsizer_simple = new wxGridSizer(3, 5, 10);
|
||||
m_gridsizer_advanced = new wxGridSizer(m_number_of_extruders+1, 5, 1);
|
||||
|
||||
// First create controls for advanced mode and assign them to m_page_advanced:
|
||||
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
|
||||
edit_boxes.push_back(std::vector<wxTextCtrl*>(0));
|
||||
|
||||
for (unsigned int j = 0; j < m_number_of_extruders; ++j) {
|
||||
edit_boxes.back().push_back(new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH, -1)));
|
||||
if (i == j)
|
||||
edit_boxes[i][j]->Disable();
|
||||
else
|
||||
edit_boxes[i][j]->SetValue(wxString("") << int(matrix[m_number_of_extruders*j + i]));
|
||||
}
|
||||
}
|
||||
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("")));
|
||||
for (unsigned int i = 0; i < m_number_of_extruders; ++i)
|
||||
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
|
||||
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
|
||||
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
|
||||
for (unsigned int j = 0; j < m_number_of_extruders; ++j)
|
||||
m_gridsizer_advanced->Add(edit_boxes[j][i], 0);
|
||||
}
|
||||
|
||||
// collect and format sizer
|
||||
format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced,
|
||||
_(L("Here you can adjust required purging volume (mm³) for any given pair of tools.")),
|
||||
_(L("Extruder changed to")));
|
||||
|
||||
// Hide preview page before new page creating
|
||||
// It allows to do that from a beginning of the main panel
|
||||
m_page_advanced->Hide();
|
||||
|
||||
// Now the same for simple mode:
|
||||
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString("")), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
|
||||
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("unloaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
|
||||
gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
for (unsigned int i=0;i<m_number_of_extruders;++i) {
|
||||
m_old.push_back(new wxSpinCtrl(m_page_simple,wxID_ANY,wxEmptyString,wxDefaultPosition, wxSize(80, -1),wxSP_ARROW_KEYS|wxALIGN_RIGHT,0,300,extruders[2*i]));
|
||||
m_new.push_back(new wxSpinCtrl(m_page_simple,wxID_ANY,wxEmptyString,wxDefaultPosition, wxSize(80, -1),wxSP_ARROW_KEYS|wxALIGN_RIGHT,0,300,extruders[2*i+1]));
|
||||
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Tool #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
||||
gridsizer_simple->Add(m_old.back(),0);
|
||||
gridsizer_simple->Add(m_new.back(),0);
|
||||
}
|
||||
|
||||
// collect and format sizer
|
||||
format_sizer(m_sizer_simple, m_page_simple, gridsizer_simple,
|
||||
_(L("Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded.")),
|
||||
_(L("Volume to purge (mm³) when the filament is being")), 50);
|
||||
|
||||
m_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_sizer->Add(m_page_simple, 0, wxEXPAND | wxALL, 25);
|
||||
m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 25);
|
||||
|
||||
m_sizer->SetSizeHints(this);
|
||||
SetSizer(m_sizer);
|
||||
|
||||
toggle_advanced(); // to show/hide what is appropriate
|
||||
|
||||
m_page_advanced->Bind(wxEVT_PAINT,[this](wxPaintEvent&) {
|
||||
wxPaintDC dc(m_page_advanced);
|
||||
int y_pos = 0.5 * (edit_boxes[0][0]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetSize().y);
|
||||
wxString label = _(L("From"));
|
||||
int text_width = 0;
|
||||
int text_height = 0;
|
||||
dc.GetTextExtent(label,&text_width,&text_height);
|
||||
int xpos = m_gridsizer_advanced->GetPosition().x;
|
||||
dc.DrawRotatedText(label,xpos-text_height,y_pos + text_width/2.f,90);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Reads values from the (advanced) wiping matrix:
|
||||
std::vector<float> WipingPanel::read_matrix_values() {
|
||||
if (!m_advanced)
|
||||
fill_in_matrix();
|
||||
std::vector<float> output;
|
||||
for (unsigned int i=0;i<m_number_of_extruders;++i) {
|
||||
for (unsigned int j=0;j<m_number_of_extruders;++j) {
|
||||
double val = 0.;
|
||||
edit_boxes[j][i]->GetValue().ToDouble(&val);
|
||||
output.push_back((float)val);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// Reads values from simple mode to save them for next time:
|
||||
std::vector<float> WipingPanel::read_extruders_values() {
|
||||
std::vector<float> output;
|
||||
for (unsigned int i=0;i<m_number_of_extruders;++i) {
|
||||
output.push_back(m_old[i]->GetValue());
|
||||
output.push_back(m_new[i]->GetValue());
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// This updates the "advanced" matrix based on values from "simple" mode
|
||||
void WipingPanel::fill_in_matrix() {
|
||||
for (unsigned i=0;i<m_number_of_extruders;++i) {
|
||||
for (unsigned j=0;j<m_number_of_extruders;++j) {
|
||||
if (i==j) continue;
|
||||
edit_boxes[j][i]->SetValue(wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Function to check if simple and advanced settings are matching
|
||||
bool WipingPanel::advanced_matches_simple() {
|
||||
for (unsigned i=0;i<m_number_of_extruders;++i) {
|
||||
for (unsigned j=0;j<m_number_of_extruders;++j) {
|
||||
if (i==j) continue;
|
||||
if (edit_boxes[j][i]->GetValue() != (wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue())))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Switches the dialog from simple to advanced mode and vice versa
|
||||
void WipingPanel::toggle_advanced(bool user_action) {
|
||||
if (m_advanced && !advanced_matches_simple() && user_action) {
|
||||
if (wxMessageDialog(this,wxString(_(L("Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?"))),
|
||||
wxString(_(L("Warning"))),wxYES_NO|wxICON_EXCLAMATION).ShowModal() != wxID_YES)
|
||||
return;
|
||||
}
|
||||
if (user_action)
|
||||
m_advanced = !m_advanced; // user demands a change -> toggle
|
||||
else
|
||||
m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate
|
||||
|
||||
(m_advanced ? m_page_advanced : m_page_simple)->Show();
|
||||
(!m_advanced ? m_page_advanced : m_page_simple)->Hide();
|
||||
|
||||
m_widget_button->SetLabel(m_advanced ? _(L("Show simplified settings")) : _(L("Show advanced settings")));
|
||||
if (m_advanced)
|
||||
if (user_action) fill_in_matrix(); // otherwise keep values loaded from config
|
||||
|
||||
m_sizer->Layout();
|
||||
Refresh();
|
||||
}
|
||||
90
src/slic3r/GUI/WipeTowerDialog.hpp
Normal file
90
src/slic3r/GUI/WipeTowerDialog.hpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#ifndef _WIPE_TOWER_DIALOG_H_
|
||||
#define _WIPE_TOWER_DIALOG_H_
|
||||
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
#include "RammingChart.hpp"
|
||||
|
||||
|
||||
class RammingPanel : public wxPanel {
|
||||
public:
|
||||
RammingPanel(wxWindow* parent);
|
||||
RammingPanel(wxWindow* parent,const std::string& data);
|
||||
std::string get_parameters();
|
||||
|
||||
private:
|
||||
Chart* m_chart = nullptr;
|
||||
wxSpinCtrl* m_widget_volume = nullptr;
|
||||
wxSpinCtrl* m_widget_ramming_line_width_multiplicator = nullptr;
|
||||
wxSpinCtrl* m_widget_ramming_step_multiplicator = nullptr;
|
||||
wxSpinCtrlDouble* m_widget_time = nullptr;
|
||||
int m_ramming_step_multiplicator;
|
||||
int m_ramming_line_width_multiplicator;
|
||||
|
||||
void line_parameters_changed();
|
||||
};
|
||||
|
||||
|
||||
class RammingDialog : public wxDialog {
|
||||
public:
|
||||
RammingDialog(wxWindow* parent,const std::string& parameters);
|
||||
std::string get_parameters() { return m_output_data; }
|
||||
private:
|
||||
RammingPanel* m_panel_ramming = nullptr;
|
||||
std::string m_output_data;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class WipingPanel : public wxPanel {
|
||||
public:
|
||||
WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, wxButton* widget_button);
|
||||
std::vector<float> read_matrix_values();
|
||||
std::vector<float> read_extruders_values();
|
||||
void toggle_advanced(bool user_action = false);
|
||||
void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift=0);
|
||||
|
||||
private:
|
||||
void fill_in_matrix();
|
||||
bool advanced_matches_simple();
|
||||
|
||||
std::vector<wxSpinCtrl*> m_old;
|
||||
std::vector<wxSpinCtrl*> m_new;
|
||||
std::vector<std::vector<wxTextCtrl*>> edit_boxes;
|
||||
unsigned int m_number_of_extruders = 0;
|
||||
bool m_advanced = false;
|
||||
wxPanel* m_page_simple = nullptr;
|
||||
wxPanel* m_page_advanced = nullptr;
|
||||
wxBoxSizer* m_sizer = nullptr;
|
||||
wxBoxSizer* m_sizer_simple = nullptr;
|
||||
wxBoxSizer* m_sizer_advanced = nullptr;
|
||||
wxGridSizer* m_gridsizer_advanced = nullptr;
|
||||
wxButton* m_widget_button = nullptr;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class WipingDialog : public wxDialog {
|
||||
public:
|
||||
WipingDialog(wxWindow* parent,const std::vector<float>& matrix, const std::vector<float>& extruders);
|
||||
std::vector<float> get_matrix() const { return m_output_matrix; }
|
||||
std::vector<float> get_extruders() const { return m_output_extruders; }
|
||||
|
||||
|
||||
private:
|
||||
WipingPanel* m_panel_wiping = nullptr;
|
||||
std::vector<float> m_output_matrix;
|
||||
std::vector<float> m_output_extruders;
|
||||
};
|
||||
|
||||
#endif // _WIPE_TOWER_DIALOG_H_
|
||||
32
src/slic3r/GUI/callback.hpp
Normal file
32
src/slic3r/GUI/callback.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// I AM A PHONY PLACEHOLDER FOR THE PERL CALLBACK.
|
||||
// GET RID OF ME!
|
||||
|
||||
#ifndef slic3r_GUI_PerlCallback_phony_hpp_
|
||||
#define slic3r_GUI_PerlCallback_phony_hpp_
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PerlCallback {
|
||||
public:
|
||||
// PerlCallback(void *) {}
|
||||
PerlCallback() {}
|
||||
void register_callback(void *) {}
|
||||
void deregister_callback() {}
|
||||
void call() const {}
|
||||
void call(int) const {}
|
||||
void call(int, int) const {}
|
||||
void call(const std::vector<int>&) const {}
|
||||
void call(double) const {}
|
||||
void call(double, double) const {}
|
||||
void call(double, double, double) const {}
|
||||
void call(double, double, double, double) const {}
|
||||
void call(double, double, double, double, double) const {}
|
||||
void call(double, double, double, double, double, double) const {}
|
||||
void call(bool b) const {}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_PerlCallback_phony_hpp_ */
|
||||
1850
src/slic3r/GUI/wxExtensions.cpp
Normal file
1850
src/slic3r/GUI/wxExtensions.cpp
Normal file
File diff suppressed because it is too large
Load diff
800
src/slic3r/GUI/wxExtensions.hpp
Normal file
800
src/slic3r/GUI/wxExtensions.hpp
Normal file
|
|
@ -0,0 +1,800 @@
|
|||
#ifndef slic3r_GUI_wxExtensions_hpp_
|
||||
#define slic3r_GUI_wxExtensions_hpp_
|
||||
|
||||
#include <wx/checklst.h>
|
||||
#include <wx/combo.h>
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/dc.h>
|
||||
#include <wx/collpane.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
|
||||
wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
|
||||
std::function<void(wxCommandEvent& event)> cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr);
|
||||
|
||||
wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, const std::string& icon = "");
|
||||
|
||||
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
|
||||
{
|
||||
static const unsigned int DefaultWidth;
|
||||
static const unsigned int DefaultHeight;
|
||||
static const unsigned int DefaultItemHeight;
|
||||
|
||||
wxString m_text;
|
||||
|
||||
// Events sent on mouseclick are quite complex. Function OnListBoxSelection is supposed to pass the event to the checkbox, which works fine on
|
||||
// Win. On OSX and Linux the events are generated differently - clicking on the checkbox square generates the event twice (and the square
|
||||
// therefore seems not to respond).
|
||||
// This enum is meant to save current state of affairs, i.e., if the event forwarding is ok to do or not. It is only used on Linux
|
||||
// and OSX by some #ifdefs. It also stores information whether OnListBoxSelection is supposed to change the checkbox status,
|
||||
// or if it changed status on its own already (which happens when the square is clicked). More comments in OnCheckListBox(...)
|
||||
// There indeed is a better solution, maybe making a custom event used for the event passing to distinguish the original and passed message
|
||||
// and blocking one of them on OSX and Linux. Feel free to refactor, but carefully test on all platforms.
|
||||
enum class OnCheckListBoxFunction{
|
||||
FreeToProceed,
|
||||
RefuseToProceed,
|
||||
WasRefusedLastTime
|
||||
} m_check_box_events_status = OnCheckListBoxFunction::FreeToProceed;
|
||||
|
||||
|
||||
public:
|
||||
virtual bool Create(wxWindow* parent);
|
||||
virtual wxWindow* GetControl();
|
||||
virtual void SetStringValue(const wxString& value);
|
||||
virtual wxString GetStringValue() const;
|
||||
virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
|
||||
|
||||
virtual void OnKeyEvent(wxKeyEvent& evt);
|
||||
|
||||
void OnCheckListBox(wxCommandEvent& evt);
|
||||
void OnListBoxSelection(wxCommandEvent& evt);
|
||||
};
|
||||
|
||||
|
||||
// *** wxDataViewTreeCtrlComboBox ***
|
||||
|
||||
class wxDataViewTreeCtrlComboPopup: public wxDataViewTreeCtrl, public wxComboPopup
|
||||
{
|
||||
static const unsigned int DefaultWidth;
|
||||
static const unsigned int DefaultHeight;
|
||||
static const unsigned int DefaultItemHeight;
|
||||
|
||||
wxString m_text;
|
||||
int m_cnt_open_items{0};
|
||||
|
||||
public:
|
||||
virtual bool Create(wxWindow* parent);
|
||||
virtual wxWindow* GetControl() { return this; }
|
||||
virtual void SetStringValue(const wxString& value) { m_text = value; }
|
||||
virtual wxString GetStringValue() const { return m_text; }
|
||||
// virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
|
||||
|
||||
virtual void OnKeyEvent(wxKeyEvent& evt);
|
||||
void OnDataViewTreeCtrlSelection(wxCommandEvent& evt);
|
||||
void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
// *** PrusaCollapsiblePane ***
|
||||
// ----------------------------------------------------------------------------
|
||||
class PrusaCollapsiblePane : public wxCollapsiblePane
|
||||
{
|
||||
public:
|
||||
PrusaCollapsiblePane() {}
|
||||
PrusaCollapsiblePane(wxWindow *parent,
|
||||
wxWindowID winid,
|
||||
const wxString& label,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxCP_DEFAULT_STYLE,
|
||||
const wxValidator& val = wxDefaultValidator,
|
||||
const wxString& name = wxCollapsiblePaneNameStr)
|
||||
{
|
||||
Create(parent, winid, label, pos, size, style, val, name);
|
||||
}
|
||||
~PrusaCollapsiblePane() {}
|
||||
|
||||
void OnStateChange(const wxSize& sz); //override/hide of OnStateChange from wxCollapsiblePane
|
||||
virtual bool Show(bool show = true) override {
|
||||
wxCollapsiblePane::Show(show);
|
||||
OnStateChange(GetBestSize());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// *** PrusaCollapsiblePaneMSW *** used only #ifdef __WXMSW__
|
||||
// ----------------------------------------------------------------------------
|
||||
#ifdef __WXMSW__
|
||||
class PrusaCollapsiblePaneMSW : public PrusaCollapsiblePane//wxCollapsiblePane
|
||||
{
|
||||
wxButton* m_pDisclosureTriangleButton = nullptr;
|
||||
wxBitmap m_bmp_close;
|
||||
wxBitmap m_bmp_open;
|
||||
wxString m_strLabel;
|
||||
public:
|
||||
PrusaCollapsiblePaneMSW() {}
|
||||
PrusaCollapsiblePaneMSW( wxWindow *parent,
|
||||
wxWindowID winid,
|
||||
const wxString& label,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxCP_DEFAULT_STYLE,
|
||||
const wxValidator& val = wxDefaultValidator,
|
||||
const wxString& name = wxCollapsiblePaneNameStr)
|
||||
{
|
||||
Create(parent, winid, label, pos, size, style, val, name);
|
||||
}
|
||||
|
||||
~PrusaCollapsiblePaneMSW() {}
|
||||
|
||||
bool Create(wxWindow *parent,
|
||||
wxWindowID id,
|
||||
const wxString& label,
|
||||
const wxPoint& pos,
|
||||
const wxSize& size,
|
||||
long style,
|
||||
const wxValidator& val,
|
||||
const wxString& name);
|
||||
|
||||
void UpdateBtnBmp();
|
||||
void SetLabel(const wxString &label) override;
|
||||
bool Layout() override;
|
||||
void Collapse(bool collapse) override;
|
||||
};
|
||||
#endif //__WXMSW__
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PrusaDataViewBitmapText: helper class used by PrusaBitmapTextRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class PrusaDataViewBitmapText : public wxObject
|
||||
{
|
||||
public:
|
||||
PrusaDataViewBitmapText(const wxString &text = wxEmptyString,
|
||||
const wxBitmap& bmp = wxNullBitmap) :
|
||||
m_text(text), m_bmp(bmp)
|
||||
{ }
|
||||
|
||||
PrusaDataViewBitmapText(const PrusaDataViewBitmapText &other)
|
||||
: wxObject(),
|
||||
m_text(other.m_text),
|
||||
m_bmp(other.m_bmp)
|
||||
{ }
|
||||
|
||||
void SetText(const wxString &text) { m_text = text; }
|
||||
wxString GetText() const { return m_text; }
|
||||
void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; }
|
||||
const wxBitmap &GetBitmap() const { return m_bmp; }
|
||||
|
||||
bool IsSameAs(const PrusaDataViewBitmapText& other) const {
|
||||
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
|
||||
}
|
||||
|
||||
bool operator==(const PrusaDataViewBitmapText& other) const {
|
||||
return IsSameAs(other);
|
||||
}
|
||||
|
||||
bool operator!=(const PrusaDataViewBitmapText& other) const {
|
||||
return !IsSameAs(other);
|
||||
}
|
||||
|
||||
private:
|
||||
wxString m_text;
|
||||
wxBitmap m_bmp;
|
||||
|
||||
wxDECLARE_DYNAMIC_CLASS(PrusaDataViewBitmapText);
|
||||
};
|
||||
DECLARE_VARIANT_OBJECT(PrusaDataViewBitmapText)
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
enum ItemType{
|
||||
itUndef = 0,
|
||||
itObject = 1,
|
||||
itVolume = 2,
|
||||
itInstanceRoot = 4,
|
||||
itInstance = 8,
|
||||
itSettings = 16
|
||||
};
|
||||
|
||||
class PrusaObjectDataViewModelNode;
|
||||
WX_DEFINE_ARRAY_PTR(PrusaObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray);
|
||||
|
||||
class PrusaObjectDataViewModelNode
|
||||
{
|
||||
PrusaObjectDataViewModelNode* m_parent;
|
||||
MyObjectTreeModelNodePtrArray m_children;
|
||||
wxBitmap m_empty_bmp;
|
||||
size_t m_volumes_cnt = 0;
|
||||
std::vector< std::string > m_opt_categories;
|
||||
public:
|
||||
PrusaObjectDataViewModelNode(const wxString &name) {
|
||||
m_parent = NULL;
|
||||
m_name = name;
|
||||
m_type = itObject;
|
||||
#ifdef __WXGTK__
|
||||
// it's necessary on GTK because of control have to know if this item will be container
|
||||
// in another case you couldn't to add subitem for this item
|
||||
// it will be produce "segmentation fault"
|
||||
m_container = true;
|
||||
#endif //__WXGTK__
|
||||
set_object_action_icon();
|
||||
}
|
||||
|
||||
PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent,
|
||||
const wxString& sub_obj_name,
|
||||
const wxBitmap& bmp,
|
||||
const wxString& extruder,
|
||||
const int idx = -1 ) {
|
||||
m_parent = parent;
|
||||
m_name = sub_obj_name;
|
||||
m_bmp = bmp;
|
||||
m_type = itVolume;
|
||||
m_idx = idx;
|
||||
m_extruder = extruder;
|
||||
#ifdef __WXGTK__
|
||||
// it's necessary on GTK because of control have to know if this item will be container
|
||||
// in another case you couldn't to add subitem for this item
|
||||
// it will be produce "segmentation fault"
|
||||
m_container = true;
|
||||
#endif //__WXGTK__
|
||||
set_part_action_icon();
|
||||
}
|
||||
|
||||
PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent, const ItemType type) :
|
||||
m_parent(parent),
|
||||
m_type(type),
|
||||
m_extruder(wxEmptyString)
|
||||
{
|
||||
if (type == itSettings) {
|
||||
m_name = "Settings to modified";
|
||||
}
|
||||
else if (type == itInstanceRoot) {
|
||||
m_name = "Instances";
|
||||
#ifdef __WXGTK__
|
||||
m_container = true;
|
||||
#endif //__WXGTK__
|
||||
}
|
||||
else if (type == itInstance) {
|
||||
m_idx = parent->GetChildCount();
|
||||
m_name = wxString::Format("Instance_%d", m_idx+1);
|
||||
}
|
||||
}
|
||||
|
||||
~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;
|
||||
wxBitmap& m_bmp = m_empty_bmp;
|
||||
ItemType m_type;
|
||||
int m_idx = -1;
|
||||
bool m_container = false;
|
||||
wxString m_extruder = "default";
|
||||
wxBitmap m_action_icon;
|
||||
|
||||
bool IsContainer() const
|
||||
{
|
||||
return m_container;
|
||||
}
|
||||
|
||||
PrusaObjectDataViewModelNode* GetParent()
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
MyObjectTreeModelNodePtrArray& GetChildren()
|
||||
{
|
||||
return m_children;
|
||||
}
|
||||
PrusaObjectDataViewModelNode* GetNthChild(unsigned int n)
|
||||
{
|
||||
return m_children.Item(n);
|
||||
}
|
||||
void Insert(PrusaObjectDataViewModelNode* child, unsigned int n)
|
||||
{
|
||||
if (!m_container)
|
||||
m_container = true;
|
||||
m_children.Insert(child, n);
|
||||
}
|
||||
void Append(PrusaObjectDataViewModelNode* child)
|
||||
{
|
||||
if (!m_container)
|
||||
m_container = true;
|
||||
m_children.Add(child);
|
||||
}
|
||||
void RemoveAllChildren()
|
||||
{
|
||||
if (GetChildCount() == 0)
|
||||
return;
|
||||
for (size_t id = GetChildCount() - 1; id >= 0; --id)
|
||||
{
|
||||
if (m_children.Item(id)->GetChildCount() > 0)
|
||||
m_children[id]->RemoveAllChildren();
|
||||
auto node = m_children[id];
|
||||
m_children.RemoveAt(id);
|
||||
delete node;
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetChildCount() const
|
||||
{
|
||||
return m_children.GetCount();
|
||||
}
|
||||
|
||||
bool SetValue(const wxVariant &variant, unsigned int col)
|
||||
{
|
||||
switch (col)
|
||||
{
|
||||
case 0:{
|
||||
PrusaDataViewBitmapText data;
|
||||
data << variant;
|
||||
m_bmp = data.GetBitmap();
|
||||
m_name = data.GetText();
|
||||
return true;}
|
||||
case 1:
|
||||
m_extruder = variant.GetString();
|
||||
return true;
|
||||
case 2:
|
||||
m_action_icon << variant;
|
||||
return true;
|
||||
default:
|
||||
printf("MyObjectTreeModel::SetValue: wrong column");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetBitmap(const wxBitmap &icon)
|
||||
{
|
||||
m_bmp = icon;
|
||||
}
|
||||
|
||||
ItemType GetType() const {
|
||||
return m_type;
|
||||
}
|
||||
|
||||
void SetIdx(const int& idx) {
|
||||
m_idx = idx;
|
||||
// update name if this node is instance
|
||||
if (m_type == itInstance)
|
||||
m_name = wxString::Format("Instance_%d", m_idx + 1);
|
||||
}
|
||||
|
||||
int GetIdx() const {
|
||||
return m_idx;
|
||||
}
|
||||
|
||||
// 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_bmp = from_node.m_bmp;
|
||||
m_idx = from_node.m_idx;
|
||||
m_extruder = from_node.m_extruder;
|
||||
m_type = from_node.m_type;
|
||||
}
|
||||
|
||||
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_idx = m_children.Item(scnd_id)->m_idx;
|
||||
new_frst.m_idx = m_children.Item(frst_id)->m_idx;
|
||||
|
||||
m_children.Item(frst_id)->AssignAllVal(new_frst);
|
||||
m_children.Item(scnd_id)->AssignAllVal(new_scnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set action icons for node
|
||||
void set_object_action_icon();
|
||||
void set_part_action_icon();
|
||||
bool update_settings_digest(const std::vector<std::string>& categories);
|
||||
private:
|
||||
friend class PrusaObjectDataViewModel;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PrusaObjectDataViewModel
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class PrusaObjectDataViewModel :public wxDataViewModel
|
||||
{
|
||||
std::vector<PrusaObjectDataViewModelNode*> m_objects;
|
||||
public:
|
||||
PrusaObjectDataViewModel();
|
||||
~PrusaObjectDataViewModel();
|
||||
|
||||
wxDataViewItem Add(const wxString &name);
|
||||
wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item,
|
||||
const wxString &name,
|
||||
const wxBitmap& icon,
|
||||
const int extruder = 0,
|
||||
const bool create_frst_child = true);
|
||||
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
|
||||
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num);
|
||||
wxDataViewItem Delete(const wxDataViewItem &item);
|
||||
wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num);
|
||||
void DeleteAll();
|
||||
void DeleteChildren(wxDataViewItem& parent);
|
||||
wxDataViewItem GetItemById(int obj_idx);
|
||||
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
|
||||
int GetIdByItem(const wxDataViewItem& item);
|
||||
int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const;
|
||||
int GetVolumeIdByItem(const wxDataViewItem& item) const;
|
||||
int GetInstanceIdByItem(const wxDataViewItem& item) const;
|
||||
void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx);
|
||||
bool IsEmpty() { return m_objects.empty(); }
|
||||
|
||||
// helper method for wxLog
|
||||
|
||||
wxString GetName(const wxDataViewItem &item) const;
|
||||
wxBitmap& GetBitmap(const wxDataViewItem &item) const;
|
||||
|
||||
// helper methods to change the model
|
||||
|
||||
virtual unsigned int GetColumnCount() const override { return 3;}
|
||||
virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); }
|
||||
|
||||
virtual void GetValue(wxVariant &variant,
|
||||
const wxDataViewItem &item, unsigned int col) const override;
|
||||
virtual bool SetValue(const wxVariant &variant,
|
||||
const wxDataViewItem &item, unsigned int col) override;
|
||||
bool SetValue(const wxVariant &variant, const int item_idx, unsigned int col);
|
||||
|
||||
// wxDataViewItem MoveChildUp(const wxDataViewItem &item);
|
||||
// wxDataViewItem MoveChildDown(const wxDataViewItem &item);
|
||||
// For parent move child from cur_volume_id place to new_volume_id
|
||||
// Remaining items will moved up/down accordingly
|
||||
wxDataViewItem ReorganizeChildren(int cur_volume_id,
|
||||
int new_volume_id,
|
||||
const wxDataViewItem &parent);
|
||||
|
||||
virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override;
|
||||
|
||||
virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override;
|
||||
// get object item
|
||||
wxDataViewItem GetTopParent(const wxDataViewItem &item) const;
|
||||
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; }
|
||||
|
||||
ItemType GetItemType(const wxDataViewItem &item) const ;
|
||||
wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const;
|
||||
bool IsSettingsItem(const wxDataViewItem &item) const;
|
||||
void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector<std::string>& categories);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PrusaBitmapTextRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer
|
||||
{
|
||||
public:
|
||||
PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT,
|
||||
int align = wxDVR_DEFAULT_ALIGNMENT):
|
||||
wxDataViewCustomRenderer(wxT("PrusaDataViewBitmapText"), mode, align) {}
|
||||
|
||||
bool SetValue(const wxVariant &value);
|
||||
bool GetValue(wxVariant &value) const;
|
||||
|
||||
virtual bool Render(wxRect cell, wxDC *dc, int state);
|
||||
virtual wxSize GetSize() const;
|
||||
|
||||
virtual bool HasEditorCtrl() const { return false; }
|
||||
|
||||
private:
|
||||
PrusaDataViewBitmapText m_value;
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// MyCustomRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class MyCustomRenderer : public wxDataViewCustomRenderer
|
||||
{
|
||||
public:
|
||||
// This renderer can be either activatable or editable, for demonstration
|
||||
// purposes. In real programs, you should select whether the user should be
|
||||
// able to activate or edit the cell and it doesn't make sense to switch
|
||||
// between the two -- but this is just an example, so it doesn't stop us.
|
||||
explicit MyCustomRenderer(wxDataViewCellMode mode)
|
||||
: wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER)
|
||||
{ }
|
||||
|
||||
virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/
|
||||
{
|
||||
dc->SetBrush(*wxLIGHT_GREY_BRUSH);
|
||||
dc->SetPen(*wxTRANSPARENT_PEN);
|
||||
|
||||
rect.Deflate(2);
|
||||
dc->DrawRoundedRectangle(rect, 5);
|
||||
|
||||
RenderText(m_value,
|
||||
0, // no offset
|
||||
wxRect(dc->GetTextExtent(m_value)).CentreIn(rect),
|
||||
dc,
|
||||
state);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ActivateCell(const wxRect& WXUNUSED(cell),
|
||||
wxDataViewModel *WXUNUSED(model),
|
||||
const wxDataViewItem &WXUNUSED(item),
|
||||
unsigned int WXUNUSED(col),
|
||||
const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/
|
||||
{
|
||||
wxString position;
|
||||
if (mouseEvent)
|
||||
position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y);
|
||||
else
|
||||
position = "from keyboard";
|
||||
// wxLogMessage("MyCustomRenderer ActivateCell() %s", position);
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual wxSize GetSize() const override/*wxOVERRIDE*/
|
||||
{
|
||||
return wxSize(60, 20);
|
||||
}
|
||||
|
||||
virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/
|
||||
{
|
||||
m_value = value.GetString();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; }
|
||||
|
||||
virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; }
|
||||
|
||||
virtual wxWindow*
|
||||
CreateEditorCtrl(wxWindow* parent,
|
||||
wxRect labelRect,
|
||||
const wxVariant& value) override/*wxOVERRIDE*/
|
||||
{
|
||||
wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value,
|
||||
labelRect.GetPosition(),
|
||||
labelRect.GetSize(),
|
||||
wxTE_PROCESS_ENTER);
|
||||
text->SetInsertionPointEnd();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/
|
||||
{
|
||||
wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl);
|
||||
if (!text)
|
||||
return false;
|
||||
|
||||
value = text->GetValue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
wxString m_value;
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PrusaDoubleSlider
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
enum SelectedSlider {
|
||||
ssUndef,
|
||||
ssLower,
|
||||
ssHigher
|
||||
};
|
||||
enum TicksAction{
|
||||
taOnIcon,
|
||||
taAdd,
|
||||
taDel
|
||||
};
|
||||
class PrusaDoubleSlider : public wxControl
|
||||
{
|
||||
public:
|
||||
PrusaDoubleSlider(
|
||||
wxWindow *parent,
|
||||
wxWindowID id,
|
||||
int lowerValue,
|
||||
int higherValue,
|
||||
int minValue,
|
||||
int maxValue,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxSL_VERTICAL,
|
||||
const wxValidator& val = wxDefaultValidator,
|
||||
const wxString& name = wxEmptyString);
|
||||
~PrusaDoubleSlider(){}
|
||||
|
||||
int GetLowerValue() const {
|
||||
return m_lower_value;
|
||||
}
|
||||
int GetHigherValue() const {
|
||||
return m_higher_value;
|
||||
}
|
||||
int GetActiveValue() const;
|
||||
double GetLowerValueD() const { return get_double_value(ssLower); }
|
||||
double GetHigherValueD() const { return get_double_value(ssHigher); }
|
||||
wxSize DoGetBestSize() const override;
|
||||
void SetLowerValue(const int lower_val);
|
||||
void SetHigherValue(const int higher_val);
|
||||
void SetMaxValue(const int max_value);
|
||||
void SetKoefForLabels(const double koef) {
|
||||
m_label_koef = koef;
|
||||
}
|
||||
void SetSliderValues(const std::vector<std::pair<int, double>>& values) {
|
||||
m_values = values;
|
||||
}
|
||||
void ChangeOneLayerLock();
|
||||
|
||||
void OnPaint(wxPaintEvent& ){ render();}
|
||||
void OnLeftDown(wxMouseEvent& event);
|
||||
void OnMotion(wxMouseEvent& event);
|
||||
void OnLeftUp(wxMouseEvent& event);
|
||||
void OnEnterWin(wxMouseEvent& event){ enter_window(event, true); }
|
||||
void OnLeaveWin(wxMouseEvent& event){ enter_window(event, false); }
|
||||
void OnWheel(wxMouseEvent& event);
|
||||
void OnKeyDown(wxKeyEvent &event);
|
||||
void OnKeyUp(wxKeyEvent &event);
|
||||
void OnRightDown(wxMouseEvent& event);
|
||||
void OnRightUp(wxMouseEvent& event);
|
||||
|
||||
protected:
|
||||
|
||||
void render();
|
||||
void draw_focus_rect();
|
||||
void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end);
|
||||
void draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
|
||||
void draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection);
|
||||
void draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos);
|
||||
void draw_ticks(wxDC& dc);
|
||||
void draw_one_layer_icon(wxDC& dc);
|
||||
void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
|
||||
void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection);
|
||||
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
|
||||
|
||||
void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection);
|
||||
void detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false);
|
||||
void correct_lower_value();
|
||||
void correct_higher_value();
|
||||
void move_current_thumb(const bool condition);
|
||||
void action_tick(const TicksAction action);
|
||||
void enter_window(wxMouseEvent& event, const bool enter);
|
||||
|
||||
bool is_point_in_rect(const wxPoint& pt, const wxRect& rect);
|
||||
bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
|
||||
|
||||
double get_scroll_step();
|
||||
wxString get_label(const SelectedSlider& selection) const;
|
||||
void get_lower_and_higher_position(int& lower_pos, int& higher_pos);
|
||||
int get_value_from_position(const wxCoord x, const wxCoord y);
|
||||
wxCoord get_position_from_value(const int value);
|
||||
wxSize get_size();
|
||||
void get_size(int *w, int *h);
|
||||
double get_double_value(const SelectedSlider& selection) const;
|
||||
|
||||
private:
|
||||
int m_min_value;
|
||||
int m_max_value;
|
||||
int m_lower_value;
|
||||
int m_higher_value;
|
||||
wxBitmap m_bmp_thumb_higher;
|
||||
wxBitmap m_bmp_thumb_lower;
|
||||
wxBitmap m_bmp_add_tick_on;
|
||||
wxBitmap m_bmp_add_tick_off;
|
||||
wxBitmap m_bmp_del_tick_on;
|
||||
wxBitmap m_bmp_del_tick_off;
|
||||
wxBitmap m_bmp_one_layer_lock_on;
|
||||
wxBitmap m_bmp_one_layer_lock_off;
|
||||
wxBitmap m_bmp_one_layer_unlock_on;
|
||||
wxBitmap m_bmp_one_layer_unlock_off;
|
||||
SelectedSlider m_selection;
|
||||
bool m_is_left_down = false;
|
||||
bool m_is_right_down = false;
|
||||
bool m_is_one_layer = false;
|
||||
bool m_is_focused = false;
|
||||
bool m_is_action_icon_focesed = false;
|
||||
bool m_is_one_layer_icon_focesed = false;
|
||||
|
||||
wxRect m_rect_lower_thumb;
|
||||
wxRect m_rect_higher_thumb;
|
||||
wxRect m_rect_tick_action;
|
||||
wxRect m_rect_one_layer_icon;
|
||||
wxSize m_thumb_size;
|
||||
int m_tick_icon_dim;
|
||||
int m_lock_icon_dim;
|
||||
long m_style;
|
||||
float m_label_koef = 1.0;
|
||||
|
||||
// control's view variables
|
||||
wxCoord SLIDER_MARGIN; // margin around slider
|
||||
|
||||
wxPen DARK_ORANGE_PEN;
|
||||
wxPen ORANGE_PEN;
|
||||
wxPen LIGHT_ORANGE_PEN;
|
||||
|
||||
wxPen DARK_GREY_PEN;
|
||||
wxPen GREY_PEN;
|
||||
wxPen LIGHT_GREY_PEN;
|
||||
|
||||
std::vector<wxPen*> line_pens;
|
||||
std::vector<wxPen*> segm_pens;
|
||||
std::set<int> m_ticks;
|
||||
std::vector<std::pair<int,double>> m_values;
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PrusaLockButton
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class PrusaLockButton : public wxButton
|
||||
{
|
||||
public:
|
||||
PrusaLockButton(
|
||||
wxWindow *parent,
|
||||
wxWindowID id,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize);
|
||||
~PrusaLockButton(){}
|
||||
|
||||
void OnButton(wxCommandEvent& event);
|
||||
void OnEnterBtn(wxMouseEvent& event){ enter_button(true); event.Skip(); }
|
||||
void OnLeaveBtn(wxMouseEvent& event){ enter_button(false); event.Skip(); }
|
||||
|
||||
bool IsLocked() const { return m_is_pushed; }
|
||||
|
||||
protected:
|
||||
void enter_button(const bool enter);
|
||||
|
||||
private:
|
||||
bool m_is_pushed = false;
|
||||
|
||||
wxBitmap m_bmp_lock_on;
|
||||
wxBitmap m_bmp_lock_off;
|
||||
wxBitmap m_bmp_unlock_on;
|
||||
wxBitmap m_bmp_unlock_off;
|
||||
|
||||
int m_lock_icon_dim;
|
||||
};
|
||||
|
||||
|
||||
// ******************************* EXPERIMENTS **********************************************
|
||||
// ******************************************************************************************
|
||||
|
||||
|
||||
#endif // slic3r_GUI_wxExtensions_hpp_
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue