mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-22 00:01:09 -06:00
merge with master
This commit is contained in:
commit
50a45949d1
177 changed files with 9348 additions and 3124 deletions
|
@ -101,18 +101,16 @@ namespace nowide {
|
|||
{
|
||||
char const *key = string;
|
||||
char const *key_end = string;
|
||||
while(*key_end!='=' && key_end!='\0')
|
||||
key_end++;
|
||||
if(*key_end == '\0')
|
||||
while(*key_end != '=' && *key_end != 0)
|
||||
++ key_end;
|
||||
if(*key_end == 0)
|
||||
return -1;
|
||||
wshort_stackstring wkey;
|
||||
if(!wkey.convert(key,key_end))
|
||||
return -1;
|
||||
|
||||
wstackstring wvalue;
|
||||
if(!wvalue.convert(key_end+1))
|
||||
return -1;
|
||||
|
||||
if(SetEnvironmentVariableW(wkey.c_str(),wvalue.c_str()))
|
||||
return 0;
|
||||
return -1;
|
||||
|
|
|
@ -7,7 +7,8 @@ namespace Slic3r {
|
|||
template <class PointClass>
|
||||
BoundingBoxBase<PointClass>::BoundingBoxBase(const std::vector<PointClass> &points)
|
||||
{
|
||||
if (points.empty()) CONFESS("Empty point set supplied to BoundingBoxBase constructor");
|
||||
if (points.empty())
|
||||
CONFESS("Empty point set supplied to BoundingBoxBase constructor");
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min.x = this->max.x = it->x;
|
||||
this->min.y = this->max.y = it->y;
|
||||
|
@ -26,7 +27,8 @@ template <class PointClass>
|
|||
BoundingBox3Base<PointClass>::BoundingBox3Base(const std::vector<PointClass> &points)
|
||||
: BoundingBoxBase<PointClass>(points)
|
||||
{
|
||||
if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor");
|
||||
if (points.empty())
|
||||
CONFESS("Empty point set supplied to BoundingBox3Base constructor");
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min.z = this->max.z = it->z;
|
||||
for (++it; it != points.end(); ++it) {
|
||||
|
@ -39,9 +41,10 @@ template BoundingBox3Base<Pointf3>::BoundingBox3Base(const std::vector<Pointf3>
|
|||
BoundingBox::BoundingBox(const Lines &lines)
|
||||
{
|
||||
Points points;
|
||||
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
|
||||
points.push_back(line->a);
|
||||
points.push_back(line->b);
|
||||
points.reserve(lines.size());
|
||||
for (const Line &line : lines) {
|
||||
points.emplace_back(line.a);
|
||||
points.emplace_back(line.b);
|
||||
}
|
||||
*this = BoundingBox(points);
|
||||
}
|
||||
|
@ -190,9 +193,9 @@ BoundingBox3Base<PointClass>::size() const
|
|||
}
|
||||
template Pointf3 BoundingBox3Base<Pointf3>::size() const;
|
||||
|
||||
template <class PointClass> double
|
||||
BoundingBoxBase<PointClass>::radius() const
|
||||
template <class PointClass> double BoundingBoxBase<PointClass>::radius() const
|
||||
{
|
||||
assert(this->defined);
|
||||
double x = this->max.x - this->min.x;
|
||||
double y = this->max.y - this->min.y;
|
||||
return 0.5 * sqrt(x*x+y*y);
|
||||
|
@ -200,8 +203,7 @@ BoundingBoxBase<PointClass>::radius() const
|
|||
template double BoundingBoxBase<Point>::radius() const;
|
||||
template double BoundingBoxBase<Pointf>::radius() const;
|
||||
|
||||
template <class PointClass> double
|
||||
BoundingBox3Base<PointClass>::radius() const
|
||||
template <class PointClass> double BoundingBox3Base<PointClass>::radius() const
|
||||
{
|
||||
double x = this->max.x - this->min.x;
|
||||
double y = this->max.y - this->min.y;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "Config.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include <assert.h>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <exception> // std::runtime_error
|
||||
|
@ -16,7 +16,6 @@
|
|||
#include <boost/nowide/cenv.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(_WIN32) && !defined(setenv) && defined(_putenv_s)
|
||||
|
@ -235,13 +234,16 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
|
|||
{
|
||||
t_config_option_key opt_key = opt_key_src;
|
||||
// Try to deserialize the option by its name.
|
||||
const ConfigOptionDef* optdef = this->def()->get(opt_key);
|
||||
const ConfigDef *def = this->def();
|
||||
if (def == nullptr)
|
||||
throw NoDefinitionException();
|
||||
const ConfigOptionDef *optdef = def->get(opt_key);
|
||||
if (optdef == nullptr) {
|
||||
// If we didn't find an option, look for any other option having this as an alias.
|
||||
for (const auto &opt : this->def()->options) {
|
||||
for (const auto &opt : def->options) {
|
||||
for (const t_config_option_key &opt_key2 : opt.second.aliases) {
|
||||
if (opt_key2 == opt_key) {
|
||||
opt_key = opt_key2;
|
||||
opt_key = opt.first;
|
||||
optdef = &opt.second;
|
||||
break;
|
||||
}
|
||||
|
@ -278,10 +280,16 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
|
|||
return static_cast<const ConfigOptionFloat*>(raw_opt)->value;
|
||||
if (raw_opt->type() == coFloatOrPercent) {
|
||||
// Get option definition.
|
||||
const ConfigOptionDef *def = this->def()->get(opt_key);
|
||||
assert(def != nullptr);
|
||||
const ConfigDef *def = this->def();
|
||||
if (def == nullptr)
|
||||
throw NoDefinitionException();
|
||||
const ConfigOptionDef *opt_def = def->get(opt_key);
|
||||
assert(opt_def != nullptr);
|
||||
// Compute absolute value over the absolute value of the base option.
|
||||
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(def->ratio_over));
|
||||
//FIXME there are some ratio_over chains, which end with empty ratio_with.
|
||||
// For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly.
|
||||
return opt_def->ratio_over.empty() ? 0. :
|
||||
static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
|
||||
}
|
||||
throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
|
||||
}
|
||||
|
@ -321,11 +329,23 @@ void ConfigBase::setenv_()
|
|||
|
||||
void ConfigBase::load(const std::string &file)
|
||||
{
|
||||
namespace pt = boost::property_tree;
|
||||
pt::ptree tree;
|
||||
if (boost::iends_with(file, ".gcode") || boost::iends_with(file, ".g"))
|
||||
this->load_from_gcode(file);
|
||||
else
|
||||
this->load_from_ini(file);
|
||||
}
|
||||
|
||||
void ConfigBase::load_from_ini(const std::string &file)
|
||||
{
|
||||
boost::property_tree::ptree tree;
|
||||
boost::nowide::ifstream ifs(file);
|
||||
pt::read_ini(ifs, tree);
|
||||
for (const pt::ptree::value_type &v : tree) {
|
||||
boost::property_tree::read_ini(ifs, tree);
|
||||
this->load(tree);
|
||||
}
|
||||
|
||||
void ConfigBase::load(const boost::property_tree::ptree &tree)
|
||||
{
|
||||
for (const boost::property_tree::ptree::value_type &v : tree) {
|
||||
try {
|
||||
t_config_option_key opt_key = v.first;
|
||||
this->set_deserialize(opt_key, v.second.get_value<std::string>());
|
||||
|
@ -414,18 +434,24 @@ void ConfigBase::save(const std::string &file) const
|
|||
{
|
||||
boost::nowide::ofstream c;
|
||||
c.open(file, std::ios::out | std::ios::trunc);
|
||||
{
|
||||
std::time_t now;
|
||||
time(&now);
|
||||
char buf[sizeof "0000-00-00 00:00:00"];
|
||||
strftime(buf, sizeof(buf), "%F %T", gmtime(&now));
|
||||
c << "# generated by Slic3r " << SLIC3R_VERSION << " on " << buf << std::endl;
|
||||
}
|
||||
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
for (const std::string &opt_key : this->keys())
|
||||
c << opt_key << " = " << this->serialize(opt_key) << std::endl;
|
||||
c.close();
|
||||
}
|
||||
|
||||
bool DynamicConfig::operator==(const DynamicConfig &rhs) const
|
||||
{
|
||||
t_options_map::const_iterator it1 = this->options.begin();
|
||||
t_options_map::const_iterator it1_end = this->options.end();
|
||||
t_options_map::const_iterator it2 = rhs.options.begin();
|
||||
t_options_map::const_iterator it2_end = rhs.options.end();
|
||||
for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2)
|
||||
if (*it1->second != *it2->second)
|
||||
return false;
|
||||
return it1 == it1_end && it2 == it2_end;
|
||||
}
|
||||
|
||||
ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create)
|
||||
{
|
||||
t_options_map::iterator it = options.find(opt_key);
|
||||
|
@ -436,7 +462,10 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
|
|||
// Option was not found and a new option shall not be created.
|
||||
return nullptr;
|
||||
// Try to create a new ConfigOption.
|
||||
const ConfigOptionDef *optdef = this->def()->get(opt_key);
|
||||
const ConfigDef *def = this->def();
|
||||
if (def == nullptr)
|
||||
throw NoDefinitionException();
|
||||
const ConfigOptionDef *optdef = def->get(opt_key);
|
||||
if (optdef == nullptr)
|
||||
// throw std::runtime_error(std::string("Invalid option name: ") + opt_key);
|
||||
// Let the parent decide what to do if the opt_key is not defined by this->def().
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "libslic3r.h"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Name of the configuration option.
|
||||
|
@ -27,41 +29,42 @@ extern bool unescape_strings_cstyle(const std::string &str, std::vector<
|
|||
|
||||
// Type of a configuration value.
|
||||
enum ConfigOptionType {
|
||||
coNone,
|
||||
coVectorType = 0x4000,
|
||||
coNone = 0,
|
||||
// single float
|
||||
coFloat,
|
||||
coFloat = 1,
|
||||
// vector of floats
|
||||
coFloats,
|
||||
coFloats = coFloat + coVectorType,
|
||||
// single int
|
||||
coInt,
|
||||
coInt = 2,
|
||||
// vector of ints
|
||||
coInts,
|
||||
coInts = coInt + coVectorType,
|
||||
// single string
|
||||
coString,
|
||||
coString = 3,
|
||||
// vector of strings
|
||||
coStrings,
|
||||
coStrings = coString + coVectorType,
|
||||
// percent value. Currently only used for infill.
|
||||
coPercent,
|
||||
coPercent = 4,
|
||||
// percents value. Currently used for retract before wipe only.
|
||||
coPercents,
|
||||
coPercents = coPercent + coVectorType,
|
||||
// a fraction or an absolute value
|
||||
coFloatOrPercent,
|
||||
coFloatOrPercent = 5,
|
||||
// single 2d point. Currently not used.
|
||||
coPoint,
|
||||
coPoint = 6,
|
||||
// vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets.
|
||||
coPoints,
|
||||
coPoints = coPoint + coVectorType,
|
||||
// single boolean value
|
||||
coBool,
|
||||
coBool = 7,
|
||||
// vector of boolean values
|
||||
coBools,
|
||||
coBools = coBool + coVectorType,
|
||||
// a generic enum
|
||||
coEnum,
|
||||
coEnum = 8,
|
||||
};
|
||||
|
||||
// A generic value of a configuration option.
|
||||
class ConfigOption {
|
||||
public:
|
||||
virtual ~ConfigOption() {};
|
||||
virtual ~ConfigOption() {}
|
||||
|
||||
virtual ConfigOptionType type() const = 0;
|
||||
virtual std::string serialize() const = 0;
|
||||
|
@ -75,8 +78,13 @@ public:
|
|||
virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); }
|
||||
virtual bool operator==(const ConfigOption &rhs) const = 0;
|
||||
bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); }
|
||||
bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; }
|
||||
bool is_vector() const { return ! this->is_scalar(); }
|
||||
};
|
||||
|
||||
typedef ConfigOption* ConfigOptionPtr;
|
||||
typedef const ConfigOption* ConfigOptionConstPtr;
|
||||
|
||||
// Value of a single valued option (bool, int, float, string, point, enum)
|
||||
template <class T>
|
||||
class ConfigOptionSingle : public ConfigOption {
|
||||
|
@ -91,7 +99,7 @@ public:
|
|||
throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
|
||||
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
|
||||
};
|
||||
}
|
||||
|
||||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
|
@ -110,6 +118,25 @@ class ConfigOptionVectorBase : public ConfigOption {
|
|||
public:
|
||||
// Currently used only to initialize the PlaceholderParser.
|
||||
virtual std::vector<std::string> vserialize() const = 0;
|
||||
// Set from a vector of ConfigOptions.
|
||||
// If the rhs ConfigOption is scalar, then its value is used,
|
||||
// otherwise for each of rhs, the first value of a vector is used.
|
||||
// This function is useful to collect values for multiple extrder / filament settings.
|
||||
virtual void set(const std::vector<const ConfigOption*> &rhs) = 0;
|
||||
// Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions.
|
||||
// This function is useful to split values from multiple extrder / filament settings into separate configurations.
|
||||
virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0;
|
||||
|
||||
virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0;
|
||||
|
||||
// Get size of this vector.
|
||||
virtual size_t size() const = 0;
|
||||
// Is this vector empty?
|
||||
virtual bool empty() const = 0;
|
||||
|
||||
protected:
|
||||
// Used to verify type compatibility when assigning to / from a scalar ConfigOption.
|
||||
ConfigOptionType scalar_type() const { return static_cast<ConfigOptionType>(this->type() - coVectorType); }
|
||||
};
|
||||
|
||||
// Value of a vector valued option (bools, ints, floats, strings, points), template
|
||||
|
@ -117,6 +144,11 @@ template <class T>
|
|||
class ConfigOptionVector : public ConfigOptionVectorBase
|
||||
{
|
||||
public:
|
||||
ConfigOptionVector() {}
|
||||
explicit ConfigOptionVector(size_t n, const T &value) : values(n, value) {}
|
||||
explicit ConfigOptionVector(std::initializer_list<T> il) : values(std::move(il)) {}
|
||||
explicit ConfigOptionVector(const std::vector<T> &values) : values(values) {}
|
||||
explicit ConfigOptionVector(std::vector<T> &&values) : values(std::move(values)) {}
|
||||
std::vector<T> values;
|
||||
|
||||
void set(const ConfigOption *rhs) override
|
||||
|
@ -125,16 +157,88 @@ public:
|
|||
throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
|
||||
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
|
||||
};
|
||||
}
|
||||
|
||||
// Set from a vector of ConfigOptions.
|
||||
// If the rhs ConfigOption is scalar, then its value is used,
|
||||
// otherwise for each of rhs, the first value of a vector is used.
|
||||
// This function is useful to collect values for multiple extrder / filament settings.
|
||||
void set(const std::vector<const ConfigOption*> &rhs) override
|
||||
{
|
||||
this->values.clear();
|
||||
this->values.reserve(rhs.size());
|
||||
for (const ConfigOption *opt : rhs) {
|
||||
if (opt->type() == this->type()) {
|
||||
auto other = static_cast<const ConfigOptionVector<T>*>(opt);
|
||||
if (other->values.empty())
|
||||
throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector");
|
||||
this->values.emplace_back(other->values.front());
|
||||
} else if (opt->type() == this->scalar_type())
|
||||
this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
|
||||
else
|
||||
throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
// Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions.
|
||||
// This function is useful to split values from multiple extrder / filament settings into separate configurations.
|
||||
void set_at(const ConfigOption *rhs, size_t i, size_t j) override
|
||||
{
|
||||
// It is expected that the vector value has at least one value, which is the default, if not overwritten.
|
||||
assert(! this->values.empty());
|
||||
if (this->values.size() <= i) {
|
||||
// Resize this vector, fill in the new vector fields with the copy of the first field.
|
||||
T v = this->values.front();
|
||||
this->values.resize(i + 1, v);
|
||||
}
|
||||
if (rhs->type() == this->type()) {
|
||||
// Assign the first value of the rhs vector.
|
||||
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
|
||||
if (other->values.empty())
|
||||
throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector");
|
||||
this->values[i] = other->get_at(j);
|
||||
} else if (rhs->type() == this->scalar_type())
|
||||
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
|
||||
else
|
||||
throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type");
|
||||
}
|
||||
|
||||
T& get_at(size_t i)
|
||||
{
|
||||
assert(! this->values.empty());
|
||||
return (i < this->values.size()) ? this->values[i] : this->values.front();
|
||||
};
|
||||
}
|
||||
|
||||
const T& get_at(size_t i) const { return const_cast<ConfigOptionVector<T>*>(this)->get_at(i); }
|
||||
|
||||
// Resize this vector by duplicating the last value.
|
||||
// If the current vector is empty, the default value is used instead.
|
||||
void resize(size_t n, const ConfigOption *opt_default = nullptr) override
|
||||
{
|
||||
assert(opt_default == nullptr || opt_default->is_vector());
|
||||
// assert(opt_default == nullptr || dynamic_cast<ConfigOptionVector<T>>(opt_default));
|
||||
assert(! this->values.empty() || opt_default != nullptr);
|
||||
if (n == 0)
|
||||
this->values.clear();
|
||||
else if (n < this->values.size())
|
||||
this->values.erase(this->values.begin() + n, this->values.end());
|
||||
else if (n > this->values.size()) {
|
||||
if (this->values.empty()) {
|
||||
if (opt_default == nullptr)
|
||||
throw std::runtime_error("ConfigOptionVector::resize(): No default value provided.");
|
||||
if (opt_default->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type.");
|
||||
this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front());
|
||||
} else {
|
||||
// Resize by duplicating the last value.
|
||||
this->values.resize(n, this->values.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const override { return this->values.size(); }
|
||||
bool empty() const override { return this->values.empty(); }
|
||||
|
||||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
|
@ -150,13 +254,14 @@ public:
|
|||
class ConfigOptionFloat : public ConfigOptionSingle<double>
|
||||
{
|
||||
public:
|
||||
ConfigOptionFloat() : ConfigOptionSingle<double>(0) {};
|
||||
explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {};
|
||||
ConfigOptionFloat() : ConfigOptionSingle<double>(0) {}
|
||||
explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {}
|
||||
|
||||
ConfigOptionType type() const override { return coFloat; }
|
||||
double getFloat() const override { return this->value; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionFloat(*this); }
|
||||
bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; }
|
||||
static ConfigOptionType static_type() { return coFloat; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
double getFloat() const override { return this->value; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionFloat(*this); }
|
||||
bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -183,9 +288,14 @@ public:
|
|||
class ConfigOptionFloats : public ConfigOptionVector<double>
|
||||
{
|
||||
public:
|
||||
ConfigOptionType type() const override { return coFloats; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionFloats(*this); }
|
||||
bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; }
|
||||
ConfigOptionFloats() : ConfigOptionVector<double>() {}
|
||||
explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector<double>(n, value) {}
|
||||
explicit ConfigOptionFloats(std::initializer_list<double> il) : ConfigOptionVector<double>(std::move(il)) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coFloats; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionFloats(*this); }
|
||||
bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -195,7 +305,7 @@ public:
|
|||
ss << *it;
|
||||
}
|
||||
return ss.str();
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<std::string> vserialize() const override
|
||||
{
|
||||
|
@ -234,15 +344,16 @@ public:
|
|||
class ConfigOptionInt : public ConfigOptionSingle<int>
|
||||
{
|
||||
public:
|
||||
ConfigOptionInt() : ConfigOptionSingle<int>(0) {};
|
||||
explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {};
|
||||
explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {};
|
||||
ConfigOptionInt() : ConfigOptionSingle<int>(0) {}
|
||||
explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {}
|
||||
explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {}
|
||||
|
||||
ConfigOptionType type() const override { return coInt; }
|
||||
int getInt() const override { return this->value; };
|
||||
void setInt(int val) { this->value = val; };
|
||||
ConfigOption* clone() const override { return new ConfigOptionInt(*this); }
|
||||
bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; }
|
||||
static ConfigOptionType static_type() { return coInt; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
int getInt() const override { return this->value; }
|
||||
void setInt(int val) { this->value = val; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionInt(*this); }
|
||||
bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -269,10 +380,15 @@ public:
|
|||
class ConfigOptionInts : public ConfigOptionVector<int>
|
||||
{
|
||||
public:
|
||||
ConfigOptionType type() const override { return coInts; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionInts(*this); }
|
||||
ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; }
|
||||
ConfigOptionInts() : ConfigOptionVector<int>() {}
|
||||
explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector<int>(n, value) {}
|
||||
explicit ConfigOptionInts(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coInts; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionInts(*this); }
|
||||
ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; }
|
||||
|
||||
std::string serialize() const override {
|
||||
std::ostringstream ss;
|
||||
|
@ -314,13 +430,14 @@ public:
|
|||
class ConfigOptionString : public ConfigOptionSingle<std::string>
|
||||
{
|
||||
public:
|
||||
ConfigOptionString() : ConfigOptionSingle<std::string>("") {};
|
||||
explicit ConfigOptionString(std::string _value) : ConfigOptionSingle<std::string>(_value) {};
|
||||
ConfigOptionString() : ConfigOptionSingle<std::string>("") {}
|
||||
explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {}
|
||||
|
||||
ConfigOptionType type() const override { return coString; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionString(*this); }
|
||||
ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; }
|
||||
static ConfigOptionType static_type() { return coString; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionString(*this); }
|
||||
ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -338,10 +455,17 @@ public:
|
|||
class ConfigOptionStrings : public ConfigOptionVector<std::string>
|
||||
{
|
||||
public:
|
||||
ConfigOptionType type() const override { return coStrings; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionStrings(*this); }
|
||||
ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; }
|
||||
ConfigOptionStrings() : ConfigOptionVector<std::string>() {}
|
||||
explicit ConfigOptionStrings(size_t n, const std::string &value) : ConfigOptionVector<std::string>(n, value) {}
|
||||
explicit ConfigOptionStrings(const std::vector<std::string> &values) : ConfigOptionVector<std::string>(values) {}
|
||||
explicit ConfigOptionStrings(std::vector<std::string> &&values) : ConfigOptionVector<std::string>(std::move(values)) {}
|
||||
explicit ConfigOptionStrings(std::initializer_list<std::string> il) : ConfigOptionVector<std::string>(std::move(il)) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coStrings; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionStrings(*this); }
|
||||
ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -364,14 +488,15 @@ public:
|
|||
class ConfigOptionPercent : public ConfigOptionFloat
|
||||
{
|
||||
public:
|
||||
ConfigOptionPercent() : ConfigOptionFloat(0) {};
|
||||
explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {};
|
||||
ConfigOptionPercent() : ConfigOptionFloat(0) {}
|
||||
explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {}
|
||||
|
||||
ConfigOptionType type() const override { return coPercent; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionPercent(*this); }
|
||||
ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; }
|
||||
double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; }
|
||||
static ConfigOptionType static_type() { return coPercent; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionPercent(*this); }
|
||||
ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; }
|
||||
double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -395,10 +520,15 @@ public:
|
|||
class ConfigOptionPercents : public ConfigOptionFloats
|
||||
{
|
||||
public:
|
||||
ConfigOptionType type() const override { return coPercents; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionPercents(*this); }
|
||||
ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; }
|
||||
ConfigOptionPercents() : ConfigOptionFloats() {}
|
||||
explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {}
|
||||
explicit ConfigOptionPercents(std::initializer_list<double> il) : ConfigOptionFloats(std::move(il)) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coPercents; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionPercents(*this); }
|
||||
ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -445,10 +575,11 @@ class ConfigOptionFloatOrPercent : public ConfigOptionPercent
|
|||
{
|
||||
public:
|
||||
bool percent;
|
||||
ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {};
|
||||
explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {};
|
||||
|
||||
ConfigOptionType type() const override { return coFloatOrPercent; }
|
||||
ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {}
|
||||
explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coFloatOrPercent; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionFloatOrPercent(*this); }
|
||||
ConfigOptionFloatOrPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionFloatOrPercent &rhs) const
|
||||
|
@ -485,13 +616,14 @@ public:
|
|||
class ConfigOptionPoint : public ConfigOptionSingle<Pointf>
|
||||
{
|
||||
public:
|
||||
ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {};
|
||||
explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(value) {};
|
||||
ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {}
|
||||
explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(value) {}
|
||||
|
||||
ConfigOptionType type() const override { return coPoint; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionPoint(*this); }
|
||||
ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; }
|
||||
static ConfigOptionType static_type() { return coPoint; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionPoint(*this); }
|
||||
ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -517,10 +649,15 @@ public:
|
|||
class ConfigOptionPoints : public ConfigOptionVector<Pointf>
|
||||
{
|
||||
public:
|
||||
ConfigOptionType type() const override { return coPoints; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionPoints(*this); }
|
||||
ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; }
|
||||
ConfigOptionPoints() : ConfigOptionVector<Pointf>() {}
|
||||
explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector<Pointf>(n, value) {}
|
||||
explicit ConfigOptionPoints(std::initializer_list<Pointf> il) : ConfigOptionVector<Pointf>(std::move(il)) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coPoints; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionPoints(*this); }
|
||||
ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -570,14 +707,15 @@ public:
|
|||
class ConfigOptionBool : public ConfigOptionSingle<bool>
|
||||
{
|
||||
public:
|
||||
ConfigOptionBool() : ConfigOptionSingle<bool>(false) {};
|
||||
explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_value) {};
|
||||
ConfigOptionBool() : ConfigOptionSingle<bool>(false) {}
|
||||
explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_value) {}
|
||||
|
||||
ConfigOptionType type() const override { return coBool; }
|
||||
bool getBool() const override { return this->value; };
|
||||
ConfigOption* clone() const override { return new ConfigOptionBool(*this); }
|
||||
ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; }
|
||||
static ConfigOptionType static_type() { return coBool; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
bool getBool() const override { return this->value; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionBool(*this); }
|
||||
ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -595,10 +733,15 @@ public:
|
|||
class ConfigOptionBools : public ConfigOptionVector<unsigned char>
|
||||
{
|
||||
public:
|
||||
ConfigOptionType type() const override { return coBools; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionBools(*this); }
|
||||
ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; }
|
||||
ConfigOptionBools() : ConfigOptionVector<unsigned char>() {}
|
||||
explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector<unsigned char>(n, (unsigned char)value) {}
|
||||
explicit ConfigOptionBools(std::initializer_list<bool> il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); }
|
||||
|
||||
static ConfigOptionType static_type() { return coBools; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionBools(*this); }
|
||||
ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; }
|
||||
|
||||
bool& get_at(size_t i) {
|
||||
assert(! this->values.empty());
|
||||
|
@ -652,13 +795,14 @@ class ConfigOptionEnum : public ConfigOptionSingle<T>
|
|||
{
|
||||
public:
|
||||
// by default, use the first value (0) of the T enum type
|
||||
ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {};
|
||||
explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {};
|
||||
ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {}
|
||||
explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {}
|
||||
|
||||
ConfigOptionType type() const override { return coEnum; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); }
|
||||
ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; }
|
||||
static ConfigOptionType static_type() { return coEnum; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); }
|
||||
ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
|
@ -717,8 +861,9 @@ public:
|
|||
|
||||
const t_config_enum_values* keys_map;
|
||||
|
||||
ConfigOptionType type() const override { return coEnum; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); }
|
||||
static ConfigOptionType static_type() { return coEnum; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); }
|
||||
ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
|
||||
bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; }
|
||||
|
||||
|
@ -874,6 +1019,16 @@ public:
|
|||
{ return const_cast<ConfigBase*>(this)->option(opt_key, false); }
|
||||
ConfigOption* option(const t_config_option_key &opt_key, bool create = false)
|
||||
{ return this->optptr(opt_key, create); }
|
||||
template<typename TYPE>
|
||||
TYPE* option(const t_config_option_key &opt_key, bool create = false)
|
||||
{
|
||||
ConfigOption *opt = this->optptr(opt_key, create);
|
||||
assert(opt == nullptr || opt->type() == TYPE::static_type());
|
||||
return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt);
|
||||
}
|
||||
template<typename TYPE>
|
||||
const TYPE* option(const t_config_option_key &opt_key) const
|
||||
{ return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, false); }
|
||||
// Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
|
||||
// An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
|
||||
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
|
||||
|
@ -893,7 +1048,9 @@ public:
|
|||
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
|
||||
void setenv_();
|
||||
void load(const std::string &file);
|
||||
void load_from_ini(const std::string &file);
|
||||
void load_from_gcode(const std::string &file);
|
||||
void load(const boost::property_tree::ptree &tree);
|
||||
void save(const std::string &file) const;
|
||||
|
||||
private:
|
||||
|
@ -906,26 +1063,77 @@ private:
|
|||
class DynamicConfig : public virtual ConfigBase
|
||||
{
|
||||
public:
|
||||
DynamicConfig() {}
|
||||
DynamicConfig(const DynamicConfig& other) { *this = other; }
|
||||
DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); }
|
||||
virtual ~DynamicConfig() { clear(); }
|
||||
|
||||
DynamicConfig& operator=(const DynamicConfig &other)
|
||||
// Copy a content of one DynamicConfig to another DynamicConfig.
|
||||
// If rhs.def() is not null, then it has to be equal to this->def().
|
||||
DynamicConfig& operator=(const DynamicConfig &rhs)
|
||||
{
|
||||
assert(this->def() == nullptr || this->def() == rhs.def());
|
||||
this->clear();
|
||||
for (const auto &kvp : other.options)
|
||||
for (const auto &kvp : rhs.options)
|
||||
this->options[kvp.first] = kvp.second->clone();
|
||||
return *this;
|
||||
}
|
||||
|
||||
DynamicConfig& operator=(DynamicConfig &&other)
|
||||
// Move a content of one DynamicConfig to another DynamicConfig.
|
||||
// If rhs.def() is not null, then it has to be equal to this->def().
|
||||
DynamicConfig& operator=(DynamicConfig &&rhs)
|
||||
{
|
||||
assert(this->def() == nullptr || this->def() == rhs.def());
|
||||
this->clear();
|
||||
this->options = std::move(other.options);
|
||||
other.options.clear();
|
||||
this->options = std::move(rhs.options);
|
||||
rhs.options.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Add a content of one DynamicConfig to another DynamicConfig.
|
||||
// If rhs.def() is not null, then it has to be equal to this->def().
|
||||
DynamicConfig& operator+=(const DynamicConfig &rhs)
|
||||
{
|
||||
assert(this->def() == nullptr || this->def() == rhs.def());
|
||||
for (const auto &kvp : rhs.options) {
|
||||
auto it = this->options.find(kvp.first);
|
||||
if (it == this->options.end())
|
||||
this->options[kvp.first] = kvp.second->clone();
|
||||
else {
|
||||
assert(it->second->type() == kvp.second->type());
|
||||
if (it->second->type() == kvp.second->type())
|
||||
*it->second = *kvp.second;
|
||||
else {
|
||||
delete it->second;
|
||||
it->second = kvp.second->clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move a content of one DynamicConfig to another DynamicConfig.
|
||||
// If rhs.def() is not null, then it has to be equal to this->def().
|
||||
DynamicConfig& operator+=(DynamicConfig &&rhs)
|
||||
{
|
||||
assert(this->def() == nullptr || this->def() == rhs.def());
|
||||
for (const auto &kvp : rhs.options) {
|
||||
auto it = this->options.find(kvp.first);
|
||||
if (it == this->options.end()) {
|
||||
this->options[kvp.first] = kvp.second;
|
||||
} else {
|
||||
assert(it->second->type() == kvp.second->type());
|
||||
delete it->second;
|
||||
it->second = kvp.second;
|
||||
}
|
||||
}
|
||||
rhs.options.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const DynamicConfig &rhs) const;
|
||||
bool operator!=(const DynamicConfig &rhs) const { return ! (*this == rhs); }
|
||||
|
||||
void swap(DynamicConfig &other)
|
||||
{
|
||||
std::swap(this->options, other.options);
|
||||
|
@ -948,30 +1156,49 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
|
||||
// Allow DynamicConfig to be instantiated on ints own without a definition.
|
||||
// If the definition is not defined, the method requiring the definition will throw NoDefinitionException.
|
||||
const ConfigDef* def() const override { return nullptr; };
|
||||
template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
|
||||
{ return dynamic_cast<T*>(this->option(opt_key, create)); }
|
||||
// Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name.
|
||||
ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override;
|
||||
// Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store.
|
||||
t_config_option_keys keys() const override;
|
||||
|
||||
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast<ConfigOptionString*>(this->option(opt_key, create))->value; }
|
||||
// Set a value for an opt_key. Returns true if the value did not exist yet.
|
||||
// This DynamicConfig will take ownership of opt.
|
||||
// Be careful, as this method does not test the existence of opt_key in this->def().
|
||||
bool set_key_value(const std::string &opt_key, ConfigOption *opt)
|
||||
{
|
||||
auto it = this->options.find(opt_key);
|
||||
if (it == this->options.end()) {
|
||||
this->options[opt_key] = opt;
|
||||
return true;
|
||||
} else {
|
||||
delete it->second;
|
||||
it->second = opt;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
|
||||
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionStrings*>(this->option(opt_key))->get_at(idx); }
|
||||
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); }
|
||||
|
||||
double& opt_float(const t_config_option_key &opt_key) { return dynamic_cast<ConfigOptionFloat*>(this->option(opt_key))->value; }
|
||||
double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
|
||||
const double opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
|
||||
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
|
||||
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
|
||||
const double opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
int& opt_int(const t_config_option_key &opt_key) { return dynamic_cast<ConfigOptionInt*>(this->option(opt_key))->value; }
|
||||
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
|
||||
const int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
|
||||
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
|
||||
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
|
||||
const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
protected:
|
||||
DynamicConfig() {}
|
||||
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
|
||||
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
|
||||
|
||||
private:
|
||||
typedef std::map<t_config_option_key,ConfigOption*> t_options_map;
|
||||
|
@ -1001,6 +1228,13 @@ public:
|
|||
const char* what() const noexcept override { return "Unknown config option"; }
|
||||
};
|
||||
|
||||
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
|
||||
class NoDefinitionException : public std::exception
|
||||
{
|
||||
public:
|
||||
const char* what() const noexcept override { return "No config definition"; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -149,7 +149,6 @@ inline Polylines to_polylines(const ExPolygons &src)
|
|||
return polylines;
|
||||
}
|
||||
|
||||
#if SLIC3R_CPPVER >= 11
|
||||
inline Polylines to_polylines(ExPolygon &&src)
|
||||
{
|
||||
Polylines polylines;
|
||||
|
@ -166,6 +165,7 @@ inline Polylines to_polylines(ExPolygon &&src)
|
|||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(ExPolygons &&src)
|
||||
{
|
||||
Polylines polylines;
|
||||
|
@ -184,7 +184,6 @@ inline Polylines to_polylines(ExPolygons &&src)
|
|||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline Polygons to_polygons(const ExPolygon &src)
|
||||
{
|
||||
|
|
|
@ -149,8 +149,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
// );
|
||||
}
|
||||
|
||||
for (Surfaces::const_iterator surface_it = surfaces.begin(); surface_it != surfaces.end(); ++ surface_it) {
|
||||
const Surface &surface = *surface_it;
|
||||
for (const Surface &surface : surfaces) {
|
||||
if (surface.surface_type == stInternalVoid)
|
||||
continue;
|
||||
InfillPattern fill_pattern = layerm.region()->config.fill_pattern.value;
|
||||
|
@ -262,10 +261,10 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
// Unpacks the collection, creates multiple collections per path.
|
||||
// The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection.
|
||||
// Why the paths are unpacked?
|
||||
for (ExtrusionEntitiesPtr::iterator thin_fill = layerm.thin_fills.entities.begin(); thin_fill != layerm.thin_fills.entities.end(); ++ thin_fill) {
|
||||
for (const ExtrusionEntity *thin_fill : layerm.thin_fills.entities) {
|
||||
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
|
||||
out.entities.push_back(&collection);
|
||||
collection.entities.push_back((*thin_fill)->clone());
|
||||
collection.entities.push_back(thin_fill->clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -176,11 +176,7 @@ void Fill3DHoneycomb::_fill_surface_single(
|
|||
}
|
||||
}
|
||||
Polylines chained = PolylineCollection::chained_path_from(
|
||||
#if SLIC3R_CPPVER >= 11
|
||||
std::move(polylines),
|
||||
#else
|
||||
polylines,
|
||||
#endif
|
||||
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
|
||||
bool first = true;
|
||||
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
|
||||
|
@ -199,12 +195,7 @@ void Fill3DHoneycomb::_fill_surface_single(
|
|||
}
|
||||
}
|
||||
// The lines cannot be connected.
|
||||
#if SLIC3R_CPPVER >= 11
|
||||
polylines_out.push_back(std::move(*it_polyline));
|
||||
#else
|
||||
polylines_out.push_back(Polyline());
|
||||
std::swap(polylines_out.back(), *it_polyline);
|
||||
#endif
|
||||
polylines_out.emplace_back(std::move(*it_polyline));
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,7 @@ void FillHoneycomb::_fill_surface_single(
|
|||
CacheID cache_id(params.density, this->spacing);
|
||||
Cache::iterator it_m = this->cache.find(cache_id);
|
||||
if (it_m == this->cache.end()) {
|
||||
#if 0
|
||||
// #if SLIC3R_CPPVER > 11
|
||||
it_m = this->cache.emplace_hint(it_m);
|
||||
#else
|
||||
it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData()));
|
||||
#endif
|
||||
CacheData &m = it_m->second;
|
||||
coord_t min_spacing = scale_(this->spacing);
|
||||
m.distance = min_spacing / params.density;
|
||||
|
@ -99,11 +94,7 @@ void FillHoneycomb::_fill_surface_single(
|
|||
// connect paths
|
||||
if (! paths.empty()) { // prevent calling leftmost_point() on empty collections
|
||||
Polylines chained = PolylineCollection::chained_path_from(
|
||||
#if SLIC3R_CPPVER >= 11
|
||||
std::move(paths),
|
||||
#else
|
||||
paths,
|
||||
#endif
|
||||
PolylineCollection::leftmost_point(paths), false);
|
||||
assert(paths.empty());
|
||||
paths.clear();
|
||||
|
|
|
@ -93,11 +93,7 @@ void FillRectilinear::_fill_surface_single(
|
|||
}
|
||||
}
|
||||
Polylines chained = PolylineCollection::chained_path_from(
|
||||
#if SLIC3R_CPPVER >= 11
|
||||
std::move(polylines),
|
||||
#else
|
||||
polylines,
|
||||
#endif
|
||||
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
|
||||
bool first = true;
|
||||
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
|
||||
|
@ -118,12 +114,7 @@ void FillRectilinear::_fill_surface_single(
|
|||
}
|
||||
}
|
||||
// The lines cannot be connected.
|
||||
#if SLIC3R_CPPVER >= 11
|
||||
polylines_out.push_back(std::move(*it_polyline));
|
||||
#else
|
||||
polylines_out.push_back(Polyline());
|
||||
std::swap(polylines_out.back(), *it_polyline);
|
||||
#endif
|
||||
polylines_out.emplace_back(std::move(*it_polyline));
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,39 +8,6 @@ namespace Slic3r {
|
|||
// This static method returns a sane extrusion width default.
|
||||
static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, float height)
|
||||
{
|
||||
#if 0
|
||||
// Here we calculate a sane default by matching the flow speed (at the nozzle) and the feed rate.
|
||||
// shape: rectangle with semicircles at the ends
|
||||
// This "sane" extrusion width gives the following results for a 0.4mm dmr nozzle:
|
||||
// Layer Calculated Calculated width
|
||||
// heigh extrusion over nozzle
|
||||
// width diameter
|
||||
// 0.40 0.40 1.00
|
||||
// 0.35 0.43 1.09
|
||||
// 0.30 0.48 1.21
|
||||
// 0.25 0.56 1.39
|
||||
// 0.20 0.67 1.68
|
||||
// 0.15 0.87 2.17
|
||||
// 0.10 1.28 3.20
|
||||
// 0.05 2.52 6.31
|
||||
//
|
||||
float width = float(0.25 * (nozzle_diameter * nozzle_diameter) * PI / height + height * (1.0 - 0.25 * PI));
|
||||
|
||||
switch (role) {
|
||||
case frExternalPerimeter:
|
||||
case frSupportMaterial:
|
||||
case frSupportMaterialInterface:
|
||||
return nozzle_diameter;
|
||||
case frPerimeter:
|
||||
case frSolidInfill:
|
||||
case frTopSolidInfill:
|
||||
// do not limit width for sparse infill so that we use full native flow for it
|
||||
return std::min(std::max(width, nozzle_diameter * 1.05f), nozzle_diameter * 1.7f);
|
||||
case frInfill:
|
||||
default:
|
||||
return std::max(width, nozzle_diameter * 1.05f);
|
||||
}
|
||||
#else
|
||||
switch (role) {
|
||||
case frSupportMaterial:
|
||||
case frSupportMaterialInterface:
|
||||
|
@ -53,7 +20,6 @@ static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, f
|
|||
case frInfill:
|
||||
return 1.125f * nozzle_diameter;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// This constructor builds a Flow object from an extrusion width config setting
|
||||
|
@ -154,10 +120,11 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
|
|||
|
||||
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
|
||||
{
|
||||
const auto &width = (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width;
|
||||
return Flow::new_from_config_width(
|
||||
frSupportMaterial,
|
||||
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
|
||||
(object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width,
|
||||
(width.value > 0) ? width : object->config.extrusion_width,
|
||||
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)),
|
||||
false);
|
||||
|
|
|
@ -52,6 +52,9 @@ public:
|
|||
coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); };
|
||||
|
||||
static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio);
|
||||
// Create a flow from the spacing of extrusion lines.
|
||||
// This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale
|
||||
// to fit a region with integer number of lines.
|
||||
static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "Geometry.hpp"
|
||||
#include "GCode/PrintExtents.hpp"
|
||||
#include "GCode/WipeTowerPrusaMM.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
@ -11,7 +12,6 @@
|
|||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/find.hpp>
|
||||
#include <boost/date_time/local_time/local_time.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
|
@ -30,6 +30,13 @@
|
|||
#include <assert.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Only add a newline in case the current G-code does not end with a newline.
|
||||
static inline void check_add_eol(std::string &gcode)
|
||||
{
|
||||
if (! gcode.empty() && gcode.back() != '\n')
|
||||
gcode += '\n';
|
||||
}
|
||||
|
||||
// Plan a travel move while minimizing the number of perimeter crossings.
|
||||
// point is in unscaled coordinates, in the coordinate system of the current active object
|
||||
|
@ -157,6 +164,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
{
|
||||
std::string gcode;
|
||||
|
||||
// Disable linear advance for the wipe tower operations.
|
||||
gcode += "M900 K0\n";
|
||||
// Move over the wipe tower.
|
||||
// Retract for a tool change, using the toolchange retract value and setting the priming extra length.
|
||||
gcode += gcodegen.retract(true);
|
||||
|
@ -171,8 +180,17 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
// Inform the G-code writer about the changes done behind its back.
|
||||
gcode += tcr.gcode;
|
||||
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
|
||||
if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))
|
||||
if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) {
|
||||
gcodegen.writer().toolchange(new_extruder_id);
|
||||
// Append the filament start G-code.
|
||||
const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
|
||||
if (! start_filament_gcode.empty()) {
|
||||
// Process the start_filament_gcode for the active filament only.
|
||||
gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
|
||||
gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id);
|
||||
check_add_eol(gcode);
|
||||
}
|
||||
}
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y));
|
||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
|
||||
|
@ -199,15 +217,18 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen)
|
|||
std::string gcode;
|
||||
|
||||
if (&m_priming != nullptr && ! m_priming.extrusions.empty()) {
|
||||
// Disable linear advance for the wipe tower operations.
|
||||
gcode += "M900 K0\n";
|
||||
// Let the tool change be executed by the wipe tower class.
|
||||
// Inform the G-code writer about the changes done behind its back.
|
||||
gcode += m_priming.gcode;
|
||||
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
|
||||
gcodegen.writer().toolchange(m_priming.extrusions.back().tool);
|
||||
unsigned int current_extruder_id = m_priming.extrusions.back().tool;
|
||||
gcodegen.writer().toolchange(current_extruder_id);
|
||||
gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(Pointf(m_priming.end_pos.x, m_priming.end_pos.y));
|
||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos));
|
||||
|
||||
// Prepare a future wipe.
|
||||
gcodegen.m_wipe.path.points.clear();
|
||||
// Start the wipe at the current position.
|
||||
|
@ -220,19 +241,6 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen)
|
|||
return gcode;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::prime_single_color_print(const Print & /* print */, unsigned int initial_tool, GCode & /* gcodegen */)
|
||||
{
|
||||
std::string gcode = "\
|
||||
G1 Z0.250 F7200.000\n\
|
||||
G1 X50.0 E80.0 F1000.0\n\
|
||||
G1 X160.0 E20.0 F1000.0\n\
|
||||
G1 Z0.200 F7200.000\n\
|
||||
G1 X220.0 E13 F1000.0\n\
|
||||
G1 X240.0 E0 F1000.0\n\
|
||||
G1 E-4 F1000.0\n";
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
|
||||
{
|
||||
std::string gcode;
|
||||
|
@ -313,10 +321,15 @@ inline void write_format(FILE* file, const char* format, ...)
|
|||
write(file, buffer);
|
||||
}
|
||||
|
||||
// Write a string into a file. Add a newline, if the string does not end with a newline already.
|
||||
// Used to export a custom G-code section processed by the PlaceholderParser.
|
||||
inline void writeln(FILE *file, const std::string &what)
|
||||
{
|
||||
if (! what.empty()) {
|
||||
write_format(file, "%s\n", what.c_str());
|
||||
if (what.back() != '\n')
|
||||
write_format(file, "%s\n", what.c_str());
|
||||
else
|
||||
write(file, what);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,7 +412,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
|||
return layers_to_print;
|
||||
}
|
||||
|
||||
bool GCode::do_export(Print *print, const char *path)
|
||||
void GCode::do_export(Print *print, const char *path)
|
||||
{
|
||||
// Remove the old g-code if it exists.
|
||||
boost::nowide::remove(path);
|
||||
|
@ -409,24 +422,37 @@ bool GCode::do_export(Print *print, const char *path)
|
|||
|
||||
FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
|
||||
|
||||
bool result = this->_do_export(*print, file);
|
||||
this->m_placeholder_parser_failed_templates.clear();
|
||||
this->_do_export(*print, file);
|
||||
fflush(file);
|
||||
if (ferror(file)) {
|
||||
fclose(file);
|
||||
boost::nowide::remove(path_tmp.c_str());
|
||||
throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
if (result && boost::nowide::rename(path_tmp.c_str(), path) != 0) {
|
||||
boost::nowide::cerr << "Failed to remove the output G-code file from " << path_tmp << " to " << path
|
||||
<< ". Is " << path_tmp << " locked?" << std::endl;
|
||||
result = false;
|
||||
if (! this->m_placeholder_parser_failed_templates.empty()) {
|
||||
// G-code export proceeded, but some of the PlaceholderParser substitutions failed.
|
||||
std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n";
|
||||
for (const std::string &name : this->m_placeholder_parser_failed_templates)
|
||||
msg += std::string("\t") + name + "\n";
|
||||
msg += "\nPlease inspect the file ";
|
||||
msg += path_tmp + " for error messages enclosed between\n";
|
||||
msg += " !!!!! Failed to process the custom G-code template ...\n";
|
||||
msg += "and\n";
|
||||
msg += " !!!!! End of an error report for the custom G-code template ...\n";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
if (! result)
|
||||
boost::nowide::remove(path_tmp.c_str());
|
||||
|
||||
return result;
|
||||
if (boost::nowide::rename(path_tmp.c_str(), path) != 0)
|
||||
throw std::runtime_error(
|
||||
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
|
||||
"Is " + path_tmp + " locked?" + '\n');
|
||||
}
|
||||
|
||||
bool GCode::_do_export(Print &print, FILE *file)
|
||||
void GCode::_do_export(Print &print, FILE *file)
|
||||
{
|
||||
// resets time estimator
|
||||
m_time_estimator.reset();
|
||||
|
@ -515,15 +541,7 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
|
||||
|
||||
// Write information on the generator.
|
||||
{
|
||||
const auto now = boost::posix_time::second_clock::local_time();
|
||||
const auto date = now.date();
|
||||
fprintf(file, "; generated by Slic3r %s on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
|
||||
SLIC3R_VERSION,
|
||||
// Local date in an ANSII format.
|
||||
int(now.date().year()), int(now.date().month()), int(now.date().day()),
|
||||
int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds()));
|
||||
}
|
||||
fprintf(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
|
||||
// Write notes (content of the Print Settings tab -> Notes)
|
||||
{
|
||||
std::list<std::string> lines;
|
||||
|
@ -541,6 +559,7 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
{
|
||||
const PrintObject *first_object = print.objects.front();
|
||||
const double layer_height = first_object->config.layer_height.value;
|
||||
const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height);
|
||||
for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
|
||||
auto region = print.regions[region_id];
|
||||
fprintf(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
|
||||
|
@ -551,7 +570,7 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
if (print.has_support_material())
|
||||
fprintf(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
|
||||
if (print.config.first_layer_extrusion_width.value > 0)
|
||||
fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, true, -1., *first_object).width);
|
||||
fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
|
||||
fprintf(file, "\n");
|
||||
}
|
||||
}
|
||||
|
@ -566,6 +585,7 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
unsigned int initial_extruder_id = (unsigned int)-1;
|
||||
unsigned int final_extruder_id = (unsigned int)-1;
|
||||
size_t initial_print_object_id = 0;
|
||||
bool has_wipe_tower = false;
|
||||
if (print.config.complete_objects.value) {
|
||||
// Find the 1st printing object, find its tool ordering and the initial extruder ID.
|
||||
for (; initial_print_object_id < print.objects.size(); ++initial_print_object_id) {
|
||||
|
@ -580,6 +600,7 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
ToolOrdering(print, initial_extruder_id) :
|
||||
print.m_tool_ordering;
|
||||
initial_extruder_id = tool_ordering.first_extruder();
|
||||
has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
|
||||
}
|
||||
if (initial_extruder_id == (unsigned int)-1) {
|
||||
// Nothing to print!
|
||||
|
@ -596,28 +617,35 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
if (! print.config.cooling.get_at(initial_extruder_id) || print.config.disable_fan_first_layers.get_at(initial_extruder_id))
|
||||
write(file, m_writer.set_fan(0, true));
|
||||
|
||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||
{
|
||||
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
|
||||
// the custom start G-code emited these.
|
||||
//FIXME Should one parse the custom G-code to initialize the "current" bed temp state at m_writer?
|
||||
std::string gcode = m_writer.set_bed_temperature(print.config.first_layer_bed_temperature.get_at(initial_extruder_id), true);
|
||||
if (boost::ifind_first(print.config.start_gcode.value, std::string("M140")).empty() &&
|
||||
boost::ifind_first(print.config.start_gcode.value, std::string("M190")).empty())
|
||||
write(file, gcode);
|
||||
}
|
||||
|
||||
// Set extruder(s) temperature before and after start G-code.
|
||||
this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false);
|
||||
// Let the start-up script prime the 1st printing tool.
|
||||
m_placeholder_parser.set("initial_tool", initial_extruder_id);
|
||||
m_placeholder_parser.set("initial_extruder", initial_extruder_id);
|
||||
m_placeholder_parser.set("current_extruder", initial_extruder_id);
|
||||
writeln(file, m_placeholder_parser.process(print.config.start_gcode.value, initial_extruder_id));
|
||||
// Useful for sequential prints.
|
||||
m_placeholder_parser.set("current_object_idx", 0);
|
||||
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
|
||||
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
|
||||
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id);
|
||||
|
||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
|
||||
// Set extruder(s) temperature before and after start G-code.
|
||||
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
|
||||
// Write the custom start G-code
|
||||
writeln(file, start_gcode);
|
||||
// Process filament-specific gcode in extruder order.
|
||||
for (const std::string &start_gcode : print.config.start_filament_gcode.values)
|
||||
writeln(file, m_placeholder_parser.process(start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front())));
|
||||
this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, true);
|
||||
if (print.config.single_extruder_multi_material) {
|
||||
if (has_wipe_tower) {
|
||||
// Wipe tower will control the extruder switching, it will call the start_filament_gcode.
|
||||
} else {
|
||||
// Only initialize the initial extruder.
|
||||
writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id));
|
||||
}
|
||||
} else {
|
||||
for (const std::string &start_gcode : print.config.start_filament_gcode.values)
|
||||
writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front())));
|
||||
}
|
||||
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true);
|
||||
|
||||
// Set other general things.
|
||||
write(file, this->preamble());
|
||||
|
@ -707,9 +735,12 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
// Ff we are printing the bottom layer of an object, and we have already finished
|
||||
// another one, set first layer temperatures. This happens before the Z move
|
||||
// is triggered, so machine has more time to reach such temperatures.
|
||||
write(file, m_writer.set_bed_temperature(print.config.first_layer_bed_temperature.get_at(initial_extruder_id)));
|
||||
// Set first layer extruder.
|
||||
this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false);
|
||||
m_placeholder_parser.set("current_object_idx", int(finished_objects));
|
||||
std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config.between_objects_gcode.value, initial_extruder_id);
|
||||
// Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
|
||||
this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
|
||||
this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
|
||||
writeln(file, between_objects_gcode);
|
||||
}
|
||||
// Reset the cooling buffer internal state (the current position, feed rate, accelerations).
|
||||
m_cooling_buffer->reset();
|
||||
|
@ -740,33 +771,29 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
// All extrusion moves with the same top layer height are extruded uninterrupted.
|
||||
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
|
||||
// Prusa Multi-Material wipe tower.
|
||||
if (print.has_wipe_tower() && ! layers_to_print.empty()) {
|
||||
if (tool_ordering.has_wipe_tower()) {
|
||||
m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
|
||||
write(file, m_wipe_tower->prime(*this));
|
||||
// Verify, whether the print overaps the priming extrusions.
|
||||
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
|
||||
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
|
||||
for (const PrintObject *print_object : print.objects)
|
||||
bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
|
||||
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
|
||||
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
|
||||
bbox_prime.offset(0.5f);
|
||||
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
|
||||
write(file, this->retract());
|
||||
write(file, "M300 S800 P500\n");
|
||||
if (bbox_prime.overlap(bbox_print)) {
|
||||
// Wait for the user to remove the priming extrusions, otherwise they would
|
||||
// get covered by the print.
|
||||
write(file, "M1 Remove priming towers and click button.\n");
|
||||
if (has_wipe_tower && ! layers_to_print.empty()) {
|
||||
m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
|
||||
write(file, m_wipe_tower->prime(*this));
|
||||
// Verify, whether the print overaps the priming extrusions.
|
||||
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
|
||||
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
|
||||
for (const PrintObject *print_object : print.objects)
|
||||
bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
|
||||
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
|
||||
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
|
||||
bbox_prime.offset(0.5f);
|
||||
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
|
||||
write(file, this->retract());
|
||||
write(file, "M300 S800 P500\n");
|
||||
if (bbox_prime.overlap(bbox_print)) {
|
||||
// Wait for the user to remove the priming extrusions, otherwise they would
|
||||
// get covered by the print.
|
||||
write(file, "M1 Remove priming towers and click button.\n");
|
||||
} else {
|
||||
// Just wait for a bit to let the user check, that the priming succeeded.
|
||||
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
|
||||
write(file, "M1 S10\n");
|
||||
}
|
||||
else {
|
||||
// Just wait for a bit to let the user check, that the priming succeeded.
|
||||
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
|
||||
write(file, "M1 S10\n");
|
||||
}
|
||||
} else
|
||||
write(file, WipeTowerIntegration::prime_single_color_print(print, initial_extruder_id, *this));
|
||||
}
|
||||
// Extrude the layers.
|
||||
for (auto &layer : layers_to_print) {
|
||||
|
@ -786,9 +813,14 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
write(file, this->retract());
|
||||
write(file, m_writer.set_fan(false));
|
||||
// Process filament-specific gcode in extruder order.
|
||||
for (const std::string &end_gcode : print.config.end_filament_gcode.values)
|
||||
writeln(file, m_placeholder_parser.process(end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front())));
|
||||
writeln(file, m_placeholder_parser.process(print.config.end_gcode, m_writer.extruder()->id()));
|
||||
if (print.config.single_extruder_multi_material) {
|
||||
// Process the end_filament_gcode for the active filament only.
|
||||
writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id()));
|
||||
} else {
|
||||
for (const std::string &end_gcode : print.config.end_filament_gcode.values)
|
||||
writeln(file, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front())));
|
||||
}
|
||||
writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id()));
|
||||
write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100%
|
||||
write(file, m_writer.postamble());
|
||||
|
||||
|
@ -830,22 +862,117 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||
for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++ i) {
|
||||
StaticPrintConfig *cfg = configs[i];
|
||||
for (const std::string &key : cfg->keys())
|
||||
fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
|
||||
if (key != "compatible_printers")
|
||||
fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
|
||||
{
|
||||
try {
|
||||
return m_placeholder_parser.process(templ, current_extruder_id, config_override);
|
||||
} catch (std::runtime_error &err) {
|
||||
// Collect the names of failed template substitutions for error reporting.
|
||||
this->m_placeholder_parser_failed_templates.insert(name);
|
||||
// Insert the macro error message into the G-code.
|
||||
return
|
||||
std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" +
|
||||
err.what() +
|
||||
"!!!!! End of an error report for the custom G-code template " + name + "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code.
|
||||
// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
|
||||
static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out)
|
||||
{
|
||||
temp_out = -1;
|
||||
if (gcode.empty())
|
||||
return false;
|
||||
|
||||
const char *ptr = gcode.data();
|
||||
bool temp_set_by_gcode = false;
|
||||
while (*ptr != 0) {
|
||||
// Skip whitespaces.
|
||||
for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
|
||||
if (*ptr == 'M') {
|
||||
// Line starts with 'M'. It is a machine command.
|
||||
++ ptr;
|
||||
// Parse the M code value.
|
||||
char *endptr = nullptr;
|
||||
int mcode = int(strtol(ptr, &endptr, 10));
|
||||
if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) {
|
||||
// M104/M109 or M140/M190 found.
|
||||
ptr = endptr;
|
||||
// Let the caller know that the custom G-code sets the temperature.
|
||||
temp_set_by_gcode = true;
|
||||
// Now try to parse the temperature value.
|
||||
// While not at the end of the line:
|
||||
while (strchr(";\r\n\0", *ptr) == nullptr) {
|
||||
// Skip whitespaces.
|
||||
for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
|
||||
if (*ptr == 'S') {
|
||||
// Skip whitespaces.
|
||||
for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr);
|
||||
// Parse an int.
|
||||
endptr = nullptr;
|
||||
long temp_parsed = strtol(ptr, &endptr, 10);
|
||||
if (endptr > ptr) {
|
||||
ptr = endptr;
|
||||
temp_out = temp_parsed;
|
||||
}
|
||||
} else {
|
||||
// Skip this word.
|
||||
for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Skip the rest of the line.
|
||||
for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr);
|
||||
// Skip the end of line indicators.
|
||||
for (; *ptr == '\r' || *ptr == '\n'; ++ ptr);
|
||||
}
|
||||
return temp_set_by_gcode;
|
||||
}
|
||||
|
||||
// Write 1st layer bed temperatures into the G-code.
|
||||
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
|
||||
// M140 - Set Extruder Temperature
|
||||
// M190 - Set Extruder Temperature and Wait
|
||||
void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
|
||||
{
|
||||
// Initial bed temperature based on the first extruder.
|
||||
int temp = print.config.first_layer_bed_temperature.get_at(first_printing_extruder_id);
|
||||
// Is the bed temperature set by the provided custom G-code?
|
||||
int temp_by_gcode = -1;
|
||||
bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode);
|
||||
if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000)
|
||||
temp = temp_by_gcode;
|
||||
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
|
||||
// the custom start G-code emited these.
|
||||
std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait);
|
||||
if (! temp_set_by_gcode)
|
||||
write(file, set_temp_gcode);
|
||||
}
|
||||
|
||||
// Write 1st layer extruder temperatures into the G-code.
|
||||
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
|
||||
// FIXME this does not work correctly for multi-extruder, single heater configuration as it emits multiple preheat commands for the same heater.
|
||||
// M104 - Set Extruder Temperature
|
||||
// M109 - Set Extruder Temperature and Wait
|
||||
void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, unsigned int first_printing_extruder_id, bool wait)
|
||||
void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
|
||||
{
|
||||
if (boost::ifind_first(print.config.start_gcode.value, std::string("M104")).empty() &&
|
||||
boost::ifind_first(print.config.start_gcode.value, std::string("M109")).empty()) {
|
||||
// Is the bed temperature set by the provided custom G-code?
|
||||
int temp_by_gcode = -1;
|
||||
if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) {
|
||||
// Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
|
||||
int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id);
|
||||
if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
|
||||
temp = temp_by_gcode;
|
||||
m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id);
|
||||
} else {
|
||||
// Custom G-code does not set the extruder temperature. Do it now.
|
||||
if (print.config.single_extruder_multi_material.value) {
|
||||
// Set temperature of the first printing extruder only.
|
||||
int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id);
|
||||
|
@ -953,18 +1080,22 @@ void GCode::process_layer(
|
|||
|
||||
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
|
||||
if (! print.config.before_layer_gcode.value.empty()) {
|
||||
PlaceholderParser pp(m_placeholder_parser);
|
||||
pp.set("layer_num", m_layer_index + 1);
|
||||
pp.set("layer_z", print_z);
|
||||
gcode += pp.process(print.config.before_layer_gcode.value, m_writer.extruder()->id()) + "\n";
|
||||
DynamicConfig config;
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
|
||||
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
|
||||
gcode += this->placeholder_parser_process("before_layer_gcode",
|
||||
print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config)
|
||||
+ "\n";
|
||||
}
|
||||
gcode += this->change_layer(print_z); // this will increase m_layer_index
|
||||
m_layer = &layer;
|
||||
if (! print.config.layer_gcode.value.empty()) {
|
||||
PlaceholderParser pp(m_placeholder_parser);
|
||||
pp.set("layer_num", m_layer_index);
|
||||
pp.set("layer_z", print_z);
|
||||
gcode += pp.process(print.config.layer_gcode.value, m_writer.extruder()->id()) + "\n";
|
||||
DynamicConfig config;
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
|
||||
gcode += this->placeholder_parser_process("layer_gcode",
|
||||
print.config.layer_gcode.value, m_writer.extruder()->id(), &config)
|
||||
+ "\n";
|
||||
}
|
||||
|
||||
if (! first_layer && ! m_second_layer_things_done) {
|
||||
|
@ -2141,13 +2272,14 @@ GCode::retract(bool toolchange)
|
|||
|
||||
std::string GCode::set_extruder(unsigned int extruder_id)
|
||||
{
|
||||
m_placeholder_parser.set("current_extruder", extruder_id);
|
||||
if (!m_writer.need_toolchange(extruder_id))
|
||||
return "";
|
||||
|
||||
// if we are running a single-extruder setup, just set the extruder and return nothing
|
||||
if (!m_writer.multiple_extruders)
|
||||
if (!m_writer.multiple_extruders) {
|
||||
m_placeholder_parser.set("current_extruder", extruder_id);
|
||||
return m_writer.toolchange(extruder_id);
|
||||
}
|
||||
|
||||
// prepend retraction on the current extruder
|
||||
std::string gcode = this->retract(true);
|
||||
|
@ -2155,21 +2287,41 @@ std::string GCode::set_extruder(unsigned int extruder_id)
|
|||
// Always reset the extrusion path, even if the tool change retract is set to zero.
|
||||
m_wipe.reset_path();
|
||||
|
||||
// append custom toolchange G-code
|
||||
if (m_writer.extruder() != nullptr && !m_config.toolchange_gcode.value.empty()) {
|
||||
PlaceholderParser pp = m_placeholder_parser;
|
||||
pp.set("previous_extruder", m_writer.extruder()->id());
|
||||
pp.set("next_extruder", extruder_id);
|
||||
gcode += pp.process(m_config.toolchange_gcode.value, extruder_id) + '\n';
|
||||
if (m_writer.extruder() != nullptr) {
|
||||
// Process the custom end_filament_gcode in case of single_extruder_multi_material.
|
||||
unsigned int old_extruder_id = m_writer.extruder()->id();
|
||||
const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id);
|
||||
if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) {
|
||||
gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
|
||||
check_add_eol(gcode);
|
||||
}
|
||||
}
|
||||
|
||||
m_placeholder_parser.set("current_extruder", extruder_id);
|
||||
|
||||
if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) {
|
||||
// Process the custom toolchange_gcode.
|
||||
DynamicConfig config;
|
||||
config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id()));
|
||||
config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id));
|
||||
gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config);
|
||||
check_add_eol(gcode);
|
||||
}
|
||||
|
||||
// if ooze prevention is enabled, park current extruder in the nearest
|
||||
// standby point and set it to the standby temperature
|
||||
// If ooze prevention is enabled, park current extruder in the nearest
|
||||
// standby point and set it to the standby temperature.
|
||||
if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
|
||||
gcode += m_ooze_prevention.pre_toolchange(*this);
|
||||
// append the toolchange command
|
||||
// Append the toolchange command.
|
||||
gcode += m_writer.toolchange(extruder_id);
|
||||
// set the new extruder to the operating temperature
|
||||
// Append the filament start G-code for single_extruder_multi_material.
|
||||
const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
|
||||
if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) {
|
||||
// Process the start_filament_gcode for the active filament only.
|
||||
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id);
|
||||
check_add_eol(gcode);
|
||||
}
|
||||
// Set the new extruder to the operating temperature.
|
||||
if (m_ooze_prevention.enable)
|
||||
gcode += m_ooze_prevention.post_toolchange(*this);
|
||||
|
||||
|
|
|
@ -91,7 +91,6 @@ public:
|
|||
m_brim_done(false) {}
|
||||
|
||||
std::string prime(GCode &gcodegen);
|
||||
static std::string prime_single_color_print(const Print & /* print */, unsigned int initial_tool, GCode & /* gcodegen */);
|
||||
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
|
||||
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
|
||||
std::string finalize(GCode &gcodegen);
|
||||
|
@ -132,7 +131,8 @@ public:
|
|||
{}
|
||||
~GCode() {}
|
||||
|
||||
bool do_export(Print *print, const char *path);
|
||||
// throws std::runtime_exception
|
||||
void do_export(Print *print, const char *path);
|
||||
|
||||
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
|
||||
const Pointf& origin() const { return m_origin; }
|
||||
|
@ -144,6 +144,10 @@ public:
|
|||
const FullPrintConfig &config() const { return m_config; }
|
||||
const Layer* layer() const { return m_layer; }
|
||||
GCodeWriter& writer() { return m_writer; }
|
||||
PlaceholderParser& placeholder_parser() { return m_placeholder_parser; }
|
||||
// Process a template through the placeholder parser, collect error messages to be reported
|
||||
// inside the generated string and after the G-code export finishes.
|
||||
std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr);
|
||||
bool enable_cooling_markers() const { return m_enable_cooling_markers; }
|
||||
|
||||
// For Perl bindings, to be used exclusively by unit tests.
|
||||
|
@ -152,7 +156,7 @@ public:
|
|||
void apply_print_config(const PrintConfig &print_config);
|
||||
|
||||
protected:
|
||||
bool _do_export(Print &print, FILE *file);
|
||||
void _do_export(Print &print, FILE *file);
|
||||
|
||||
// Object and support extrusions of the same PrintObject at the same print_z.
|
||||
struct LayerToPrint
|
||||
|
@ -224,6 +228,8 @@ protected:
|
|||
FullPrintConfig m_config;
|
||||
GCodeWriter m_writer;
|
||||
PlaceholderParser m_placeholder_parser;
|
||||
// Collection of templates, on which the placeholder substitution failed.
|
||||
std::set<std::string> m_placeholder_parser_failed_templates;
|
||||
OozePrevention m_ooze_prevention;
|
||||
Wipe m_wipe;
|
||||
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
|
||||
|
@ -272,7 +278,8 @@ protected:
|
|||
GCodeTimeEstimator m_time_estimator;
|
||||
|
||||
std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
|
||||
void _print_first_layer_extruder_temperatures(FILE *file, Print &print, unsigned int first_printing_extruder_id, bool wait);
|
||||
void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
// this flag triggers first layer speeds
|
||||
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; }
|
||||
|
||||
|
|
|
@ -53,14 +53,14 @@ public:
|
|||
|
||||
void clear() { m_layer_tools.clear(); }
|
||||
|
||||
// Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
|
||||
// Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed.
|
||||
unsigned int first_extruder() const { return m_first_printing_extruder; }
|
||||
|
||||
// Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
|
||||
unsigned int last_extruder() const { return m_last_printing_extruder; }
|
||||
|
||||
// For a multi-material print, the printing extruders are ordered in the order they shall be primed.
|
||||
std::vector<unsigned int> all_extruders() const { return m_all_printing_extruders; }
|
||||
const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; }
|
||||
|
||||
// Find LayerTools with the closest print_z.
|
||||
LayerTools& tools_for_layer(coordf_t print_z);
|
||||
|
|
|
@ -21,8 +21,8 @@ public:
|
|||
xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; }
|
||||
xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; }
|
||||
xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; }
|
||||
bool operator==(const xy &rhs) { return x == rhs.x && y == rhs.y; }
|
||||
bool operator!=(const xy &rhs) { return x != rhs.x || y != rhs.y; }
|
||||
bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; }
|
||||
bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; }
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
@ -109,7 +109,7 @@ public:
|
|||
// print_z of the first layer.
|
||||
float first_layer_height,
|
||||
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
|
||||
std::vector<unsigned int> tools,
|
||||
const std::vector<unsigned int> &tools,
|
||||
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
|
||||
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
|
||||
bool last_wipe_inside_wipe_tower,
|
||||
|
|
|
@ -368,7 +368,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
|
|||
// print_z of the first layer.
|
||||
float first_layer_height,
|
||||
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
|
||||
std::vector<unsigned int> tools,
|
||||
const std::vector<unsigned int> &tools,
|
||||
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
|
||||
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
|
||||
bool last_wipe_inside_wipe_tower,
|
||||
|
|
|
@ -112,7 +112,7 @@ public:
|
|||
// print_z of the first layer.
|
||||
float first_layer_height,
|
||||
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
|
||||
std::vector<unsigned int> tools,
|
||||
const std::vector<unsigned int> &tools,
|
||||
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
|
||||
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
|
||||
bool last_wipe_inside_wipe_tower,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,23 +11,28 @@ namespace Slic3r {
|
|||
|
||||
class PlaceholderParser
|
||||
{
|
||||
public:
|
||||
std::map<std::string, std::string> m_single;
|
||||
std::map<std::string, std::vector<std::string>> m_multiple;
|
||||
|
||||
public:
|
||||
PlaceholderParser();
|
||||
|
||||
void update_timestamp();
|
||||
void apply_config(const DynamicPrintConfig &config);
|
||||
void apply_env_variables();
|
||||
void set(const std::string &key, const std::string &value);
|
||||
void set(const std::string &key, int value);
|
||||
void set(const std::string &key, unsigned int value);
|
||||
void set(const std::string &key, double value);
|
||||
void set(const std::string &key, std::vector<std::string> values);
|
||||
std::string process(std::string str, unsigned int current_extruder_id) const;
|
||||
|
||||
// Add new ConfigOption values to m_config.
|
||||
void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); }
|
||||
void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); }
|
||||
void set(const std::string &key, unsigned int value) { this->set(key, int(value)); }
|
||||
void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); }
|
||||
void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); }
|
||||
void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); }
|
||||
void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); }
|
||||
const ConfigOption* option(const std::string &key) const { return m_config.option(key); }
|
||||
|
||||
// Fill in the template.
|
||||
std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const;
|
||||
|
||||
private:
|
||||
bool find_and_replace(std::string &source, std::string const &find, std::string const &replace) const;
|
||||
DynamicConfig m_config;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -154,7 +154,6 @@ inline Polylines to_polylines(const Polygons &polys)
|
|||
return polylines;
|
||||
}
|
||||
|
||||
#if SLIC3R_CPPVER >= 11
|
||||
inline Polylines to_polylines(Polygons &&polys)
|
||||
{
|
||||
Polylines polylines;
|
||||
|
@ -168,7 +167,6 @@ inline Polylines to_polylines(Polygons &&polys)
|
|||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // Slic3r
|
||||
|
||||
|
|
|
@ -81,80 +81,80 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
|
||||
// Cache the plenty of parameters, which influence the G-code generator only,
|
||||
// or they are only notes not influencing the generated G-code.
|
||||
static std::unordered_set<std::string> steps_ignore;
|
||||
if (steps_ignore.empty()) {
|
||||
steps_ignore.insert("avoid_crossing_perimeters");
|
||||
steps_ignore.insert("bed_shape");
|
||||
steps_ignore.insert("bed_temperature");
|
||||
steps_ignore.insert("before_layer_gcode");
|
||||
steps_ignore.insert("bridge_acceleration");
|
||||
steps_ignore.insert("bridge_fan_speed");
|
||||
steps_ignore.insert("cooling");
|
||||
steps_ignore.insert("default_acceleration");
|
||||
steps_ignore.insert("deretract_speed");
|
||||
steps_ignore.insert("disable_fan_first_layers");
|
||||
steps_ignore.insert("duplicate_distance");
|
||||
steps_ignore.insert("end_gcode");
|
||||
steps_ignore.insert("end_filament_gcode");
|
||||
steps_ignore.insert("extrusion_axis");
|
||||
steps_ignore.insert("extruder_clearance_height");
|
||||
steps_ignore.insert("extruder_clearance_radius");
|
||||
steps_ignore.insert("extruder_colour");
|
||||
steps_ignore.insert("extruder_offset");
|
||||
steps_ignore.insert("extrusion_multiplier");
|
||||
steps_ignore.insert("fan_always_on");
|
||||
steps_ignore.insert("fan_below_layer_time");
|
||||
steps_ignore.insert("filament_colour");
|
||||
steps_ignore.insert("filament_diameter");
|
||||
steps_ignore.insert("filament_density");
|
||||
steps_ignore.insert("filament_notes");
|
||||
steps_ignore.insert("filament_cost");
|
||||
steps_ignore.insert("filament_max_volumetric_speed");
|
||||
steps_ignore.insert("first_layer_acceleration");
|
||||
steps_ignore.insert("first_layer_bed_temperature");
|
||||
steps_ignore.insert("first_layer_speed");
|
||||
steps_ignore.insert("gcode_comments");
|
||||
steps_ignore.insert("gcode_flavor");
|
||||
steps_ignore.insert("infill_acceleration");
|
||||
steps_ignore.insert("infill_first");
|
||||
steps_ignore.insert("layer_gcode");
|
||||
steps_ignore.insert("min_fan_speed");
|
||||
steps_ignore.insert("max_fan_speed");
|
||||
steps_ignore.insert("min_print_speed");
|
||||
steps_ignore.insert("max_print_speed");
|
||||
steps_ignore.insert("max_volumetric_speed");
|
||||
steps_ignore.insert("max_volumetric_extrusion_rate_slope_positive");
|
||||
steps_ignore.insert("max_volumetric_extrusion_rate_slope_negative");
|
||||
steps_ignore.insert("notes");
|
||||
steps_ignore.insert("only_retract_when_crossing_perimeters");
|
||||
steps_ignore.insert("output_filename_format");
|
||||
steps_ignore.insert("perimeter_acceleration");
|
||||
steps_ignore.insert("post_process");
|
||||
steps_ignore.insert("printer_notes");
|
||||
steps_ignore.insert("retract_before_travel");
|
||||
steps_ignore.insert("retract_before_wipe");
|
||||
steps_ignore.insert("retract_layer_change");
|
||||
steps_ignore.insert("retract_length");
|
||||
steps_ignore.insert("retract_length_toolchange");
|
||||
steps_ignore.insert("retract_lift");
|
||||
steps_ignore.insert("retract_lift_above");
|
||||
steps_ignore.insert("retract_lift_below");
|
||||
steps_ignore.insert("retract_restart_extra");
|
||||
steps_ignore.insert("retract_restart_extra_toolchange");
|
||||
steps_ignore.insert("retract_speed");
|
||||
steps_ignore.insert("slowdown_below_layer_time");
|
||||
steps_ignore.insert("standby_temperature_delta");
|
||||
steps_ignore.insert("start_gcode");
|
||||
steps_ignore.insert("start_filament_gcode");
|
||||
steps_ignore.insert("toolchange_gcode");
|
||||
steps_ignore.insert("threads");
|
||||
steps_ignore.insert("travel_speed");
|
||||
steps_ignore.insert("use_firmware_retraction");
|
||||
steps_ignore.insert("use_relative_e_distances");
|
||||
steps_ignore.insert("use_volumetric_e");
|
||||
steps_ignore.insert("variable_layer_height");
|
||||
steps_ignore.insert("wipe");
|
||||
}
|
||||
static std::unordered_set<std::string> steps_ignore = {
|
||||
"avoid_crossing_perimeters",
|
||||
"bed_shape",
|
||||
"bed_temperature",
|
||||
"before_layer_gcode",
|
||||
"between_objects_gcode",
|
||||
"bridge_acceleration",
|
||||
"bridge_fan_speed",
|
||||
"cooling",
|
||||
"default_acceleration",
|
||||
"deretract_speed",
|
||||
"disable_fan_first_layers",
|
||||
"duplicate_distance",
|
||||
"end_gcode",
|
||||
"end_filament_gcode",
|
||||
"extrusion_axis",
|
||||
"extruder_clearance_height",
|
||||
"extruder_clearance_radius",
|
||||
"extruder_colour",
|
||||
"extruder_offset",
|
||||
"extrusion_multiplier",
|
||||
"fan_always_on",
|
||||
"fan_below_layer_time",
|
||||
"filament_colour",
|
||||
"filament_diameter",
|
||||
"filament_density",
|
||||
"filament_notes",
|
||||
"filament_cost",
|
||||
"filament_max_volumetric_speed",
|
||||
"first_layer_acceleration",
|
||||
"first_layer_bed_temperature",
|
||||
"first_layer_speed",
|
||||
"gcode_comments",
|
||||
"gcode_flavor",
|
||||
"infill_acceleration",
|
||||
"infill_first",
|
||||
"layer_gcode",
|
||||
"min_fan_speed",
|
||||
"max_fan_speed",
|
||||
"min_print_speed",
|
||||
"max_print_speed",
|
||||
"max_volumetric_speed",
|
||||
"max_volumetric_extrusion_rate_slope_positive",
|
||||
"max_volumetric_extrusion_rate_slope_negative",
|
||||
"notes",
|
||||
"only_retract_when_crossing_perimeters",
|
||||
"output_filename_format",
|
||||
"perimeter_acceleration",
|
||||
"post_process",
|
||||
"printer_notes",
|
||||
"retract_before_travel",
|
||||
"retract_before_wipe",
|
||||
"retract_layer_change",
|
||||
"retract_length",
|
||||
"retract_length_toolchange",
|
||||
"retract_lift",
|
||||
"retract_lift_above",
|
||||
"retract_lift_below",
|
||||
"retract_restart_extra",
|
||||
"retract_restart_extra_toolchange",
|
||||
"retract_speed",
|
||||
"slowdown_below_layer_time",
|
||||
"standby_temperature_delta",
|
||||
"start_gcode",
|
||||
"start_filament_gcode",
|
||||
"toolchange_gcode",
|
||||
"threads",
|
||||
"travel_speed",
|
||||
"use_firmware_retraction",
|
||||
"use_relative_e_distances",
|
||||
"use_volumetric_e",
|
||||
"variable_layer_height",
|
||||
"wipe"
|
||||
};
|
||||
|
||||
std::vector<PrintStep> steps;
|
||||
std::vector<PrintObjectStep> osteps;
|
||||
|
@ -707,7 +707,10 @@ double Print::skirt_first_layer_height() const
|
|||
Flow Print::brim_flow() const
|
||||
{
|
||||
ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width;
|
||||
if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width;
|
||||
if (width.value == 0)
|
||||
width = this->regions.front()->config.perimeter_extrusion_width;
|
||||
if (width.value == 0)
|
||||
width = this->objects.front()->config.extrusion_width;
|
||||
|
||||
/* We currently use a random region's perimeter extruder.
|
||||
While this works for most cases, we should probably consider all of the perimeter
|
||||
|
@ -726,7 +729,10 @@ Flow Print::brim_flow() const
|
|||
Flow Print::skirt_flow() const
|
||||
{
|
||||
ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width;
|
||||
if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width;
|
||||
if (width.value == 0)
|
||||
width = this->regions.front()->config.perimeter_extrusion_width;
|
||||
if (width.value == 0)
|
||||
width = this->objects.front()->config.extrusion_width;
|
||||
|
||||
/* We currently use a random object's support material extruder.
|
||||
While this works for most cases, we should probably consider all of the support material
|
||||
|
@ -968,7 +974,6 @@ void Print::_make_wipe_tower()
|
|||
|
||||
// Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
|
||||
m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
|
||||
unsigned int initial_extruder_id = m_tool_ordering.first_extruder();
|
||||
if (! m_tool_ordering.has_wipe_tower())
|
||||
// Don't generate any wipe tower.
|
||||
return;
|
||||
|
@ -977,7 +982,7 @@ void Print::_make_wipe_tower()
|
|||
WipeTowerPrusaMM wipe_tower(
|
||||
float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value),
|
||||
float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value),
|
||||
initial_extruder_id);
|
||||
m_tool_ordering.first_extruder());
|
||||
|
||||
//wipe_tower.set_retract();
|
||||
//wipe_tower.set_zhop();
|
||||
|
@ -1000,7 +1005,8 @@ void Print::_make_wipe_tower()
|
|||
|
||||
// Generate the wipe tower layers.
|
||||
m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size());
|
||||
unsigned int current_extruder_id = initial_extruder_id;
|
||||
// Set current_extruder_id to the last extruder primed.
|
||||
unsigned int current_extruder_id = m_tool_ordering.all_extruders().back();
|
||||
for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) {
|
||||
if (! layer_tools.has_wipe_tower)
|
||||
// This is a support only layer, or the wipe tower does not reach to this height.
|
||||
|
@ -1015,7 +1021,11 @@ void Print::_make_wipe_tower()
|
|||
last_layer);
|
||||
std::vector<WipeTower::ToolChangeResult> tool_changes;
|
||||
for (unsigned int extruder_id : layer_tools.extruders)
|
||||
if ((first_layer && extruder_id == initial_extruder_id) || extruder_id != current_extruder_id) {
|
||||
// Call the wipe_tower.tool_change() at the first layer for the initial extruder
|
||||
// to extrude the wipe tower brim,
|
||||
if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) ||
|
||||
// or when an extruder shall be switched.
|
||||
extruder_id != current_extruder_id) {
|
||||
tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, extruder_id == layer_tools.extruders.back(), WipeTower::PURPOSE_EXTRUDE));
|
||||
current_extruder_id = extruder_id;
|
||||
}
|
||||
|
@ -1065,7 +1075,11 @@ void Print::_make_wipe_tower()
|
|||
std::string Print::output_filename()
|
||||
{
|
||||
this->placeholder_parser.update_timestamp();
|
||||
return this->placeholder_parser.process(this->config.output_filename_format.value, 0);
|
||||
try {
|
||||
return this->placeholder_parser.process(this->config.output_filename_format.value, 0);
|
||||
} catch (std::runtime_error &err) {
|
||||
throw std::runtime_error(std::string("Failed processing of the output_filename_format template.\n") + err.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Print::output_filepath(const std::string &path)
|
||||
|
@ -1079,13 +1093,13 @@ std::string Print::output_filepath(const std::string &path)
|
|||
if (! input_file.empty())
|
||||
break;
|
||||
}
|
||||
return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).string();
|
||||
return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string();
|
||||
}
|
||||
|
||||
// if we were supplied a directory, use it and append our automatically generated filename
|
||||
boost::filesystem::path p(path);
|
||||
if (boost::filesystem::is_directory(p))
|
||||
return (p / this->output_filename()).string();
|
||||
return (p / this->output_filename()).make_preferred().string();
|
||||
|
||||
// if we were supplied a file which is not a directory, use it
|
||||
return path;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -142,6 +142,9 @@ public:
|
|||
DynamicPrintConfig() {}
|
||||
DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {}
|
||||
|
||||
static DynamicPrintConfig* new_from_defaults();
|
||||
static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys);
|
||||
|
||||
// Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
|
||||
const ConfigDef* def() const override { return &print_config_def; }
|
||||
|
||||
|
@ -449,6 +452,7 @@ class GCodeConfig : public StaticPrintConfig
|
|||
STATIC_PRINT_CONFIG_CACHE(GCodeConfig)
|
||||
public:
|
||||
ConfigOptionString before_layer_gcode;
|
||||
ConfigOptionString between_objects_gcode;
|
||||
ConfigOptionFloats deretract_speed;
|
||||
ConfigOptionString end_gcode;
|
||||
ConfigOptionStrings end_filament_gcode;
|
||||
|
@ -497,6 +501,7 @@ protected:
|
|||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||
{
|
||||
OPT_PTR(before_layer_gcode);
|
||||
OPT_PTR(between_objects_gcode);
|
||||
OPT_PTR(deretract_speed);
|
||||
OPT_PTR(end_gcode);
|
||||
OPT_PTR(end_filament_gcode);
|
||||
|
|
|
@ -103,7 +103,7 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters
|
|||
sp1.layer_height == sp2.layer_height &&
|
||||
sp1.min_layer_height == sp2.min_layer_height &&
|
||||
sp1.max_layer_height == sp2.max_layer_height &&
|
||||
sp1.max_suport_layer_height == sp2.max_suport_layer_height &&
|
||||
// sp1.max_suport_layer_height == sp2.max_suport_layer_height &&
|
||||
sp1.first_print_layer_height == sp2.first_print_layer_height &&
|
||||
sp1.first_object_layer_height == sp2.first_object_layer_height &&
|
||||
sp1.first_object_layer_bridging == sp2.first_object_layer_bridging &&
|
||||
|
|
|
@ -2569,7 +2569,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
// TODO: use brim ordering algorithm
|
||||
to_infill_polygons = to_polygons(to_infill);
|
||||
// TODO: use offset2_ex()
|
||||
to_infill = offset_ex(to_infill, float(- flow.scaled_spacing()));
|
||||
to_infill = offset_ex(to_infill, float(- 0.4 * flow.scaled_spacing()));
|
||||
extrusion_entities_append_paths(
|
||||
support_layer.support_fills.entities,
|
||||
to_polylines(STDMOVE(to_infill_polygons)),
|
||||
|
@ -2602,7 +2602,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
// Base flange.
|
||||
filler->angle = raft_angle_1st_layer;
|
||||
filler->spacing = m_first_layer_flow.spacing();
|
||||
density = 0.5f;
|
||||
// 70% of density on the 1st layer.
|
||||
density = 0.7f;
|
||||
} else if (support_layer_id >= m_slicing_params.base_raft_layers) {
|
||||
filler->angle = raft_angle_interface;
|
||||
// We don't use $base_flow->spacing because we need a constant spacing
|
||||
|
@ -2776,7 +2777,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
// TODO: use brim ordering algorithm
|
||||
Polygons to_infill_polygons = to_polygons(to_infill);
|
||||
// TODO: use offset2_ex()
|
||||
to_infill = offset_ex(to_infill, - float(flow.scaled_spacing()));
|
||||
to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing()));
|
||||
extrusion_entities_append_paths(
|
||||
base_layer.extrusions,
|
||||
to_polylines(STDMOVE(to_infill_polygons)),
|
||||
|
|
|
@ -1208,6 +1208,8 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
|
|||
|
||||
// perform a safety offset to merge very close facets (TODO: find test case for this)
|
||||
double safety_offset = scale_(0.0499);
|
||||
//FIXME see https://github.com/prusa3d/Slic3r/issues/520
|
||||
// double safety_offset = scale_(0.0001);
|
||||
ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
|
||||
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef slic3r_Utils_hpp_
|
||||
#define slic3r_Utils_hpp_
|
||||
|
||||
#include <locale>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
extern void set_logging_level(unsigned int level);
|
||||
|
@ -8,15 +10,31 @@ extern void trace(unsigned int level, const char *message);
|
|||
|
||||
// Set a path with GUI resource files.
|
||||
void set_var_dir(const std::string &path);
|
||||
// Return a path to the GUI resource files.
|
||||
// Return a full path to the GUI resource files.
|
||||
const std::string& var_dir();
|
||||
// Return a resource path for a file_name.
|
||||
// Return a full resource path for a file_name.
|
||||
std::string var(const std::string &file_name);
|
||||
|
||||
// Set a path with various static definition data (for example the initial config bundles).
|
||||
void set_resources_dir(const std::string &path);
|
||||
// Return a full path to the resources directory.
|
||||
const std::string& resources_dir();
|
||||
|
||||
// Set a path with preset files.
|
||||
void set_data_dir(const std::string &path);
|
||||
// Return a full path to the GUI resource files.
|
||||
const std::string& data_dir();
|
||||
|
||||
extern std::string encode_path(const char *src);
|
||||
extern std::string decode_path(const char *src);
|
||||
extern std::string normalize_utf8_nfc(const char *src);
|
||||
|
||||
// Timestamp formatted for header_slic3r_generated().
|
||||
extern std::string timestamp_str();
|
||||
// Standard "generated by Slic3r version xxx timestamp xxx" header string,
|
||||
// to be placed at the top of Slic3r generated files.
|
||||
inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); }
|
||||
|
||||
// Compute the next highest power of 2 of 32-bit v
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html
|
||||
template<typename T>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include <boost/thread.hpp>
|
||||
|
||||
#define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
|
||||
#define SLIC3R_VERSION "1.33.8.devel"
|
||||
#define SLIC3R_VERSION "1.38.4"
|
||||
#define SLIC3R_BUILD "UNKNOWN"
|
||||
|
||||
typedef long coord_t;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <locale>
|
||||
#include <ctime>
|
||||
|
||||
#include <boost/log/core.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
@ -6,8 +7,9 @@
|
|||
|
||||
#include <boost/locale.hpp>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/date_time/local_time/local_time.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <boost/nowide/integration/filesystem.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
|
@ -87,6 +89,30 @@ std::string var(const std::string &file_name)
|
|||
return file.string();
|
||||
}
|
||||
|
||||
static std::string g_resources_dir;
|
||||
|
||||
void set_resources_dir(const std::string &dir)
|
||||
{
|
||||
g_resources_dir = dir;
|
||||
}
|
||||
|
||||
const std::string& resources_dir()
|
||||
{
|
||||
return g_resources_dir;
|
||||
}
|
||||
|
||||
static std::string g_data_dir;
|
||||
|
||||
void set_data_dir(const std::string &dir)
|
||||
{
|
||||
g_data_dir = dir;
|
||||
}
|
||||
|
||||
const std::string& data_dir()
|
||||
{
|
||||
return g_data_dir;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#ifdef SLIC3R_HAS_BROKEN_CROAK
|
||||
|
@ -207,4 +233,16 @@ std::string normalize_utf8_nfc(const char *src)
|
|||
return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8);
|
||||
}
|
||||
|
||||
std::string timestamp_str()
|
||||
{
|
||||
const auto now = boost::posix_time::second_clock::local_time();
|
||||
const auto date = now.date();
|
||||
char buf[2048];
|
||||
sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d",
|
||||
// Local date in an ANSII format.
|
||||
int(now.date().year()), int(now.date().month()), int(now.date().day()),
|
||||
int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds()));
|
||||
return buf;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
|
|
@ -54,12 +54,14 @@ REGISTER_CLASS(Surface, "Surface");
|
|||
REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
|
||||
REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2");
|
||||
REGISTER_CLASS(TriangleMesh, "TriangleMesh");
|
||||
REGISTER_CLASS(AppConfig, "GUI::AppConfig");
|
||||
REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader");
|
||||
REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume");
|
||||
REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection");
|
||||
REGISTER_CLASS(Preset, "GUI::Preset");
|
||||
REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
|
||||
REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
|
||||
REGISTER_CLASS(PresetHints, "GUI::PresetHints");
|
||||
|
||||
SV* ConfigBase__as_hash(ConfigBase* THIS)
|
||||
{
|
||||
|
|
154
xs/src/slic3r/GUI/AppConfig.cpp
Normal file
154
xs/src/slic3r/GUI/AppConfig.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
#include <GL/glew.h>
|
||||
|
||||
#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 <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>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
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", "1");
|
||||
// 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");
|
||||
// Version check is enabled by default in the config, but it is not implemented yet.
|
||||
if (get("version_check").empty())
|
||||
set("version_check", "1");
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
c.close();
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
std::string AppConfig::config_path()
|
||||
{
|
||||
return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string();
|
||||
}
|
||||
|
||||
bool AppConfig::exists()
|
||||
{
|
||||
return boost::filesystem::exists(AppConfig::config_path());
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
91
xs/src/slic3r/GUI/AppConfig.hpp
Normal file
91
xs/src/slic3r/GUI/AppConfig.hpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
#ifndef slic3r_AppConfig_hpp_
|
||||
#define slic3r_AppConfig_hpp_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig
|
||||
{
|
||||
public:
|
||||
AppConfig() : m_dirty(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 clear_section(const std::string §ion)
|
||||
{ m_storage[section].clear(); }
|
||||
|
||||
// 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);
|
||||
|
||||
// Get the default config path from Slic3r::data_dir().
|
||||
static std::string config_path();
|
||||
|
||||
// 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;
|
||||
// Has any value been modified since the config.ini has been last saved or loaded?
|
||||
bool m_dirty;
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_AppConfig_hpp_ */
|
|
@ -1,20 +1,33 @@
|
|||
#include "GUI.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#if __APPLE__
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#elif _WIN32
|
||||
#include <Windows.h>
|
||||
#include "boost/nowide/convert.hpp"
|
||||
#pragma comment(lib, "user32.lib")
|
||||
#endif
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/frame.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
#if __APPLE__
|
||||
IOPMAssertionID assertionID;
|
||||
#endif
|
||||
|
||||
void
|
||||
disable_screensaver()
|
||||
void disable_screensaver()
|
||||
{
|
||||
#if __APPLE__
|
||||
CFStringRef reasonForActivity = CFSTR("Slic3r");
|
||||
|
@ -26,8 +39,7 @@ disable_screensaver()
|
|||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
enable_screensaver()
|
||||
void enable_screensaver()
|
||||
{
|
||||
#if __APPLE__
|
||||
IOReturn success = IOPMAssertionRelease(assertionID);
|
||||
|
@ -36,8 +48,84 @@ enable_screensaver()
|
|||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
debugged()
|
||||
std::vector<std::string> scan_serial_ports()
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
#ifdef _WIN32
|
||||
// 1) Open the registry key SERIALCOM.
|
||||
HKEY hKey;
|
||||
LONG lRes = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey);
|
||||
assert(lRes == ERROR_SUCCESS);
|
||||
if (lRes == ERROR_SUCCESS) {
|
||||
// 2) Get number of values of SERIALCOM key.
|
||||
DWORD cValues; // number of values for key
|
||||
{
|
||||
TCHAR achKey[255]; // buffer for subkey name
|
||||
DWORD cbName; // size of name string
|
||||
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
|
||||
DWORD cchClassName = MAX_PATH; // size of class string
|
||||
DWORD cSubKeys=0; // number of subkeys
|
||||
DWORD cbMaxSubKey; // longest subkey size
|
||||
DWORD cchMaxClass; // longest class string
|
||||
DWORD cchMaxValue; // longest value name
|
||||
DWORD cbMaxValueData; // longest value data
|
||||
DWORD cbSecurityDescriptor; // size of security descriptor
|
||||
FILETIME ftLastWriteTime; // last write time
|
||||
// Get the class name and the value count.
|
||||
lRes = RegQueryInfoKey(
|
||||
hKey, // key handle
|
||||
achClass, // buffer for class name
|
||||
&cchClassName, // size of class string
|
||||
NULL, // reserved
|
||||
&cSubKeys, // number of subkeys
|
||||
&cbMaxSubKey, // longest subkey size
|
||||
&cchMaxClass, // longest class string
|
||||
&cValues, // number of values for this key
|
||||
&cchMaxValue, // longest value name
|
||||
&cbMaxValueData, // longest value data
|
||||
&cbSecurityDescriptor, // security descriptor
|
||||
&ftLastWriteTime); // last write time
|
||||
assert(lRes == ERROR_SUCCESS);
|
||||
}
|
||||
// 3) Read the SERIALCOM values.
|
||||
{
|
||||
DWORD dwIndex = 0;
|
||||
for (int i = 0; i < cValues; ++ i, ++ dwIndex) {
|
||||
wchar_t valueName[2048];
|
||||
DWORD valNameLen = 2048;
|
||||
DWORD dataType;
|
||||
wchar_t data[2048];
|
||||
DWORD dataSize = 4096;
|
||||
lRes = ::RegEnumValueW(hKey, dwIndex, valueName, &valNameLen, nullptr, &dataType, (BYTE*)&data, &dataSize);
|
||||
if (lRes == ERROR_SUCCESS && dataType == REG_SZ && valueName[0] != 0)
|
||||
out.emplace_back(boost::nowide::narrow(data));
|
||||
}
|
||||
}
|
||||
::RegCloseKey(hKey);
|
||||
}
|
||||
#else
|
||||
// UNIX and OS X
|
||||
std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" };
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) {
|
||||
std::string name = dir_entry.path().filename().string();
|
||||
for (const char *prefix : prefixes) {
|
||||
if (boost::starts_with(name, prefix)) {
|
||||
out.emplace_back(dir_entry.path().string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
out.erase(std::remove_if(out.begin(), out.end(),
|
||||
[](const std::string &key){
|
||||
return boost::starts_with(key, "Bluetooth") || boost::starts_with(key, "FireFly");
|
||||
}),
|
||||
out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
bool debugged()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return IsDebuggerPresent();
|
||||
|
@ -46,8 +134,7 @@ debugged()
|
|||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
void
|
||||
break_to_debugger()
|
||||
void break_to_debugger()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (IsDebuggerPresent())
|
||||
|
@ -55,4 +142,45 @@ break_to_debugger()
|
|||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
// Passing the wxWidgets GUI classes instantiated by the Perl part to C++.
|
||||
wxApp *g_wxApp = nullptr;
|
||||
wxFrame *g_wxMainFrame = nullptr;
|
||||
wxNotebook *g_wxTabPanel = nullptr;
|
||||
|
||||
void set_wxapp(wxApp *app)
|
||||
{
|
||||
g_wxApp = app;
|
||||
}
|
||||
|
||||
void set_main_frame(wxFrame *main_frame)
|
||||
{
|
||||
g_wxMainFrame = main_frame;
|
||||
}
|
||||
|
||||
void set_tab_panel(wxNotebook *tab_panel)
|
||||
{
|
||||
g_wxTabPanel = tab_panel;
|
||||
}
|
||||
|
||||
void add_debug_menu(wxMenuBar *menu)
|
||||
{
|
||||
#if 0
|
||||
auto debug_menu = new wxMenu();
|
||||
debug_menu->Append(wxWindow::NewControlId(1), "Some debug");
|
||||
menu->Append(debug_menu, _T("&Debug"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void create_preset_tab(const char *name)
|
||||
{
|
||||
auto *panel = new wxPanel(g_wxTabPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
|
||||
// Vertical sizer to hold the choice menu and the rest of the page.
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->SetSizeHints(panel);
|
||||
panel->SetSizer(sizer);
|
||||
auto *button = new wxButton(panel, wxID_ANY, "Hello World", wxDefaultPosition, wxDefaultSize, 0);
|
||||
sizer->Add(button, 0, 0, 0);
|
||||
g_wxTabPanel->AddPage(panel, name);
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
@ -1,13 +1,32 @@
|
|||
#ifndef slic3r_GUI_hpp_
|
||||
#define slic3r_GUI_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class wxApp;
|
||||
class wxFrame;
|
||||
class wxMenuBar;
|
||||
class wxNotebook;
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void disable_screensaver();
|
||||
void enable_screensaver();
|
||||
std::vector<std::string> scan_serial_ports();
|
||||
bool debugged();
|
||||
void break_to_debugger();
|
||||
|
||||
// Passing the wxWidgets GUI classes instantiated by the Perl part to C++.
|
||||
void set_wxapp(wxApp *app);
|
||||
void set_main_frame(wxFrame *main_frame);
|
||||
void set_tab_panel(wxNotebook *tab_panel);
|
||||
|
||||
void add_debug_menu(wxMenuBar *menu);
|
||||
// Create a new preset tab (print, filament or printer),
|
||||
// add it at the end of the tab panel.
|
||||
void create_preset_tab(const char *name);
|
||||
|
||||
} }
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,22 +1,113 @@
|
|||
//#undef NDEBUG
|
||||
#include <cassert>
|
||||
|
||||
#include "Preset.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <boost/nowide/cenv.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 <boost/locale.hpp>
|
||||
|
||||
#include <wx/image.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#if 0
|
||||
#define DEBUG
|
||||
#define _DEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree)
|
||||
{
|
||||
size_t app_config = 0;
|
||||
size_t bundle = 0;
|
||||
size_t config = 0;
|
||||
for (const boost::property_tree::ptree::value_type &v : tree) {
|
||||
if (v.second.empty()) {
|
||||
if (v.first == "background_processing" ||
|
||||
v.first == "last_output_path" ||
|
||||
v.first == "no_controller" ||
|
||||
v.first == "no_defaults")
|
||||
++ app_config;
|
||||
else if (v.first == "nozzle_diameter" ||
|
||||
v.first == "filament_diameter")
|
||||
++ config;
|
||||
} else if (boost::algorithm::starts_with(v.first, "print:") ||
|
||||
boost::algorithm::starts_with(v.first, "filament:") ||
|
||||
boost::algorithm::starts_with(v.first, "printer:") ||
|
||||
v.first == "settings")
|
||||
++ bundle;
|
||||
else if (v.first == "presets") {
|
||||
++ app_config;
|
||||
++ bundle;
|
||||
} else if (v.first == "recent") {
|
||||
for (auto &kvp : v.second)
|
||||
if (kvp.first == "config_directory" || kvp.first == "skein_directory")
|
||||
++ app_config;
|
||||
}
|
||||
}
|
||||
return (app_config > bundle && app_config > config) ? CONFIG_FILE_TYPE_APP_CONFIG :
|
||||
(bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG;
|
||||
}
|
||||
|
||||
// Suffix to be added to a modified preset name in the combo box.
|
||||
static std::string g_suffix_modified = " (modified)";
|
||||
const std::string& Preset::suffix_modified()
|
||||
{
|
||||
return g_suffix_modified;
|
||||
}
|
||||
// Remove an optional "(modified)" suffix from a name.
|
||||
// This converts a UI name to a unique preset identifier.
|
||||
std::string Preset::remove_suffix_modified(const std::string &name)
|
||||
{
|
||||
return boost::algorithm::ends_with(name, g_suffix_modified) ?
|
||||
name.substr(0, name.size() - g_suffix_modified.size()) :
|
||||
name;
|
||||
}
|
||||
|
||||
void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extruders)
|
||||
{
|
||||
const auto &defaults = FullPrintConfig::defaults();
|
||||
for (const std::string &key : Preset::nozzle_options()) {
|
||||
auto *opt = config.option(key, false);
|
||||
assert(opt != nullptr);
|
||||
assert(opt->is_vector());
|
||||
if (opt != nullptr && opt->is_vector())
|
||||
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
|
||||
}
|
||||
}
|
||||
|
||||
// Update new extruder fields at the printer profile.
|
||||
void Preset::normalize(DynamicPrintConfig &config)
|
||||
{
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
|
||||
if (nozzle_diameter != nullptr)
|
||||
// Loaded the Printer settings. Verify, that all extruder dependent values have enough values.
|
||||
set_num_extruders(config, (unsigned int)nozzle_diameter->values.size());
|
||||
if (config.option("filament_diameter") != nullptr) {
|
||||
// This config contains single or multiple filament presets.
|
||||
// Ensure that the filament preset vector options contain the correct number of values.
|
||||
size_t n = (nozzle_diameter == nullptr) ? 1 : nozzle_diameter->values.size();
|
||||
const auto &defaults = FullPrintConfig::defaults();
|
||||
for (const std::string &key : Preset::filament_options()) {
|
||||
if (key == "compatible_printers")
|
||||
continue;
|
||||
auto *opt = config.option(key, false);
|
||||
assert(opt != nullptr);
|
||||
assert(opt->is_vector());
|
||||
if (opt != nullptr && opt->is_vector())
|
||||
static_cast<ConfigOptionVectorBase*>(opt)->resize(n, defaults.option(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file.
|
||||
// In case of a "default" config item, return the default values.
|
||||
DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys)
|
||||
|
@ -24,284 +115,49 @@ DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys)
|
|||
// Set the configuration from the defaults.
|
||||
Slic3r::FullPrintConfig defaults;
|
||||
this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys);
|
||||
|
||||
if (! this->is_default) {
|
||||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
if (boost::algorithm::iends_with(this->file, ".gcode") || boost::algorithm::iends_with(this->file, ".g"))
|
||||
this->config.load_from_gcode(this->file);
|
||||
else
|
||||
this->config.load(this->file);
|
||||
} catch (const std::ifstream::failure&) {
|
||||
throw std::runtime_error(std::string("The selected preset does not exist anymore: ") + this->file);
|
||||
} catch (const std::runtime_error&) {
|
||||
throw std::runtime_error(std::string("Failed loading the preset file: ") + this->file);
|
||||
}
|
||||
|
||||
if (this->type == TYPE_PRINTER && std::find(keys.begin(), keys.end(), "nozzle_diameter") != keys.end()) {
|
||||
// Loaded the Printer settings. Verify, that all extruder dependent values have enough values.
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(this->config.option("nozzle_diameter"));
|
||||
size_t num_extruders = nozzle_diameter->values.size();
|
||||
auto *deretract_speed = dynamic_cast<ConfigOptionFloats*>(this->config.option("deretract_speed"));
|
||||
deretract_speed->values.resize(num_extruders, deretract_speed->values.empty() ?
|
||||
defaults.deretract_speed.values.front() : deretract_speed->values.front());
|
||||
auto *extruder_colour = dynamic_cast<ConfigOptionStrings*>(this->config.option("extruder_colour"));
|
||||
extruder_colour->values.resize(num_extruders, extruder_colour->values.empty() ?
|
||||
defaults.extruder_colour.values.front() : extruder_colour->values.front());
|
||||
auto *retract_before_wipe = dynamic_cast<ConfigOptionPercents*>(this->config.option("retract_before_wipe"));
|
||||
retract_before_wipe->values.resize(num_extruders, retract_before_wipe->values.empty() ?
|
||||
defaults.retract_before_wipe.values.front() : retract_before_wipe->values.front());
|
||||
this->config.load_from_ini(this->file);
|
||||
Preset::normalize(this->config);
|
||||
} catch (const std::ifstream::failure &err) {
|
||||
throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + this->file + "\n\tReason: " + err.what());
|
||||
} catch (const std::runtime_error &err) {
|
||||
throw std::runtime_error(std::string("Failed loading the preset file: ") + this->file + "\n\tReason: " + err.what());
|
||||
}
|
||||
}
|
||||
|
||||
this->loaded = true;
|
||||
return this->config;
|
||||
}
|
||||
|
||||
bool Preset::enable_compatible(const std::string &active_printer)
|
||||
void Preset::save()
|
||||
{
|
||||
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.optptr("compatible_printers"));
|
||||
this->is_visible = compatible_printers && ! compatible_printers->values.empty() &&
|
||||
this->config.save(this->file);
|
||||
}
|
||||
|
||||
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
|
||||
std::string Preset::label() const
|
||||
{
|
||||
return this->name + (this->is_dirty ? g_suffix_modified : "");
|
||||
}
|
||||
|
||||
bool Preset::is_compatible_with_printer(const std::string &active_printer) const
|
||||
{
|
||||
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_printers"));
|
||||
return this->is_default || active_printer.empty() ||
|
||||
compatible_printers == nullptr || compatible_printers->values.empty() ||
|
||||
std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer) !=
|
||||
compatible_printers->values.end();
|
||||
return this->is_visible;
|
||||
}
|
||||
|
||||
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys) :
|
||||
m_type(type),
|
||||
m_edited_preset(type, "", false)
|
||||
bool Preset::update_compatible_with_printer(const std::string &active_printer)
|
||||
{
|
||||
// Insert just the default preset.
|
||||
m_presets.emplace_back(Preset(type, "- default -", true));
|
||||
m_presets.front().load(keys);
|
||||
return this->is_compatible = is_compatible_with_printer(active_printer);
|
||||
}
|
||||
|
||||
// Load all presets found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
|
||||
{
|
||||
m_presets.erase(m_presets.begin()+1, m_presets.end());
|
||||
t_config_option_keys keys = this->default_preset().config.keys();
|
||||
for (auto &file : boost::filesystem::directory_iterator(boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred()))
|
||||
if (boost::filesystem::is_regular_file(file.status()) && boost::algorithm::iends_with(file.path().filename().string(), ".ini")) {
|
||||
std::string name = file.path().filename().string();
|
||||
// Remove the .ini suffix.
|
||||
name.erase(name.size() - 4);
|
||||
try {
|
||||
Preset preset(m_type, name, false);
|
||||
preset.file = file.path().string();
|
||||
preset.load(keys);
|
||||
m_presets.emplace_back(preset);
|
||||
} catch (const boost::filesystem::filesystem_error &err) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PresetCollection::set_default_suppressed(bool default_suppressed)
|
||||
{
|
||||
if (m_default_suppressed != default_suppressed) {
|
||||
m_default_suppressed = default_suppressed;
|
||||
m_presets.front().is_visible = ! default_suppressed || m_presets.size() > 1;
|
||||
}
|
||||
}
|
||||
|
||||
void PresetCollection::enable_disable_compatible_to_printer(const std::string &active_printer)
|
||||
{
|
||||
size_t num_visible = 0;
|
||||
for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset)
|
||||
if (m_presets[idx_preset].enable_compatible(active_printer))
|
||||
++ num_visible;
|
||||
if (num_visible == 0)
|
||||
// Show the "-- default --" preset.
|
||||
m_presets.front().is_visible = true;
|
||||
}
|
||||
|
||||
static std::string g_suffix_modified = " (modified)";
|
||||
|
||||
// Update the wxChoice UI component from this list of presets.
|
||||
// Hide the
|
||||
void PresetCollection::update_editor_ui(wxBitmapComboBox *ui)
|
||||
{
|
||||
if (ui == nullptr)
|
||||
return;
|
||||
|
||||
size_t n_visible = this->num_visible();
|
||||
size_t n_choice = size_t(ui->GetCount());
|
||||
std::string name_selected = dynamic_cast<wxItemContainerImmutable*>(ui)->GetStringSelection().ToUTF8().data();
|
||||
if (boost::algorithm::iends_with(name_selected, g_suffix_modified))
|
||||
// Remove the g_suffix_modified.
|
||||
name_selected.erase(name_selected.end() - g_suffix_modified.size(), name_selected.end());
|
||||
#if 0
|
||||
if (std::abs(int(n_visible) - int(n_choice)) <= 1) {
|
||||
// The number of items differs by at most one, update the choice.
|
||||
size_t i_preset = 0;
|
||||
size_t i_ui = 0;
|
||||
while (i_preset < presets.size()) {
|
||||
std::string name_ui = ui->GetString(i_ui).ToUTF8();
|
||||
if (boost::algorithm::iends_with(name_ui, g_suffix_modified))
|
||||
// Remove the g_suffix_modified.
|
||||
name_ui.erase(name_ui.end() - g_suffix_modified.size(), name_ui.end());
|
||||
while (this->presets[i_preset].name )
|
||||
const Preset &preset = this->presets[i_preset];
|
||||
if (preset)
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// Otherwise fill in the list from scratch.
|
||||
ui->Clear();
|
||||
for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) {
|
||||
const Preset &preset = this->m_presets[i];
|
||||
const wxBitmap *bmp = (i == 0 || preset.is_visible) ? m_bitmap_compatible : m_bitmap_incompatible;
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? wxNullBitmap : *bmp, (void*)i);
|
||||
if (name_selected == preset.name)
|
||||
ui->SetSelection(ui->GetCount() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PresetCollection::update_platter_ui(wxBitmapComboBox *ui)
|
||||
{
|
||||
if (ui == nullptr)
|
||||
return;
|
||||
|
||||
size_t n_visible = this->num_visible();
|
||||
size_t n_choice = size_t(ui->GetCount());
|
||||
if (std::abs(int(n_visible) - int(n_choice)) <= 1) {
|
||||
// The number of items differs by at most one, update the choice.
|
||||
} else {
|
||||
// Otherwise fill in the list from scratch.
|
||||
}
|
||||
}
|
||||
|
||||
PresetBundle::PresetBundle() :
|
||||
prints(Preset::TYPE_PRINT, print_options()),
|
||||
filaments(Preset::TYPE_FILAMENT, filament_options()),
|
||||
printers(Preset::TYPE_PRINTER, printer_options()),
|
||||
m_bitmapCompatible(new wxBitmap),
|
||||
m_bitmapIncompatible(new wxBitmap)
|
||||
{
|
||||
// Create the ID config keys, as they are not part of the Static print config classes.
|
||||
this->prints.preset(0).config.opt_string("print_settings_id", true);
|
||||
this->filaments.preset(0).config.opt_string("filament_settings_id", true);
|
||||
this->printers.preset(0).config.opt_string("print_settings_id", true);
|
||||
// Create the "compatible printers" keys, as they are not part of the Static print config classes.
|
||||
this->filaments.preset(0).config.optptr("compatible_printers", true);
|
||||
this->prints.preset(0).config.optptr("compatible_printers", true);
|
||||
}
|
||||
|
||||
PresetBundle::~PresetBundle()
|
||||
{
|
||||
assert(m_bitmapCompatible != nullptr);
|
||||
assert(m_bitmapIncompatible != nullptr);
|
||||
delete m_bitmapCompatible;
|
||||
m_bitmapCompatible = nullptr;
|
||||
delete m_bitmapIncompatible;
|
||||
m_bitmapIncompatible = nullptr;
|
||||
for (std::pair<const std::string, wxBitmap*> &bitmap : m_mapColorToBitmap)
|
||||
delete bitmap.second;
|
||||
}
|
||||
|
||||
void PresetBundle::load_presets(const std::string &dir_path)
|
||||
{
|
||||
this->prints.load_presets(dir_path, "print");
|
||||
this->prints.load_presets(dir_path, "filament");
|
||||
this->prints.load_presets(dir_path, "printer");
|
||||
}
|
||||
|
||||
bool PresetBundle::load_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible)
|
||||
{
|
||||
bool loaded_compatible = m_bitmapCompatible ->LoadFile(wxString::FromUTF8(path_bitmap_compatible.c_str()));
|
||||
bool loaded_incompatible = m_bitmapIncompatible->LoadFile(wxString::FromUTF8(path_bitmap_incompatible.c_str()));
|
||||
if (loaded_compatible) {
|
||||
prints .set_bitmap_compatible(m_bitmapCompatible);
|
||||
filaments.set_bitmap_compatible(m_bitmapCompatible);
|
||||
printers .set_bitmap_compatible(m_bitmapCompatible);
|
||||
}
|
||||
if (loaded_incompatible) {
|
||||
prints .set_bitmap_compatible(m_bitmapIncompatible);
|
||||
filaments.set_bitmap_compatible(m_bitmapIncompatible);
|
||||
printers .set_bitmap_compatible(m_bitmapIncompatible);
|
||||
}
|
||||
return loaded_compatible && loaded_incompatible;
|
||||
}
|
||||
|
||||
static inline int hex_digit_to_int(const char c)
|
||||
{
|
||||
return
|
||||
(c >= '0' && c <= '9') ? int(c - '0') :
|
||||
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
|
||||
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
|
||||
}
|
||||
|
||||
static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out)
|
||||
{
|
||||
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
|
||||
const char *c = scolor.data() + 1;
|
||||
if (scolor.size() != 7 || scolor.front() != '#')
|
||||
return false;
|
||||
for (size_t i = 0; i < 3; ++ i) {
|
||||
int digit1 = hex_digit_to_int(*c ++);
|
||||
int digit2 = hex_digit_to_int(*c ++);
|
||||
if (digit1 == -1 || digit2 == -1)
|
||||
return false;
|
||||
rgb_out[i] = (unsigned char)(digit1 * 16 + digit2);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update the colors preview at the platter extruder combo box.
|
||||
void PresetBundle::update_platter_filament_ui_colors(wxBitmapComboBox *ui, unsigned int idx_extruder, unsigned int idx_filament)
|
||||
{
|
||||
unsigned char rgb[3];
|
||||
std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder);
|
||||
if (! parse_color(extruder_color, rgb))
|
||||
// Extruder color is not defined.
|
||||
extruder_color.clear();
|
||||
|
||||
for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) {
|
||||
if (! ui->HasClientUntypedData())
|
||||
continue;
|
||||
size_t filament_preset_id = size_t(ui->GetClientData(ui_id));
|
||||
const Preset &filament_preset = filaments.preset(filament_preset_id);
|
||||
// Assign an extruder color to the selected item if the extruder color is defined.
|
||||
std::string filament_rgb = filament_preset.config.opt_string("filament_colour", 0);
|
||||
std::string extruder_rgb = (int(ui_id) == ui->GetSelection() && ! extruder_color.empty()) ? extruder_color : filament_rgb;
|
||||
wxBitmap *bitmap = nullptr;
|
||||
if (filament_rgb == extruder_rgb) {
|
||||
auto it = m_mapColorToBitmap.find(filament_rgb);
|
||||
if (it == m_mapColorToBitmap.end()) {
|
||||
// Create the bitmap.
|
||||
parse_color(filament_rgb, rgb);
|
||||
wxImage image(24, 16);
|
||||
image.SetRGB(wxRect(0, 0, 24, 16), rgb[0], rgb[1], rgb[2]);
|
||||
m_mapColorToBitmap[filament_rgb] = new wxBitmap(image);
|
||||
} else {
|
||||
bitmap = it->second;
|
||||
}
|
||||
} else {
|
||||
std::string bitmap_key = filament_rgb + extruder_rgb;
|
||||
auto it = m_mapColorToBitmap.find(bitmap_key);
|
||||
if (it == m_mapColorToBitmap.end()) {
|
||||
// Create the bitmap.
|
||||
wxImage image(24, 16);
|
||||
parse_color(extruder_rgb, rgb);
|
||||
image.SetRGB(wxRect(0, 0, 16, 16), rgb[0], rgb[1], rgb[2]);
|
||||
parse_color(filament_rgb, rgb);
|
||||
image.SetRGB(wxRect(16, 0, 8, 16), rgb[0], rgb[1], rgb[2]);
|
||||
m_mapColorToBitmap[filament_rgb] = new wxBitmap(image);
|
||||
} else {
|
||||
bitmap = it->second;
|
||||
}
|
||||
}
|
||||
ui->SetItemBitmap(ui_id, *bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& PresetBundle::print_options()
|
||||
const std::vector<std::string>& Preset::print_options()
|
||||
{
|
||||
const char *opts[] = {
|
||||
static std::vector<std::string> s_opts {
|
||||
"layer_height", "first_layer_height", "perimeters", "spiral_vase", "top_solid_layers", "bottom_solid_layers",
|
||||
"extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs",
|
||||
"seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "external_fill_pattern",
|
||||
|
@ -323,43 +179,369 @@ const std::vector<std::string>& PresetBundle::print_options()
|
|||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_per_color_wipe"
|
||||
"wipe_tower_width", "wipe_tower_per_color_wipe",
|
||||
"compatible_printers"
|
||||
};
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty())
|
||||
s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0])));
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& PresetBundle::filament_options()
|
||||
const std::vector<std::string>& Preset::filament_options()
|
||||
{
|
||||
const char *opts[] = {
|
||||
static std::vector<std::string> s_opts {
|
||||
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
|
||||
"extrusion_multiplier", "filament_density", "filament_cost", "temperature", "first_layer_temperature", "bed_temperature",
|
||||
"first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed",
|
||||
"disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode",
|
||||
"end_filament_gcode"
|
||||
"end_filament_gcode",
|
||||
"compatible_printers"
|
||||
};
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty())
|
||||
s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0])));
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& PresetBundle::printer_options()
|
||||
const std::vector<std::string>& Preset::printer_options()
|
||||
{
|
||||
const char *opts[] = {
|
||||
"bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
|
||||
"octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
|
||||
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
|
||||
"nozzle_diameter", "extruder_offset", "retract_length", "retract_lift", "retract_speed", "deretract_speed",
|
||||
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "retract_layer_change", "wipe",
|
||||
"retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", "printer_notes"
|
||||
};
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty())
|
||||
s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0])));
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
|
||||
"octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
|
||||
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
|
||||
"between_objects_gcode", "printer_notes"
|
||||
};
|
||||
s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end());
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::nozzle_options()
|
||||
{
|
||||
// ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
|
||||
static std::vector<std::string> s_opts {
|
||||
"nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
|
||||
"retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed",
|
||||
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe",
|
||||
"retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour"
|
||||
};
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys) :
|
||||
m_type(type),
|
||||
m_edited_preset(type, "", false),
|
||||
m_idx_selected(0),
|
||||
m_bitmap_main_frame(new wxBitmap)
|
||||
{
|
||||
// Insert just the default preset.
|
||||
m_presets.emplace_back(Preset(type, "- default -", true));
|
||||
m_presets.front().load(keys);
|
||||
m_edited_preset.config.apply(m_presets.front().config);
|
||||
}
|
||||
|
||||
PresetCollection::~PresetCollection()
|
||||
{
|
||||
delete m_bitmap_main_frame;
|
||||
m_bitmap_main_frame = nullptr;
|
||||
}
|
||||
|
||||
// Load all presets found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
|
||||
{
|
||||
boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred();
|
||||
m_dir_path = dir.string();
|
||||
m_presets.erase(m_presets.begin()+1, m_presets.end());
|
||||
t_config_option_keys keys = this->default_preset().config.keys();
|
||||
std::string errors_cummulative;
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
|
||||
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) {
|
||||
std::string name = dir_entry.path().filename().string();
|
||||
// Remove the .ini suffix.
|
||||
name.erase(name.size() - 4);
|
||||
try {
|
||||
Preset preset(m_type, name, false);
|
||||
preset.file = dir_entry.path().string();
|
||||
preset.load(keys);
|
||||
m_presets.emplace_back(preset);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
errors_cummulative += "\n";
|
||||
}
|
||||
}
|
||||
std::sort(m_presets.begin() + 1, m_presets.end());
|
||||
this->select_preset(first_visible_idx());
|
||||
if (! errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
}
|
||||
|
||||
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
|
||||
// and select it, losing previous modifications.
|
||||
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select)
|
||||
{
|
||||
DynamicPrintConfig cfg(this->default_preset().config);
|
||||
cfg.apply_only(config, cfg.keys(), true);
|
||||
return this->load_preset(path, name, std::move(cfg));
|
||||
}
|
||||
|
||||
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select)
|
||||
{
|
||||
auto it = this->find_preset_internal(name);
|
||||
if (it == m_presets.end() || it->name != name) {
|
||||
// The preset was not found. Create a new preset.
|
||||
it = m_presets.emplace(it, Preset(m_type, name, false));
|
||||
}
|
||||
Preset &preset = *it;
|
||||
preset.file = path;
|
||||
preset.config = std::move(config);
|
||||
preset.loaded = true;
|
||||
preset.is_dirty = false;
|
||||
if (select)
|
||||
this->select_preset_by_name(name, true);
|
||||
return preset;
|
||||
}
|
||||
|
||||
void PresetCollection::save_current_preset(const std::string &new_name)
|
||||
{
|
||||
// 1) Find the preset with a new_name or create a new one,
|
||||
// initialize it with the edited config.
|
||||
auto it = this->find_preset_internal(new_name);
|
||||
if (it != m_presets.end() && it->name == new_name) {
|
||||
// Preset with the same name found.
|
||||
Preset &preset = *it;
|
||||
if (preset.is_default)
|
||||
// Cannot overwrite the default preset.
|
||||
return;
|
||||
// Overwriting an existing preset.
|
||||
preset.config = std::move(m_edited_preset.config);
|
||||
} else {
|
||||
// Creating a new preset.
|
||||
Preset &preset = *m_presets.insert(it, m_edited_preset);
|
||||
std::string file_name = new_name;
|
||||
if (! boost::iends_with(file_name, ".ini"))
|
||||
file_name += ".ini";
|
||||
preset.name = new_name;
|
||||
preset.file = (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string();
|
||||
}
|
||||
// 2) Activate the saved preset.
|
||||
this->select_preset_by_name(new_name, true);
|
||||
// 2) Store the active preset to disk.
|
||||
this->get_selected_preset().save();
|
||||
}
|
||||
|
||||
void PresetCollection::delete_current_preset()
|
||||
{
|
||||
const Preset &selected = this->get_selected_preset();
|
||||
if (selected.is_default)
|
||||
return;
|
||||
if (! selected.is_external) {
|
||||
// Erase the preset file.
|
||||
boost::nowide::remove(selected.file.c_str());
|
||||
}
|
||||
// Remove the preset from the list.
|
||||
m_presets.erase(m_presets.begin() + m_idx_selected);
|
||||
// Find the next visible preset.
|
||||
size_t new_selected_idx = m_idx_selected;
|
||||
if (new_selected_idx < m_presets.size())
|
||||
for (; new_selected_idx < m_presets.size() && ! m_presets[new_selected_idx].is_visible; ++ new_selected_idx) ;
|
||||
if (new_selected_idx == m_presets.size())
|
||||
for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx);
|
||||
this->select_preset(new_selected_idx);
|
||||
}
|
||||
|
||||
bool PresetCollection::load_bitmap_default(const std::string &file_name)
|
||||
{
|
||||
return m_bitmap_main_frame->LoadFile(wxString::FromUTF8(Slic3r::var(file_name).c_str()), wxBITMAP_TYPE_PNG);
|
||||
}
|
||||
|
||||
// Return a preset by its name. If the preset is active, a temporary copy is returned.
|
||||
// If a preset is not found by its name, null is returned.
|
||||
Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found)
|
||||
{
|
||||
Preset key(m_type, name, false);
|
||||
auto it = this->find_preset_internal(name);
|
||||
// Ensure that a temporary copy is returned if the preset found is currently selected.
|
||||
return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) :
|
||||
first_visible_if_not_found ? &this->first_visible() : nullptr;
|
||||
}
|
||||
|
||||
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
|
||||
size_t PresetCollection::first_visible_idx() const
|
||||
{
|
||||
size_t idx = m_default_suppressed ? 1 : 0;
|
||||
for (; idx < this->m_presets.size(); ++ idx)
|
||||
if (m_presets[idx].is_visible)
|
||||
break;
|
||||
if (idx == this->m_presets.size())
|
||||
idx = 0;
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
|
||||
size_t PresetCollection::first_compatible_idx() const
|
||||
{
|
||||
size_t idx = m_default_suppressed ? 1 : 0;
|
||||
for (; idx < this->m_presets.size(); ++ idx)
|
||||
if (m_presets[idx].is_compatible)
|
||||
break;
|
||||
if (idx == this->m_presets.size())
|
||||
idx = 0;
|
||||
return idx;
|
||||
}
|
||||
|
||||
void PresetCollection::set_default_suppressed(bool default_suppressed)
|
||||
{
|
||||
if (m_default_suppressed != default_suppressed) {
|
||||
m_default_suppressed = default_suppressed;
|
||||
m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > 1 && m_idx_selected > 0);
|
||||
}
|
||||
}
|
||||
|
||||
void PresetCollection::update_compatible_with_printer(const std::string &active_printer, bool select_other_if_incompatible)
|
||||
{
|
||||
size_t num_visible = 0;
|
||||
for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) {
|
||||
bool selected = idx_preset == m_idx_selected;
|
||||
Preset &preset_selected = m_presets[idx_preset];
|
||||
Preset &preset_edited = selected ? m_edited_preset : preset_selected;
|
||||
if (preset_edited.update_compatible_with_printer(active_printer))
|
||||
// Mark compatible presets as visible.
|
||||
preset_selected.is_visible = true;
|
||||
else if (selected && select_other_if_incompatible) {
|
||||
preset_selected.is_visible = false;
|
||||
m_idx_selected = (size_t)-1;
|
||||
}
|
||||
if (selected)
|
||||
preset_selected.is_compatible = preset_edited.is_compatible;
|
||||
if (preset_selected.is_visible)
|
||||
++ num_visible;
|
||||
}
|
||||
if (m_idx_selected == (size_t)-1)
|
||||
// Find some other visible preset.
|
||||
this->select_preset(first_visible_idx());
|
||||
else if (num_visible == 0)
|
||||
// Show the "-- default --" preset.
|
||||
m_presets.front().is_visible = 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 PresetCollection::save_current_preset(const std::string &new_name);
|
||||
|
||||
// Delete the current preset, activate the first visible preset.
|
||||
//void PresetCollection::delete_current_preset();
|
||||
|
||||
// Update the wxChoice UI component from this list of presets.
|
||||
// Hide the
|
||||
void PresetCollection::update_platter_ui(wxBitmapComboBox *ui)
|
||||
{
|
||||
if (ui == nullptr)
|
||||
return;
|
||||
// Otherwise fill in the list from scratch.
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) {
|
||||
const Preset &preset = this->m_presets[i];
|
||||
if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected))
|
||||
continue;
|
||||
const wxBitmap *bmp = (i == 0 || preset.is_compatible) ? m_bitmap_main_frame : m_bitmap_incompatible;
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
|
||||
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
|
||||
if (i == m_idx_selected)
|
||||
ui->SetSelection(ui->GetCount() - 1);
|
||||
}
|
||||
ui->Thaw();
|
||||
}
|
||||
|
||||
void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible)
|
||||
{
|
||||
if (ui == nullptr)
|
||||
return;
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) {
|
||||
const Preset &preset = this->m_presets[i];
|
||||
if (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)
|
||||
continue;
|
||||
const wxBitmap *bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible;
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
|
||||
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
|
||||
if (i == m_idx_selected)
|
||||
ui->SetSelection(ui->GetCount() - 1);
|
||||
}
|
||||
ui->Thaw();
|
||||
}
|
||||
|
||||
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
|
||||
// Return true if the dirty flag changed.
|
||||
bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui)
|
||||
{
|
||||
wxWindowUpdateLocker noUpdates(ui);
|
||||
// 1) Update the dirty flag of the current preset.
|
||||
bool was_dirty = this->get_selected_preset().is_dirty;
|
||||
bool is_dirty = current_is_dirty();
|
||||
this->get_selected_preset().is_dirty = is_dirty;
|
||||
this->get_edited_preset().is_dirty = is_dirty;
|
||||
// 2) Update the labels.
|
||||
for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) {
|
||||
std::string old_label = ui->GetString(ui_id).utf8_str().data();
|
||||
std::string preset_name = Preset::remove_suffix_modified(old_label);
|
||||
const Preset *preset = this->find_preset(preset_name, false);
|
||||
assert(preset != nullptr);
|
||||
std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name;
|
||||
if (old_label != new_label)
|
||||
ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str()));
|
||||
}
|
||||
return was_dirty != is_dirty;
|
||||
}
|
||||
|
||||
Preset& PresetCollection::select_preset(size_t idx)
|
||||
{
|
||||
for (Preset &preset : m_presets)
|
||||
preset.is_dirty = false;
|
||||
if (idx >= m_presets.size())
|
||||
idx = first_visible_idx();
|
||||
m_idx_selected = idx;
|
||||
m_edited_preset = m_presets[idx];
|
||||
m_presets.front().is_visible = ! m_default_suppressed || m_idx_selected == 0;
|
||||
return m_presets[idx];
|
||||
}
|
||||
|
||||
bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force)
|
||||
{
|
||||
std::string name = Preset::remove_suffix_modified(name_w_suffix);
|
||||
// 1) Try to find the preset by its name.
|
||||
auto it = this->find_preset_internal(name);
|
||||
size_t idx = 0;
|
||||
if (it != m_presets.end() && it->name == name)
|
||||
// Preset found by its name.
|
||||
idx = it - m_presets.begin();
|
||||
else {
|
||||
// Find the first visible preset.
|
||||
for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i)
|
||||
if (m_presets[i].is_visible) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
// If the first visible preset was not found, return the 0th element, which is the default preset.
|
||||
}
|
||||
|
||||
// 2) Select the new preset.
|
||||
if (m_idx_selected != idx || force) {
|
||||
this->select_preset(idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string PresetCollection::name() const
|
||||
{
|
||||
switch (this->type()) {
|
||||
case Preset::TYPE_PRINT: return "print";
|
||||
case Preset::TYPE_FILAMENT: return "filament";
|
||||
case Preset::TYPE_PRINTER: return "printer";
|
||||
default: return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -1,14 +1,28 @@
|
|||
#ifndef slic3r_Preset_hpp_
|
||||
#define slic3r_Preset_hpp_
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/PrintConfig.hpp"
|
||||
|
||||
class wxBitmap;
|
||||
class wxChoice;
|
||||
class wxBitmapComboBox;
|
||||
class wxItemContainer;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
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 Preset
|
||||
{
|
||||
public:
|
||||
|
@ -35,6 +49,8 @@ public:
|
|||
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;
|
||||
|
@ -53,10 +69,41 @@ public:
|
|||
// Throws std::runtime_error in case the file cannot be read.
|
||||
DynamicPrintConfig& load(const std::vector<std::string> &keys);
|
||||
|
||||
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 enable_compatible(const std::string &active_printer);
|
||||
|
||||
bool is_compatible_with_printer(const std::string &active_printer) const;
|
||||
// Mark this preset as compatible if it is compatible with active_printer.
|
||||
bool update_compatible_with_printer(const std::string &active_printer);
|
||||
|
||||
// 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();
|
||||
|
||||
protected:
|
||||
friend class PresetCollection;
|
||||
friend class PresetBundle;
|
||||
static void normalize(DynamicPrintConfig &config);
|
||||
// Resize the extruder specific vectors ()
|
||||
static void set_num_extruders(DynamicPrintConfig &config, unsigned int n);
|
||||
static const std::string& suffix_modified();
|
||||
static std::string remove_suffix_modified(const std::string &name);
|
||||
};
|
||||
|
||||
// Collections of presets of the same type (one of the Print, Filament or Printer type).
|
||||
|
@ -65,10 +112,31 @@ class PresetCollection
|
|||
public:
|
||||
// Initialize the PresetCollection with the "- default -" preset.
|
||||
PresetCollection(Preset::Type type, const std::vector<std::string> &keys);
|
||||
~PresetCollection();
|
||||
|
||||
Preset::Type type() const { return m_type; }
|
||||
std::string name() const;
|
||||
const std::deque<Preset>& operator()() const { return m_presets; }
|
||||
|
||||
// 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);
|
||||
|
||||
// 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; }
|
||||
|
@ -82,70 +150,101 @@ public:
|
|||
// 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; }
|
||||
// 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; }
|
||||
// Return a preset possibly with modifications.
|
||||
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_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;
|
||||
size_t first_compatible_idx() const;
|
||||
// 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()); }
|
||||
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 this->m_presets.size(); }
|
||||
|
||||
// For Print / Filament presets, disable those, which are not compatible with the printer.
|
||||
void enable_disable_compatible_to_printer(const std::string &active_printer);
|
||||
void update_compatible_with_printer(const std::string &active_printer, bool select_other_if_incompatible);
|
||||
|
||||
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
|
||||
void delete_preset(const size_t idx);
|
||||
|
||||
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
|
||||
bool current_is_dirty() { 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() { return this->get_selected_preset().config.diff(this->get_edited_preset().config); }
|
||||
|
||||
// Update the choice UI from the list of presets.
|
||||
void update_editor_ui(wxBitmapComboBox *ui);
|
||||
// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
|
||||
// If an incompatible preset is selected, it is shown as well.
|
||||
void update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible);
|
||||
// Update the choice UI from the list of presets.
|
||||
// Only the compatible presets are shown.
|
||||
// If an incompatible preset is selected, it is shown as well.
|
||||
void update_platter_ui(wxBitmapComboBox *ui);
|
||||
|
||||
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
|
||||
// Return true if the dirty flag changed.
|
||||
bool update_dirty_ui(wxBitmapComboBox *ui);
|
||||
|
||||
// Select a profile by its name. Return true if the selection changed.
|
||||
// Without force, the selection is only updated if the index changes.
|
||||
// With force, the changes are reverted if the new index is the same as the old index.
|
||||
bool select_preset_by_name(const std::string &name, bool force);
|
||||
|
||||
private:
|
||||
PresetCollection();
|
||||
PresetCollection(const PresetCollection &other);
|
||||
PresetCollection& operator=(const PresetCollection &other);
|
||||
|
||||
// Find a preset in the sorted list of presets.
|
||||
// The "-- default -- " preset is always the first, so it needs
|
||||
// to be handled differently.
|
||||
std::deque<Preset>::iterator find_preset_internal(const std::string &name)
|
||||
{
|
||||
Preset key(m_type, name);
|
||||
auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key);
|
||||
return (it == m_presets.end() && m_presets.front().name == name) ? m_presets.begin() : it;
|
||||
}
|
||||
std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const
|
||||
{ return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
|
||||
|
||||
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
|
||||
Preset::Type m_type;
|
||||
// List of presets, starting with the "- default -" preset.
|
||||
std::vector<Preset> m_presets;
|
||||
// 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;
|
||||
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
|
||||
// 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;
|
||||
};
|
||||
|
||||
// Bundle of Print + Filament + Printer presets.
|
||||
class PresetBundle
|
||||
{
|
||||
public:
|
||||
PresetBundle();
|
||||
~PresetBundle();
|
||||
|
||||
bool load_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible);
|
||||
|
||||
// Load ini files of all types (print, filament, printer) from the provided directory path.
|
||||
void load_presets(const std::string &dir_path);
|
||||
|
||||
PresetCollection prints;
|
||||
PresetCollection filaments;
|
||||
PresetCollection printers;
|
||||
|
||||
// Update the colors preview at the platter extruder combo box.
|
||||
void update_platter_filament_ui_colors(wxBitmapComboBox *ui, unsigned int idx_extruder, unsigned int idx_filament);
|
||||
|
||||
static const std::vector<std::string>& print_options();
|
||||
static const std::vector<std::string>& filament_options();
|
||||
static const std::vector<std::string>& printer_options();
|
||||
|
||||
private:
|
||||
// Indicator, that the preset is compatible with the selected printer.
|
||||
wxBitmap *m_bitmapCompatible;
|
||||
// Indicator, that the preset is NOT compatible with the selected printer.
|
||||
wxBitmap *m_bitmapIncompatible;
|
||||
// Caching color bitmaps for the
|
||||
std::map<std::string, wxBitmap*> m_mapColorToBitmap;
|
||||
// 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;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
693
xs/src/slic3r/GUI/PresetBundle.cpp
Normal file
693
xs/src/slic3r/GUI/PresetBundle.cpp
Normal file
|
@ -0,0 +1,693 @@
|
|||
//#undef NDEBUG
|
||||
#include <cassert>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/algorithm/clamp.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <boost/nowide/cenv.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 <boost/locale.hpp>
|
||||
|
||||
#include <wx/dcmemory.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/PlaceholderParser.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
PresetBundle::PresetBundle() :
|
||||
prints(Preset::TYPE_PRINT, Preset::print_options()),
|
||||
filaments(Preset::TYPE_FILAMENT, Preset::filament_options()),
|
||||
printers(Preset::TYPE_PRINTER, Preset::printer_options()),
|
||||
m_bitmapCompatible(new wxBitmap),
|
||||
m_bitmapIncompatible(new wxBitmap)
|
||||
{
|
||||
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
|
||||
wxImage::AddHandler(new wxPNGHandler);
|
||||
|
||||
// Create the ID config keys, as they are not part of the Static print config classes.
|
||||
this->prints.preset(0).config.opt_string("print_settings_id", true);
|
||||
this->filaments.preset(0).config.opt_string("filament_settings_id", true);
|
||||
this->printers.preset(0).config.opt_string("print_settings_id", true);
|
||||
// Create the "compatible printers" keys, as they are not part of the Static print config classes.
|
||||
this->filaments.preset(0).config.optptr("compatible_printers", true);
|
||||
this->prints.preset(0).config.optptr("compatible_printers", true);
|
||||
|
||||
this->prints .load_bitmap_default("cog.png");
|
||||
this->filaments.load_bitmap_default("spool.png");
|
||||
this->printers .load_bitmap_default("printer_empty.png");
|
||||
this->load_compatible_bitmaps();
|
||||
}
|
||||
|
||||
PresetBundle::~PresetBundle()
|
||||
{
|
||||
assert(m_bitmapCompatible != nullptr);
|
||||
assert(m_bitmapIncompatible != nullptr);
|
||||
delete m_bitmapCompatible;
|
||||
m_bitmapCompatible = nullptr;
|
||||
delete m_bitmapIncompatible;
|
||||
m_bitmapIncompatible = nullptr;
|
||||
for (std::pair<const std::string, wxBitmap*> &bitmap : m_mapColorToBitmap)
|
||||
delete bitmap.second;
|
||||
}
|
||||
|
||||
void PresetBundle::setup_directories()
|
||||
{
|
||||
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||
std::initializer_list<boost::filesystem::path> paths = {
|
||||
data_dir,
|
||||
data_dir / "presets",
|
||||
data_dir / "presets" / "print",
|
||||
data_dir / "presets" / "filament",
|
||||
data_dir / "presets" / "printer" };
|
||||
for (const boost::filesystem::path &path : paths) {
|
||||
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 its data directory at ") + subdir.string());
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::load_presets()
|
||||
{
|
||||
std::string errors_cummulative;
|
||||
const std::string dir_path = data_dir() + "/presets";
|
||||
try {
|
||||
this->prints.load_presets(dir_path, "print");
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->filaments.load_presets(dir_path, "filament");
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->printers.load_presets(dir_path, "printer");
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
this->update_multi_material_filament_presets();
|
||||
if (! errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
}
|
||||
|
||||
static inline std::string remove_ini_suffix(const std::string &name)
|
||||
{
|
||||
std::string out = name;
|
||||
if (boost::iends_with(out, ".ini"))
|
||||
out.erase(out.end() - 4, out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
void PresetBundle::load_selections(const AppConfig &config)
|
||||
{
|
||||
prints.select_preset_by_name(remove_ini_suffix(config.get("presets", "print")), true);
|
||||
filaments.select_preset_by_name(remove_ini_suffix(config.get("presets", "filament")), true);
|
||||
printers.select_preset_by_name(remove_ini_suffix(config.get("presets", "printer")), true);
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printers.get_selected_preset().config.option("nozzle_diameter"));
|
||||
size_t num_extruders = nozzle_diameter->values.size();
|
||||
this->set_filament_preset(0, filaments.get_selected_preset().name);
|
||||
for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) {
|
||||
char name[64];
|
||||
sprintf(name, "filament_%d", i);
|
||||
if (! config.has("presets", name))
|
||||
break;
|
||||
this->set_filament_preset(i, remove_ini_suffix(config.get("presets", name)));
|
||||
}
|
||||
// Update visibility of presets based on their compatibility with the active printer.
|
||||
// This will switch the print or filament presets to compatible if the active presets are incompatible.
|
||||
this->update_compatible_with_printer(false);
|
||||
}
|
||||
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
void PresetBundle::export_selections(AppConfig &config)
|
||||
{
|
||||
assert(filament_presets.size() >= 1);
|
||||
assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front());
|
||||
config.clear_section("presets");
|
||||
config.set("presets", "print", prints.get_selected_preset().name);
|
||||
config.set("presets", "filament", filament_presets.front());
|
||||
for (int i = 1; i < filament_presets.size(); ++i) {
|
||||
char name[64];
|
||||
sprintf(name, "filament_%d", i);
|
||||
config.set("presets", name, filament_presets[i]);
|
||||
}
|
||||
config.set("presets", "printer", printers.get_selected_preset().name);
|
||||
}
|
||||
|
||||
void PresetBundle::export_selections(PlaceholderParser &pp)
|
||||
{
|
||||
assert(filament_presets.size() >= 1);
|
||||
assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front());
|
||||
pp.set("print_preset", prints.get_selected_preset().name);
|
||||
pp.set("filament_preset", filament_presets);
|
||||
pp.set("printer_preset", printers.get_selected_preset().name);
|
||||
}
|
||||
|
||||
bool PresetBundle::load_compatible_bitmaps()
|
||||
{
|
||||
const std::string path_bitmap_compatible = "flag-green-icon.png";
|
||||
const std::string path_bitmap_incompatible = "flag-red-icon.png";
|
||||
bool loaded_compatible = m_bitmapCompatible ->LoadFile(
|
||||
wxString::FromUTF8(Slic3r::var(path_bitmap_compatible).c_str()), wxBITMAP_TYPE_PNG);
|
||||
bool loaded_incompatible = m_bitmapIncompatible->LoadFile(
|
||||
wxString::FromUTF8(Slic3r::var(path_bitmap_incompatible).c_str()), wxBITMAP_TYPE_PNG);
|
||||
if (loaded_compatible) {
|
||||
prints .set_bitmap_compatible(m_bitmapCompatible);
|
||||
filaments.set_bitmap_compatible(m_bitmapCompatible);
|
||||
// printers .set_bitmap_compatible(m_bitmapCompatible);
|
||||
}
|
||||
if (loaded_incompatible) {
|
||||
prints .set_bitmap_incompatible(m_bitmapIncompatible);
|
||||
filaments.set_bitmap_incompatible(m_bitmapIncompatible);
|
||||
// printers .set_bitmap_incompatible(m_bitmapIncompatible);
|
||||
}
|
||||
return loaded_compatible && loaded_incompatible;
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_config() const
|
||||
{
|
||||
DynamicPrintConfig out;
|
||||
out.apply(FullPrintConfig());
|
||||
out.apply(this->prints.get_edited_preset().config);
|
||||
out.apply(this->printers.get_edited_preset().config);
|
||||
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(out.option("nozzle_diameter"));
|
||||
size_t num_extruders = nozzle_diameter->values.size();
|
||||
|
||||
if (num_extruders <= 1) {
|
||||
out.apply(this->filaments.get_edited_preset().config);
|
||||
} else {
|
||||
// Retrieve filament presets and build a single config object for them.
|
||||
// First collect the filament configurations based on the user selection of this->filament_presets.
|
||||
std::vector<const DynamicPrintConfig*> filament_configs;
|
||||
for (const std::string &filament_preset_name : this->filament_presets)
|
||||
filament_configs.emplace_back(&this->filaments.find_preset(filament_preset_name, true)->config);
|
||||
while (filament_configs.size() < num_extruders)
|
||||
filament_configs.emplace_back(&this->filaments.first_visible().config);
|
||||
// Option values to set a ConfigOptionVector from.
|
||||
std::vector<const ConfigOption*> filament_opts(num_extruders, nullptr);
|
||||
// loop through options and apply them to the resulting config.
|
||||
for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) {
|
||||
if (key == "compatible_printers")
|
||||
continue;
|
||||
// Get a destination option.
|
||||
ConfigOption *opt_dst = out.option(key, false);
|
||||
if (opt_dst->is_scalar()) {
|
||||
// Get an option, do not create if it does not exist.
|
||||
const ConfigOption *opt_src = filament_configs.front()->option(key);
|
||||
if (opt_src != nullptr)
|
||||
opt_dst->set(opt_src);
|
||||
} else {
|
||||
// Setting a vector value from all filament_configs.
|
||||
for (size_t i = 0; i < filament_opts.size(); ++ i)
|
||||
filament_opts[i] = filament_configs[i]->option(key);
|
||||
static_cast<ConfigOptionVectorBase*>(opt_dst)->set(filament_opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.erase("compatible_printers");
|
||||
|
||||
static const char *keys[] = { "perimeter", "infill", "solid_infill", "support_material", "support_material_interface" };
|
||||
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++ i) {
|
||||
std::string key = std::string(keys[i]) + "_extruder";
|
||||
auto *opt = dynamic_cast<ConfigOptionInt*>(out.option(key, false));
|
||||
assert(opt != nullptr);
|
||||
opt->value = boost::algorithm::clamp<int>(opt->value, 0, int(num_extruders));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// 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 PresetBundle::load_config_file(const std::string &path)
|
||||
{
|
||||
if (boost::iends_with(path, ".gcode") || boost::iends_with(path, ".g")) {
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load_from_gcode(path);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, std::move(config));
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) Try to load the config file into a boost property tree.
|
||||
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 std::runtime_error(std::string("The config file cannot be loaded: ") + path + "\n\tReason: " + err.what());
|
||||
} catch (const std::runtime_error &err) {
|
||||
throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
|
||||
}
|
||||
|
||||
// 2) Continue based on the type of the configuration file.
|
||||
ConfigFileType config_file_type = guess_config_file_type(tree);
|
||||
switch (config_file_type) {
|
||||
case CONFIG_FILE_TYPE_UNKNOWN:
|
||||
throw std::runtime_error(std::string("Unknown configuration file type: ") + path);
|
||||
case CONFIG_FILE_TYPE_APP_CONFIG:
|
||||
throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
|
||||
case CONFIG_FILE_TYPE_CONFIG:
|
||||
{
|
||||
// Initialize a config from full defaults.
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load(tree);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, std::move(config));
|
||||
break;
|
||||
}
|
||||
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
|
||||
load_config_file_config_bundle(path, tree);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Load a config file from a boost property_tree. This is a private method called from load_config_file.
|
||||
void PresetBundle::load_config_file_config(const std::string &path, DynamicPrintConfig &&config)
|
||||
{
|
||||
// The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway,
|
||||
// but some of the alpha versions of Slic3r did.
|
||||
{
|
||||
ConfigOption *opt_compatible = config.optptr("compatible_printers");
|
||||
if (opt_compatible != nullptr) {
|
||||
assert(opt_compatible->type() == coStrings);
|
||||
if (opt_compatible->type() == coStrings)
|
||||
static_cast<ConfigOptionStrings*>(opt_compatible)->values.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Create a name from the file name.
|
||||
// Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles.
|
||||
std::string name = boost::filesystem::path(path).filename().string();
|
||||
|
||||
// 2) If the loading succeeded, split and load the config into print / filament / printer settings.
|
||||
// First load the print and printer presets.
|
||||
for (size_t i_group = 0; i_group < 2; ++ i_group) {
|
||||
PresetCollection &presets = (i_group == 0) ? this->prints : this->printers;
|
||||
presets.load_preset(path, name, config).is_external = true;
|
||||
}
|
||||
|
||||
// 3) Now load the filaments. If there are multiple filament presets, split them and load them.
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
|
||||
auto *filament_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("filament_diameter"));
|
||||
size_t num_extruders = std::min(nozzle_diameter->values.size(), filament_diameter->values.size());
|
||||
if (num_extruders <= 1) {
|
||||
this->filaments.load_preset(path, name, config).is_external = true;
|
||||
this->filament_presets.clear();
|
||||
this->filament_presets.emplace_back(name);
|
||||
} else {
|
||||
// Split the filament presets, load each of them separately.
|
||||
std::vector<DynamicPrintConfig> configs(num_extruders, this->filaments.default_preset().config);
|
||||
// loop through options and scatter them into configs.
|
||||
for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) {
|
||||
const ConfigOption *other_opt = config.option(key);
|
||||
if (other_opt == nullptr)
|
||||
continue;
|
||||
if (other_opt->is_scalar()) {
|
||||
for (size_t i = 0; i < configs.size(); ++ i)
|
||||
configs[i].option(key, false)->set(other_opt);
|
||||
} else if (key != "compatible_printers") {
|
||||
for (size_t i = 0; i < configs.size(); ++ i)
|
||||
static_cast<ConfigOptionVectorBase*>(configs[i].option(key, false))->set_at(other_opt, 0, i);
|
||||
}
|
||||
}
|
||||
// Load the configs into this->filaments and make them active.
|
||||
this->filament_presets.clear();
|
||||
for (size_t i = 0; i < configs.size(); ++ i) {
|
||||
char suffix[64];
|
||||
if (i == 0)
|
||||
suffix[0] = 0;
|
||||
else
|
||||
sprintf(suffix, " (%d)", i);
|
||||
// Load all filament presets, but only select the first one in the preset dialog.
|
||||
this->filaments.load_preset(path, name + suffix, std::move(configs[i]), i == 0).is_external = true;
|
||||
this->filament_presets.emplace_back(name + suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
|
||||
void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
|
||||
{
|
||||
// 1) Load the config bundle into a temp data.
|
||||
PresetBundle tmp_bundle;
|
||||
tmp_bundle.load_configbundle(path);
|
||||
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
|
||||
|
||||
// 2) Extract active configs from the config bundle, copy them and activate them in this bundle.
|
||||
auto load_one = [this, &path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
|
||||
Preset *preset_src = collection_src.find_preset(preset_name_src, false);
|
||||
Preset *preset_dst = collection_dst.find_preset(preset_name_src, false);
|
||||
assert(preset_src != nullptr);
|
||||
std::string preset_name_dst;
|
||||
if (preset_dst != nullptr && preset_dst->is_default) {
|
||||
// No need to copy a default preset, it always exists in collection_dst.
|
||||
if (activate)
|
||||
collection_dst.select_preset(0);
|
||||
return preset_name_src;
|
||||
} else if (preset_dst != nullptr && preset_src->config == preset_dst->config) {
|
||||
// Don't save as the config exists in the current bundle and its content is the same.
|
||||
return preset_name_src;
|
||||
} else {
|
||||
// Generate a new unique name.
|
||||
preset_name_dst = preset_name_src + bundle_name;
|
||||
Preset *preset_dup = nullptr;
|
||||
for (size_t i = 1; (preset_dup = collection_dst.find_preset(preset_name_dst, false)) != nullptr; ++ i) {
|
||||
if (preset_src->config == preset_dup->config)
|
||||
// The preset has been already copied into collection_dst.
|
||||
return preset_name_dst;
|
||||
// Try to generate another name.
|
||||
char buf[64];
|
||||
sprintf(buf, " (%d)", i);
|
||||
preset_name_dst = preset_name_src + buf + bundle_name;
|
||||
}
|
||||
}
|
||||
assert(! preset_name_dst.empty());
|
||||
// Save preset_src->config into collection_dst under preset_name_dst.
|
||||
// The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway,
|
||||
// but some of the alpha versions of Slic3r did.
|
||||
ConfigOption *opt_compatible = preset_src->config.optptr("compatible_printers");
|
||||
if (opt_compatible != nullptr) {
|
||||
assert(opt_compatible->type() == coStrings);
|
||||
if (opt_compatible->type() == coStrings)
|
||||
static_cast<ConfigOptionStrings*>(opt_compatible)->values.clear();
|
||||
}
|
||||
collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true;
|
||||
return preset_name_dst;
|
||||
};
|
||||
load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true);
|
||||
load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments.get_selected_preset().name, true);
|
||||
load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true);
|
||||
this->update_multi_material_filament_presets();
|
||||
for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i)
|
||||
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
|
||||
}
|
||||
|
||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||
// of the local configuration directory.
|
||||
size_t PresetBundle::load_configbundle(const std::string &path)
|
||||
{
|
||||
// 1) Read the complete config file into a boost::property_tree.
|
||||
namespace pt = boost::property_tree;
|
||||
pt::ptree tree;
|
||||
boost::nowide::ifstream ifs(path);
|
||||
pt::read_ini(ifs, tree);
|
||||
|
||||
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
|
||||
std::vector<std::string> loaded_prints;
|
||||
std::vector<std::string> loaded_filaments;
|
||||
std::vector<std::string> loaded_printers;
|
||||
std::string active_print;
|
||||
std::vector<std::string> active_filaments;
|
||||
std::string active_printer;
|
||||
size_t presets_loaded = 0;
|
||||
for (const auto §ion : tree) {
|
||||
PresetCollection *presets = nullptr;
|
||||
std::vector<std::string> *loaded = nullptr;
|
||||
std::string preset_name;
|
||||
if (boost::starts_with(section.first, "print:")) {
|
||||
presets = &prints;
|
||||
loaded = &loaded_prints;
|
||||
preset_name = section.first.substr(6);
|
||||
} else if (boost::starts_with(section.first, "filament:")) {
|
||||
presets = &filaments;
|
||||
loaded = &loaded_filaments;
|
||||
preset_name = section.first.substr(9);
|
||||
} else if (boost::starts_with(section.first, "printer:")) {
|
||||
presets = &printers;
|
||||
loaded = &loaded_printers;
|
||||
preset_name = section.first.substr(8);
|
||||
} else if (section.first == "presets") {
|
||||
// Load the names of the active presets.
|
||||
for (auto &kvp : section.second) {
|
||||
if (kvp.first == "print") {
|
||||
active_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(active_filaments.size()) <= idx)
|
||||
active_filaments.resize(idx + 1, std::string());
|
||||
active_filaments[idx] = kvp.second.data();
|
||||
}
|
||||
} else if (kvp.first == "printer") {
|
||||
active_printer = kvp.second.data();
|
||||
}
|
||||
}
|
||||
} else if (section.first == "settings") {
|
||||
// Load the settings.
|
||||
for (auto &kvp : section.second) {
|
||||
if (kvp.first == "autocenter") {
|
||||
}
|
||||
}
|
||||
} else
|
||||
// Ignore an unknown section.
|
||||
continue;
|
||||
if (presets != nullptr) {
|
||||
// Load the print, filament or printer preset.
|
||||
DynamicPrintConfig config(presets->default_preset().config);
|
||||
for (auto &kvp : section.second)
|
||||
config.set_deserialize(kvp.first, kvp.second.data());
|
||||
Preset::normalize(config);
|
||||
// Decide a full path to this .ini file.
|
||||
auto file_name = boost::algorithm::iends_with(preset_name, ".ini") ? preset_name : preset_name + ".ini";
|
||||
auto file_path = (boost::filesystem::path(data_dir()) / "presets" / presets->name() / file_name).make_preferred();
|
||||
// Load the preset into the list of presets, save it to disk.
|
||||
presets->load_preset(file_path.string(), preset_name, std::move(config), false).save();
|
||||
++ presets_loaded;
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Activate the presets.
|
||||
if (! active_print.empty())
|
||||
prints.select_preset_by_name(active_print, true);
|
||||
if (! active_printer.empty())
|
||||
printers.select_preset_by_name(active_printer, true);
|
||||
// Activate the first filament preset.
|
||||
if (! active_filaments.empty() && ! active_filaments.front().empty())
|
||||
filaments.select_preset_by_name(active_filaments.front(), true);
|
||||
|
||||
this->update_multi_material_filament_presets();
|
||||
for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i)
|
||||
this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name;
|
||||
return presets_loaded;
|
||||
}
|
||||
|
||||
void PresetBundle::update_multi_material_filament_presets()
|
||||
{
|
||||
// Verify and select the filament presets.
|
||||
auto *nozzle_diameter = static_cast<const ConfigOptionFloats*>(printers.get_edited_preset().config.option("nozzle_diameter"));
|
||||
size_t num_extruders = nozzle_diameter->values.size();
|
||||
// Verify validity of the current filament presets.
|
||||
for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i)
|
||||
this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name;
|
||||
// Append the rest of filament presets.
|
||||
// if (this->filament_presets.size() < num_extruders)
|
||||
this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back());
|
||||
}
|
||||
|
||||
void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible)
|
||||
{
|
||||
this->prints.update_compatible_with_printer(this->printers.get_selected_preset().name, select_other_if_incompatible);
|
||||
this->filaments.update_compatible_with_printer(this->printers.get_selected_preset().name, select_other_if_incompatible);
|
||||
if (select_other_if_incompatible) {
|
||||
// Verify validity of the current filament presets.
|
||||
for (std::string &filament_name : this->filament_presets) {
|
||||
Preset *preset = this->filaments.find_preset(filament_name, false);
|
||||
if (preset == nullptr || ! preset->is_compatible)
|
||||
filament_name = this->filaments.first_compatible().name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings
|
||||
{
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path, std::ios::out | std::ios::trunc);
|
||||
|
||||
// Put a comment at the first line including the time stamp and Slic3r version.
|
||||
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
|
||||
// Export the print, filament and printer profiles.
|
||||
for (size_t i_group = 0; i_group < 3; ++ i_group) {
|
||||
const PresetCollection &presets = (i_group == 0) ? this->prints : (i_group == 1) ? this->filaments : this->printers;
|
||||
for (const Preset &preset : presets()) {
|
||||
if (preset.is_default || preset.is_external)
|
||||
// Only export the common presets, not external files or the default preset.
|
||||
continue;
|
||||
c << std::endl << "[" << presets.name() << ":" << preset.name << "]" << std::endl;
|
||||
for (const std::string &opt_key : preset.config.keys())
|
||||
c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Export the names of the active presets.
|
||||
c << std::endl << "[presets]" << std::endl;
|
||||
c << "print = " << this->prints.get_selected_preset().name << std::endl;
|
||||
c << "printer = " << this->printers.get_selected_preset().name << std::endl;
|
||||
for (size_t i = 0; i < this->filament_presets.size(); ++ i) {
|
||||
char suffix[64];
|
||||
if (i > 0)
|
||||
sprintf(suffix, "_%d", i);
|
||||
else
|
||||
suffix[0] = 0;
|
||||
c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Export the following setting values from the provided setting repository.
|
||||
static const char *settings_keys[] = { "autocenter" };
|
||||
c << "[settings]" << std::endl;
|
||||
for (size_t i = 0; i < sizeof(settings_keys) / sizeof(settings_keys[0]); ++ i)
|
||||
c << settings_keys[i] << " = " << settings.serialize(settings_keys[i]) << std::endl;
|
||||
#endif
|
||||
|
||||
c.close();
|
||||
}
|
||||
|
||||
// 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 PresetBundle::set_filament_preset(size_t idx, const std::string &name)
|
||||
{
|
||||
if (idx >= filament_presets.size())
|
||||
filament_presets.resize(idx + 1, filaments.default_preset().name);
|
||||
filament_presets[idx] = Preset::remove_suffix_modified(name);
|
||||
}
|
||||
|
||||
static inline int hex_digit_to_int(const char c)
|
||||
{
|
||||
return
|
||||
(c >= '0' && c <= '9') ? int(c - '0') :
|
||||
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
|
||||
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
|
||||
}
|
||||
|
||||
static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out)
|
||||
{
|
||||
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
|
||||
if (scolor.size() != 7 || scolor.front() != '#')
|
||||
return false;
|
||||
const char *c = scolor.data() + 1;
|
||||
for (size_t i = 0; i < 3; ++ i) {
|
||||
int digit1 = hex_digit_to_int(*c ++);
|
||||
int digit2 = hex_digit_to_int(*c ++);
|
||||
if (digit1 == -1 || digit2 == -1)
|
||||
return false;
|
||||
rgb_out[i] = (unsigned char)(digit1 * 16 + digit2);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui)
|
||||
{
|
||||
if (ui == nullptr)
|
||||
return;
|
||||
|
||||
unsigned char rgb[3];
|
||||
std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder);
|
||||
if (! parse_color(extruder_color, rgb))
|
||||
// Extruder color is not defined.
|
||||
extruder_color.clear();
|
||||
|
||||
// Fill in the list from scratch.
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]);
|
||||
// Show wide icons if the currently selected preset is not compatible with the current printer,
|
||||
// and draw a red flag in front of the selected preset.
|
||||
bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr;
|
||||
assert(selected_preset != nullptr);
|
||||
for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++ i) {
|
||||
const Preset &preset = this->filaments.preset(i);
|
||||
bool selected = this->filament_presets[idx_extruder] == preset.name;
|
||||
if (! preset.is_visible || (! preset.is_compatible && ! selected))
|
||||
continue;
|
||||
// Assign an extruder color to the selected item if the extruder color is defined.
|
||||
std::string filament_rgb = preset.config.opt_string("filament_colour", 0);
|
||||
std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb;
|
||||
bool single_bar = filament_rgb == extruder_rgb;
|
||||
std::string bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb;
|
||||
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
|
||||
// to the filament color image.
|
||||
if (wide_icons)
|
||||
bitmap_key += preset.is_compatible ? "comp" : "notcomp";
|
||||
auto it = m_mapColorToBitmap.find(bitmap_key);
|
||||
wxBitmap *bitmap = (it == m_mapColorToBitmap.end()) ? nullptr : it->second;
|
||||
if (bitmap == nullptr) {
|
||||
// Create the bitmap with color bars.
|
||||
bitmap = new wxBitmap((wide_icons ? 16 : 0) + 24, 16);
|
||||
#if defined(__APPLE__) || defined(_MSC_VER)
|
||||
bitmap->UseAlpha();
|
||||
#endif
|
||||
wxMemoryDC memDC;
|
||||
memDC.SelectObject(*bitmap);
|
||||
memDC.SetBackground(*wxTRANSPARENT_BRUSH);
|
||||
memDC.Clear();
|
||||
if (wide_icons && ! preset.is_compatible)
|
||||
// Paint the red flag.
|
||||
memDC.DrawBitmap(*m_bitmapIncompatible, 0, 0, true);
|
||||
// Paint the color bars.
|
||||
parse_color(filament_rgb, rgb);
|
||||
wxImage image(24, 16);
|
||||
image.InitAlpha();
|
||||
unsigned char* imgdata = image.GetData();
|
||||
unsigned char* imgalpha = image.GetAlpha();
|
||||
for (size_t i = 0; i < image.GetWidth() * image.GetHeight(); ++ i) {
|
||||
*imgdata ++ = rgb[0];
|
||||
*imgdata ++ = rgb[1];
|
||||
*imgdata ++ = rgb[2];
|
||||
*imgalpha ++ = wxALPHA_OPAQUE;
|
||||
}
|
||||
if (! single_bar) {
|
||||
parse_color(extruder_rgb, rgb);
|
||||
imgdata = image.GetData();
|
||||
for (size_t r = 0; r < 16; ++ r) {
|
||||
imgdata = image.GetData() + r * image.GetWidth() * 3;
|
||||
for (size_t c = 0; c < 16; ++ c) {
|
||||
*imgdata ++ = rgb[0];
|
||||
*imgdata ++ = rgb[1];
|
||||
*imgdata ++ = rgb[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
memDC.DrawBitmap(wxBitmap(image), wide_icons ? 16 : 0, 0, true);
|
||||
memDC.SelectObject(wxNullBitmap);
|
||||
m_mapColorToBitmap[bitmap_key] = bitmap;
|
||||
}
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap);
|
||||
if (selected)
|
||||
ui->SetSelection(ui->GetCount() - 1);
|
||||
}
|
||||
ui->Thaw();
|
||||
}
|
||||
|
||||
void PresetBundle::set_default_suppressed(bool default_suppressed)
|
||||
{
|
||||
prints.set_default_suppressed(default_suppressed);
|
||||
filaments.set_default_suppressed(default_suppressed);
|
||||
printers.set_default_suppressed(default_suppressed);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
95
xs/src/slic3r/GUI/PresetBundle.hpp
Normal file
95
xs/src/slic3r/GUI/PresetBundle.hpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
#ifndef slic3r_PresetBundle_hpp_
|
||||
#define slic3r_PresetBundle_hpp_
|
||||
|
||||
#include "AppConfig.hpp"
|
||||
#include "Preset.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PlaceholderParser;
|
||||
|
||||
// Bundle of Print + Filament + Printer presets.
|
||||
class PresetBundle
|
||||
{
|
||||
public:
|
||||
PresetBundle();
|
||||
~PresetBundle();
|
||||
|
||||
void setup_directories();
|
||||
|
||||
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
|
||||
void load_presets();
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
void load_selections(const AppConfig &config);
|
||||
// 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 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;
|
||||
|
||||
bool has_defauls_only() const
|
||||
{ return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; }
|
||||
|
||||
DynamicPrintConfig full_config() const;
|
||||
|
||||
// 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 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.
|
||||
size_t load_configbundle(const std::string &path);
|
||||
|
||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||
void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings);
|
||||
|
||||
// Update a filament selection combo box on the platter for an idx_extruder.
|
||||
void update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui);
|
||||
|
||||
// Enable / disable the "- default -" preset.
|
||||
void set_default_suppressed(bool default_suppressed);
|
||||
|
||||
// Set the filament preset name. As the name could come from the UI selection box,
|
||||
// an optional "(modified)" suffix will be removed from the filament name.
|
||||
void set_filament_preset(size_t idx, const std::string &name);
|
||||
|
||||
// Read out the number of extruders from an active printer preset,
|
||||
// update size and content of filament_presets.
|
||||
void update_multi_material_filament_presets();
|
||||
|
||||
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
|
||||
// as compatible with the currently selected printer.
|
||||
// Also updates the is_visible flag of each preset.
|
||||
// If select_other_if_incompatible is true, then the print or filament preset is switched to some compatible
|
||||
// preset if the current print or filament preset is not compatible.
|
||||
void update_compatible_with_printer(bool select_other_if_incompatible);
|
||||
|
||||
private:
|
||||
void load_config_file_config(const std::string &path, DynamicPrintConfig &&config);
|
||||
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
|
||||
bool load_compatible_bitmaps();
|
||||
|
||||
// Indicator, that the preset is compatible with the selected printer.
|
||||
wxBitmap *m_bitmapCompatible;
|
||||
// Indicator, that the preset is NOT compatible with the selected printer.
|
||||
wxBitmap *m_bitmapIncompatible;
|
||||
// Caching color bitmaps for the
|
||||
std::map<std::string, wxBitmap*> m_mapColorToBitmap;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_PresetBundle_hpp_ */
|
229
xs/src/slic3r/GUI/PresetHints.cpp
Normal file
229
xs/src/slic3r/GUI/PresetHints.cpp
Normal file
|
@ -0,0 +1,229 @@
|
|||
//#undef NDEBUG
|
||||
#include <cassert>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
#include "PresetHints.hpp"
|
||||
#include "Flow.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::string PresetHints::cooling_description(const Preset &preset)
|
||||
{
|
||||
std::string out;
|
||||
char buf[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, "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, "\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 += "\nDuring the other layers, fan ";
|
||||
} else {
|
||||
out = "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, "will always run at %d%% ", min_fan_speed);
|
||||
out += buf;
|
||||
if (disable_fan_first_layers > 1) {
|
||||
sprintf(buf, "except for the first %d layers", disable_fan_first_layers);
|
||||
out += buf;
|
||||
}
|
||||
else if (disable_fan_first_layers == 1)
|
||||
out += "except for the first layer";
|
||||
} else
|
||||
out += "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);
|
||||
}
|
||||
|
||||
static double first_positive(double v1, double v2)
|
||||
{
|
||||
return (v1 > 0.) ? v1 : v2;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
// 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;
|
||||
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 :
|
||||
first_positive(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 = "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 :
|
||||
first_positive(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
|
||||
if (max_flow < perimeter_rate) {
|
||||
max_flow = perimeter_rate;
|
||||
max_flow_extrusion_type = "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() * first_positive(infill_speed, max_print_speed);
|
||||
if (max_flow < infill_rate) {
|
||||
max_flow = infill_rate;
|
||||
max_flow_extrusion_type = "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 : first_positive(solid_infill_speed, max_print_speed));
|
||||
if (max_flow < solid_infill_rate) {
|
||||
max_flow = solid_infill_rate;
|
||||
max_flow_extrusion_type = "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() * first_positive(top_solid_infill_speed, max_print_speed);
|
||||
if (max_flow < top_solid_infill_rate) {
|
||||
max_flow = top_solid_infill_rate;
|
||||
max_flow_extrusion_type = "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 : first_positive(support_material_speed, max_print_speed));
|
||||
if (max_flow < support_material_rate) {
|
||||
max_flow = support_material_rate;
|
||||
max_flow_extrusion_type = "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 : first_positive(support_material_interface_speed, max_print_speed));
|
||||
if (max_flow < support_material_interface_rate) {
|
||||
max_flow = support_material_interface_rate;
|
||||
max_flow_extrusion_type = "support interface";
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME handle gap_fill_speed
|
||||
if (! out.empty())
|
||||
out += "\n";
|
||||
out += (first_layer ? "First layer volumetric" : (bridging ? "Bridging volumetric" : "Volumetric"));
|
||||
out += " flow rate is maximized ";
|
||||
out += ((max_volumetric_speed > 0 && max_volumetric_speed < max_flow) ?
|
||||
"by the print profile maximum" :
|
||||
("when printing " + max_flow_extrusion_type))
|
||||
+ " with a volumetric rate ";
|
||||
if (max_volumetric_speed > 0 && max_volumetric_speed < max_flow)
|
||||
max_flow = max_volumetric_speed;
|
||||
char buf[2048];
|
||||
sprintf(buf, "%3.2f mm³/s", max_flow);
|
||||
out += buf;
|
||||
sprintf(buf, " at filament speed %3.2f mm/s.", max_flow / filament_crossection);
|
||||
out += buf;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
25
xs/src/slic3r/GUI/PresetHints.hpp
Normal file
25
xs/src/slic3r/GUI/PresetHints.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#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);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_PresetHints_hpp_ */
|
|
@ -54,6 +54,8 @@ extern "C" {
|
|||
#ifdef _MSC_VER
|
||||
// Undef some of the macros set by Perl <xsinit.h>, which cause compilation errors on Win32
|
||||
#undef connect
|
||||
#undef link
|
||||
#undef unlink
|
||||
#undef seek
|
||||
#undef send
|
||||
#undef write
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue