Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVojtech Bubnik <bubnikv@gmail.com>2021-05-24 15:10:04 +0300
committerVojtech Bubnik <bubnikv@gmail.com>2021-05-24 15:10:04 +0300
commite658fe0698327c7a54a15953f32f9befb6c8cdfb (patch)
tree7f3717d5540f64f1a67af80096f46a71f7f67722
parent740773db85c6be11c29b701763137239d16c457c (diff)
WIP: PrintRegion refactoring, it finally compiles!
Config/PrintConfig refactoring to support operator< for StaticPrintConfig derived containers.
-rw-r--r--src/libslic3r/Config.hpp46
-rw-r--r--src/libslic3r/Point.hpp61
-rw-r--r--src/libslic3r/Print.hpp6
-rw-r--r--src/libslic3r/PrintApply.cpp97
-rw-r--r--src/libslic3r/PrintConfig.hpp9
-rw-r--r--src/libslic3r/PrintObjectSlice.cpp138
-rw-r--r--src/libslic3r/TriangleMeshSlicer.cpp14
-rw-r--r--src/libslic3r/TriangleMeshSlicer.hpp4
8 files changed, 145 insertions, 230 deletions
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index adda2654e..30d76d610 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -37,6 +37,7 @@ namespace Slic3r {
inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value == r.value && l.percent == r.percent; }
inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return !(l == r); }
+ inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); }
}
namespace std {
@@ -230,6 +231,7 @@ public:
bool operator==(const T &rhs) const throw() { return this->value == rhs; }
bool operator!=(const T &rhs) const throw() { return this->value != rhs; }
+ bool operator< (const T &rhs) const throw() { return this->value < rhs; }
size_t hash() const throw() override { return std::hash<T>{}(this->value); }
@@ -467,6 +469,7 @@ public:
double getFloat() const override { return this->value; }
ConfigOption* clone() const override { return new ConfigOptionFloat(*this); }
bool operator==(const ConfigOptionFloat &rhs) const throw() { return this->value == rhs.value; }
+ bool operator< (const ConfigOptionFloat &rhs) const throw() { return this->value < rhs.value; }
std::string serialize() const override
{
@@ -508,6 +511,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionFloatsTempl(*this); }
bool operator==(const ConfigOptionFloatsTempl &rhs) const throw() { return vectors_equal(this->values, rhs.values); }
+ bool operator< (const ConfigOptionFloatsTempl &rhs) const throw() { return vectors_lower(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
@@ -598,6 +602,18 @@ protected:
// Not supporting nullable values, the default vector compare is cheaper.
return v1 == v2;
}
+ static bool vectors_lower(const std::vector<double> &v1, const std::vector<double> &v2) {
+ if (NULLABLE) {
+ for (auto it1 = v1.begin(), it2 = v2.begin(); it1 != v1.end() && it2 != v2.end(); ++ it1, ++ it2) {
+ auto null1 = int(std::isnan(*it1));
+ auto null2 = int(std::isnan(*it2));
+ return (null1 < null2) || (null1 == null2 && *it1 < *it2);
+ }
+ return v1.size() < v2.size();
+ } else
+ // Not supporting nullable values, the default vector compare is cheaper.
+ return v1 < v2;
+ }
private:
friend class cereal::access;
@@ -658,8 +674,9 @@ public:
static ConfigOptionType static_type() { return coInts; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionIntsTempl(*this); }
- ConfigOptionIntsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ ConfigOptionIntsTempl& operator= (const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionIntsTempl &rhs) const throw() { return this->values == rhs.values; }
+ bool operator< (const ConfigOptionIntsTempl &rhs) const throw() { return this->values < rhs.values; }
// Could a special "nil" value be stored inside the vector, indicating undefined value?
bool nullable() const override { return NULLABLE; }
// Special "nil" value to be stored into the vector if this->supports_nil().
@@ -776,6 +793,7 @@ public:
ConfigOption* clone() const override { return new ConfigOptionStrings(*this); }
ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionStrings &rhs) const throw() { return this->values == rhs.values; }
+ bool operator< (const ConfigOptionStrings &rhs) const throw() { return this->values < rhs.values; }
bool is_nil(size_t) const override { return false; }
std::string serialize() const override
@@ -969,6 +987,7 @@ public:
assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values);
}
+ bool operator< (const ConfigOptionFloatsOrPercentsTempl &rhs) const throw() { return vectors_lower(this->values, rhs.values); }
// Could a special "nil" value be stored inside the vector, indicating undefined value?
bool nullable() const override { return NULLABLE; }
@@ -1057,6 +1076,18 @@ protected:
// Not supporting nullable values, the default vector compare is cheaper.
return v1 == v2;
}
+ static bool vectors_lower(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) {
+ if (NULLABLE) {
+ for (auto it1 = v1.begin(), it2 = v2.begin(); it1 != v1.end() && it2 != v2.end(); ++ it1, ++ it2) {
+ auto null1 = int(std::isnan(*it1));
+ auto null2 = int(std::isnan(*it2));
+ return (null1 < null2) || (null1 == null2 && *it1 < *it2);
+ }
+ return v1.size() < v2.size();
+ } else
+ // Not supporting nullable values, the default vector compare is cheaper.
+ return v1 < v2;
+ }
private:
friend class cereal::access;
@@ -1077,6 +1108,7 @@ public:
ConfigOption* clone() const override { return new ConfigOptionPoint(*this); }
ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionPoint &rhs) const throw() { return this->value == rhs.value; }
+ bool operator< (const ConfigOptionPoint &rhs) const throw() { return this->value < rhs.value; }
std::string serialize() const override
{
@@ -1111,8 +1143,10 @@ public:
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; }
+ ConfigOptionPoints& operator= (const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionPoints &rhs) const throw() { return this->values == rhs.values; }
+ bool operator< (const ConfigOptionPoints &rhs) const throw()
+ { return std::lexicographical_compare(this->values.begin(), this->values.end(), rhs.values.begin(), rhs.values.end(), [](const auto &l, const auto &r){ return l < r; }); }
bool is_nil(size_t) const override { return false; }
std::string serialize() const override
@@ -1185,6 +1219,8 @@ public:
ConfigOption* clone() const override { return new ConfigOptionPoint3(*this); }
ConfigOptionPoint3& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionPoint3 &rhs) const throw() { return this->value == rhs.value; }
+ bool operator< (const ConfigOptionPoint3 &rhs) const throw()
+ { return this->value.x() < rhs.value.x() || (this->value.x() == rhs.value.x() && (this->value.y() < rhs.value.y() || (this->value.y() == rhs.value.y() && this->value.z() < rhs.value.z()))); }
std::string serialize() const override
{
@@ -1222,6 +1258,7 @@ public:
ConfigOption* clone() const override { return new ConfigOptionBool(*this); }
ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionBool &rhs) const throw() { return this->value == rhs.value; }
+ bool operator< (const ConfigOptionBool &rhs) const throw() { return int(this->value) < int(rhs.value); }
std::string serialize() const override
{
@@ -1256,6 +1293,7 @@ public:
ConfigOption* clone() const override { return new ConfigOptionBoolsTempl(*this); }
ConfigOptionBoolsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionBoolsTempl &rhs) const throw() { return this->values == rhs.values; }
+ bool operator< (const ConfigOptionBoolsTempl &rhs) const throw() { return this->values < rhs.values; }
// Could a special "nil" value be stored inside the vector, indicating undefined value?
bool nullable() const override { return NULLABLE; }
// Special "nil" value to be stored into the vector if this->supports_nil().
@@ -1350,6 +1388,7 @@ public:
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 throw() { return this->value == rhs.value; }
+ bool operator< (const ConfigOptionEnum<T> &rhs) const throw() { return int(this->value) < int(rhs.value); }
int getInt() const override { return (int)this->value; }
void setInt(int val) override { this->value = T(val); }
@@ -1419,8 +1458,9 @@ public:
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; }
+ ConfigOptionEnumGeneric& operator= (const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionEnumGeneric &rhs) const throw() { return this->value == rhs.value; }
+ bool operator< (const ConfigOptionEnumGeneric &rhs) const throw() { return this->value < rhs.value; }
bool operator==(const ConfigOption &rhs) const override
{
diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp
index 12870b713..36d85ef90 100644
--- a/src/libslic3r/Point.hpp
+++ b/src/libslic3r/Point.hpp
@@ -54,7 +54,7 @@ using Transform2d = Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAli
using Transform3f = Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign>;
using Transform3d = Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign>;
-inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0) || (lhs(0) == rhs(0) && lhs(1) < rhs(1)); }
+inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); }
template<int Options>
int32_t cross2(const Eigen::MatrixBase<Eigen::Matrix<int32_t, 2, 1, Options>> &v1, const Eigen::MatrixBase<Eigen::Matrix<int32_t, 2, 1, Options>> &v2) = delete;
@@ -62,36 +62,36 @@ int32_t cross2(const Eigen::MatrixBase<Eigen::Matrix<int32_t, 2, 1, Options>> &v
template<typename T, int Options>
inline T cross2(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> &v1, const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> &v2)
{
- return v1(0) * v2(1) - v1(1) * v2(0);
+ return v1.x() * v2.y() - v1.y() * v2.x();
}
template<typename Derived, typename Derived2>
inline typename Derived::Scalar cross2(const Eigen::MatrixBase<Derived> &v1, const Eigen::MatrixBase<Derived2> &v2)
{
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "cross2(): Scalar types of 1st and 2nd operand must be equal.");
- return v1(0) * v2(1) - v1(1) * v2(0);
+ return v1.x() * v2.y() - v1.y() * v2.x();
}
template<typename T, int Options>
inline Eigen::Matrix<T, 2, 1, Eigen::DontAlign> perp(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> &v) { return Eigen::Matrix<T, 2, 1, Eigen::DontAlign>(- v.y(), v.x()); }
template<class T, int N, int Options>
-Eigen::Matrix<T, 2, 1, Eigen::DontAlign> to_2d(const Eigen::MatrixBase<Eigen::Matrix<T, N, 1, Options>> &ptN) { return { ptN(0), ptN(1) }; }
+Eigen::Matrix<T, 2, 1, Eigen::DontAlign> to_2d(const Eigen::MatrixBase<Eigen::Matrix<T, N, 1, Options>> &ptN) { return { ptN.x(), ptN.y() }; }
template<class T, int Options>
-Eigen::Matrix<T, 3, 1, Eigen::DontAlign> to_3d(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> & pt, const T z) { return { pt(0), pt(1), z }; }
+Eigen::Matrix<T, 3, 1, Eigen::DontAlign> to_3d(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> & pt, const T z) { return { pt.x(), pt.y(), z }; }
inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale<double>(x), unscale<double>(y)); }
-inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale<double>(pt(0)), unscale<double>(pt(1))); }
-inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale<double>(pt(0)), unscale<double>(pt(1))); }
+inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale<double>(pt.x()), unscale<double>(pt.y())); }
+inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale<double>(pt.x()), unscale<double>(pt.y())); }
inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale<double>(x), unscale<double>(y), unscale<double>(z)); }
-inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale<double>(pt(0)), unscale<double>(pt(1)), unscale<double>(pt(2))); }
-inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale<double>(pt(0)), unscale<double>(pt(1)), unscale<double>(pt(2))); }
+inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale<double>(pt.x()), unscale<double>(pt.y()), unscale<double>(pt.z())); }
+inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale<double>(pt.x()), unscale<double>(pt.y()), unscale<double>(pt.z())); }
-inline std::string to_string(const Vec2crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; }
-inline std::string to_string(const Vec2d &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; }
-inline std::string to_string(const Vec3crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; }
-inline std::string to_string(const Vec3d &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; }
+inline std::string to_string(const Vec2crd &pt) { return std::string("[") + std::to_string(pt.x()) + ", " + std::to_string(pt.y()) + "]"; }
+inline std::string to_string(const Vec2d &pt) { return std::string("[") + std::to_string(pt.x()) + ", " + std::to_string(pt.y()) + "]"; }
+inline std::string to_string(const Vec3crd &pt) { return std::string("[") + std::to_string(pt.x()) + ", " + std::to_string(pt.y()) + ", " + std::to_string(pt.z()) + "]"; }
+inline std::string to_string(const Vec3d &pt) { return std::string("[") + std::to_string(pt.x()) + ", " + std::to_string(pt.y()) + ", " + std::to_string(pt.z()) + "]"; }
std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
Pointf3s transform(const Pointf3s& points, const Transform3d& t);
@@ -123,19 +123,17 @@ public:
return *this;
}
- bool operator< (const Point& rhs) const { return (*this)(0) < rhs(0) || ((*this)(0) == rhs(0) && (*this)(1) < rhs(1)); }
-
- Point& operator+=(const Point& rhs) { (*this)(0) += rhs(0); (*this)(1) += rhs(1); return *this; }
- Point& operator-=(const Point& rhs) { (*this)(0) -= rhs(0); (*this)(1) -= rhs(1); return *this; }
- Point& operator*=(const double &rhs) { (*this)(0) = coord_t((*this)(0) * rhs); (*this)(1) = coord_t((*this)(1) * rhs); return *this; }
- Point operator*(const double &rhs) { return Point((*this)(0) * rhs, (*this)(1) * rhs); }
+ Point& operator+=(const Point& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); return *this; }
+ Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; }
+ Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; }
+ Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); }
void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); }
void rotate(double cos_a, double sin_a) {
- double cur_x = (double)(*this)(0);
- double cur_y = (double)(*this)(1);
- (*this)(0) = (coord_t)round(cos_a * cur_x - sin_a * cur_y);
- (*this)(1) = (coord_t)round(cos_a * cur_y + sin_a * cur_x);
+ double cur_x = (double)this->x();
+ double cur_y = (double)this->y();
+ this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y);
+ this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x);
}
void rotate(double angle, const Point &center);
@@ -153,6 +151,11 @@ public:
Point projection_onto(const Line &line) const;
};
+inline bool operator<(const Point &l, const Point &r)
+{
+ return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y());
+}
+
inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON))
{
Point d = (p2 - p1).cwiseAbs();
@@ -204,7 +207,7 @@ namespace int128 {
// To be used by std::unordered_map, std::unordered_multimap and friends.
struct PointHash {
size_t operator()(const Vec2crd &pt) const {
- return std::hash<coord_t>()(pt(0)) ^ std::hash<coord_t>()(pt(1));
+ return std::hash<coord_t>()(pt.x()) ^ std::hash<coord_t>()(pt.y());
}
};
@@ -265,7 +268,7 @@ public:
const Point *pt = m_point_accessor(value);
if (pt != nullptr) {
// Range of fragment starts around grid_corner, close to pt.
- auto range = m_map.equal_range(Point((*pt)(0)>>m_grid_log2, (*pt)(1)>>m_grid_log2));
+ auto range = m_map.equal_range(Point((*pt).x()>>m_grid_log2, (*pt).y()>>m_grid_log2));
// Remove the first item.
for (auto it = range.first; it != range.second; ++ it) {
if (it->second == value) {
@@ -284,12 +287,12 @@ public:
const ValueType *value_min = nullptr;
double dist_min = std::numeric_limits<double>::max();
// Round pt to a closest grid_cell corner.
- Vec2crd grid_corner((pt(0)+(m_grid_resolution>>1))>>m_grid_log2, (pt(1)+(m_grid_resolution>>1))>>m_grid_log2);
+ Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2);
// For four neighbors of grid_corner:
for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) {
for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) {
// Range of fragment starts around grid_corner, close to pt.
- auto range = m_map.equal_range(Vec2crd(grid_corner(0) + neighbor_x, grid_corner(1) + neighbor_y));
+ auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y));
// Find the map entry closest to pt.
for (auto it = range.first; it != range.second; ++it) {
const ValueType &value = it->second;
@@ -313,14 +316,14 @@ public:
std::vector<std::pair<const ValueType*, double>> find_all(const Vec2crd &pt) {
// Iterate over 4 closest grid cells around pt,
// Round pt to a closest grid_cell corner.
- Vec2crd grid_corner((pt(0)+(m_grid_resolution>>1))>>m_grid_log2, (pt(1)+(m_grid_resolution>>1))>>m_grid_log2);
+ Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2);
// For four neighbors of grid_corner:
std::vector<std::pair<const ValueType*, double>> out;
const double r2 = double(m_search_radius) * m_search_radius;
for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) {
for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) {
// Range of fragment starts around grid_corner, close to pt.
- auto range = m_map.equal_range(Vec2crd(grid_corner(0) + neighbor_x, grid_corner(1) + neighbor_y));
+ auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y));
// Find the map entry closest to pt.
for (auto it = range.first; it != range.second; ++it) {
const ValueType &value = it->second;
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index a90c76a91..1cc87d9d9 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -221,9 +221,9 @@ public:
Transform3d trafo_bboxes;
std::vector<ObjectID> cached_volume_ids;
- size_t ref_cnt_inc() { ++ m_ref_cnt; }
- size_t ref_cnt_dec() { if (-- m_ref_cnt == 0) delete this; }
- void clear() {
+ void ref_cnt_inc() { ++ m_ref_cnt; }
+ void ref_cnt_dec() { if (-- m_ref_cnt == 0) delete this; }
+ void clear() {
all_regions.clear();
layer_ranges.clear();
}
diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp
index 3dc27a5b5..f428971fe 100644
--- a/src/libslic3r/PrintApply.cpp
+++ b/src/libslic3r/PrintApply.cpp
@@ -242,45 +242,6 @@ static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &cu
return full_config_diff;
}
-bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type)
-{
- size_t i_old, i_new;
- for (i_old = 0, i_new = 0; i_old < model_object_old.volumes.size() && i_new < model_object_new.volumes.size();) {
- const ModelVolume &mv_old = *model_object_old.volumes[i_old];
- const ModelVolume &mv_new = *model_object_new.volumes[i_new];
- if (mv_old.type() != type) {
- ++ i_old;
- continue;
- }
- if (mv_new.type() != type) {
- ++ i_new;
- continue;
- }
- if (mv_old.id() != mv_new.id())
- return true;
- //FIXME test for the content of the mesh!
-
- if (!mv_old.get_matrix().isApprox(mv_new.get_matrix()))
- return true;
-
- ++ i_old;
- ++ i_new;
- }
- for (; i_old < model_object_old.volumes.size(); ++ i_old) {
- const ModelVolume &mv_old = *model_object_old.volumes[i_old];
- if (mv_old.type() == type)
- // ModelVolume was deleted.
- return true;
- }
- for (; i_new < model_object_new.volumes.size(); ++ i_new) {
- const ModelVolume &mv_new = *model_object_new.volumes[i_new];
- if (mv_new.type() == type)
- // ModelVolume was added.
- return true;
- }
- return false;
-}
-
// Repository for solving partial overlaps of ModelObject::layer_config_ranges.
// Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges.
class LayerRanges
@@ -290,6 +251,8 @@ public:
t_layer_height_range layer_height_range;
// Config is owned by the associated ModelObject.
const DynamicPrintConfig* config { nullptr };
+
+ bool operator<(const LayerRange &rhs) const throw() { return this->layer_height_range < rhs.layer_height_range; }
};
LayerRanges() = default;
@@ -323,7 +286,7 @@ public:
}
const DynamicPrintConfig* config(const t_layer_height_range &range) const {
- auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange { t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr });
+ auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange{ { range.first - EPSILON, range.second - EPSILON } });
// #ys_FIXME_COLOR
// assert(it != m_ranges.end());
// assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON);
@@ -518,7 +481,7 @@ static BoundingBoxf3 transformed_its_bbox2d(const indexed_triangle_set &its, con
BoundingBoxf3 bbox;
for (const stl_triangle_vertex_indices &tri : its.indices)
for (int i = 0; i < 3; ++ i)
- bbox.merge(m * its.vertices[tri(i)]);
+ bbox.merge((m * its.vertices[tri(i)]).cast<double>());
bbox.min.x() -= offset;
bbox.min.y() -= offset;
bbox.min.x() += offset;
@@ -556,25 +519,25 @@ static void transformed_its_bboxes_in_z_ranges(
float t2 = (z_range.second - p1->z()) / zspan;
Vec2f p = to_2d(*p1);
Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
- bbox.merge(to_3d((p + v * t1).eval(), float(z_range.first)));
- bbox.merge(to_3d((p + v * t2).eval(), float(z_range.second)));
+ bbox.merge((to_3d((p + v * t1).eval(), float(z_range.first))).cast<double>());
+ bbox.merge((to_3d((p + v * t2).eval(), float(z_range.second))).cast<double>());
} else {
// Single intersection with the lower limit.
float t = (z_range.first - p1->z()) / (p2->z() - p1->z());
Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
- bbox.merge(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.first)));
- bbox.merge(*p2);
+ bbox.merge((to_3d((to_2d(*p1) + v * t).eval(), float(z_range.first))).cast<double>());
+ bbox.merge(p2->cast<double>());
}
} else if (p2->z() > z_range.second) {
// Single intersection with the upper limit.
float t = (z_range.second - p1->z()) / (p2->z() - p1->z());
Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
- bbox.merge(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.second)));
- bbox.merge(*p1);
+ bbox.merge((to_3d((to_2d(*p1) + v * t).eval(), float(z_range.second)).cast<double>()));
+ bbox.merge(p1->cast<double>());
} else {
// Both points are inside.
- bbox.merge(*p1);
- bbox.merge(*p2);
+ bbox.merge(p1->cast<double>());
+ bbox.merge(p2->cast<double>());
}
iprev = iedge;
}
@@ -744,8 +707,8 @@ void update_volume_bboxes(
if (it != volumes_old.end() && it->volume_id == model_volume->id())
layer_range.volumes.emplace_back(*it);
} else
- layer_range.volumes.emplace_back(model_volume->id(),
- transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset));
+ layer_range.volumes.push_back({ model_volume->id(),
+ transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) });
}
} else {
std::vector<std::vector<PrintObjectRegions::VolumeExtents>> volumes_old;
@@ -779,7 +742,7 @@ void update_volume_bboxes(
} else {
transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset);
for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
- layer_range.volumes.emplace_back(model_volume->id(), bboxes[&layer_range - layer_ranges.data()]);
+ layer_range.volumes.push_back({ model_volume->id(), bboxes[&layer_range - layer_ranges.data()] });
}
}
}
@@ -799,6 +762,7 @@ static PrintObjectRegions* generate_print_object_regions(
const PrintRegionConfig &default_region_config,
const Transform3d &trafo,
size_t num_extruders,
+ const float xy_size_compensation,
const std::vector<unsigned int> &painting_extruders)
{
// Reuse the old object or generate a new one.
@@ -827,11 +791,20 @@ static PrintObjectRegions* generate_print_object_regions(
layer_ranges_regions.push_back({ range.layer_height_range, range.config });
}
- update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, std::max(0.f, float(print_object.config().xy_size_compensation()));
-
- std::set<const PrintRegion*> region_set;
- auto get_create_region = [&region_set](PrintRegionConfig &&config) -> PrintRegion* {
- return nullptr;
+ update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, std::max(0.f, xy_size_compensation));
+
+ std::vector<PrintRegion*> region_set;
+ auto get_create_region = [&region_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* {
+ size_t hash = config.hash();
+ auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash](const PrintRegion* l) {
+ return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config); });
+ if ((*it)->config_hash() == hash && (*it)->config() == config)
+ return *it;
+ // Insert into a sorted array, it has O(n) complexity, but the calling algorithm has an O(n^2*log(n)) complexity anyways.
+ all_regions.emplace_back(std::make_unique<PrintRegion>(std::move(config), hash));
+ PrintRegion *region = all_regions.back().get();
+ region_set.emplace(it, region);
+ return region;
};
// Chain the regions in the order they are stored in the volumes list.
@@ -1259,11 +1232,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
num_extruders,
painting_extruders,
*print_object_regions,
- [](){
- // Stop the background process before assigning new configuration to the regions.
- t_config_option_keys diff = region.config().diff(region_config);
- update_apply_status(print_object->invalidate_state_by_config_options(region.config(), region_config, diff));
- region.config_apply_only(region_config, diff, false);
+ [&print_object, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) {
+ update_apply_status(print_object.invalidate_state_by_config_options(old_config, new_config, diff_keys));
})) {
// Regions are valid, just keep them.
} else {
@@ -1285,9 +1255,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
print_object_regions,
print_object.model_object()->volumes,
LayerRanges(print_object.model_object()->layer_config_ranges),
- model_object_status.print_instances.front().trafo,
m_default_region_config,
+ model_object_status.print_instances.front().trafo,
num_extruders,
+ float(print_object.config().xy_size_compensation.value),
painting_extruders);
}
for (auto it = it_print_object; it != it_print_object_end; ++it)
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index 2ba6d4cbd..0d94513d6 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -346,6 +346,9 @@ public: \
#define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION(r, data, elem) PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(BOOST_PP_TUPLE_ELEM(1, elem))
#define PRINT_CONFIG_CLASS_ELEMENT_HASH(r, data, elem) boost::hash_combine(seed, BOOST_PP_TUPLE_ELEM(1, elem).hash());
#define PRINT_CONFIG_CLASS_ELEMENT_EQUAL(r, data, elem) if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false;
+#define PRINT_CONFIG_CLASS_ELEMENT_LOWER(r, data, elem) \
+ if (BOOST_PP_TUPLE_ELEM(1, elem) < rhs.BOOST_PP_TUPLE_ELEM(1, elem)) return true; \
+ if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false;
#define PRINT_CONFIG_CLASS_DEFINE(CLASS_NAME, PARAMETER_DEFINITION_SEQ) \
class CLASS_NAME : public StaticPrintConfig { \
@@ -364,6 +367,11 @@ public: \
return true; \
} \
bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \
+ bool operator<(const CLASS_NAME &rhs) const throw() \
+ { \
+ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_LOWER, _, PARAMETER_DEFINITION_SEQ) \
+ return false; \
+ } \
protected: \
void initialize(StaticCacheBase &cache, const char *base_ptr) \
{ \
@@ -932,6 +940,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE0(
#undef STATIC_PRINT_CONFIG_CACHE_DERIVED
#undef PRINT_CONFIG_CLASS_ELEMENT_DEFINITION
#undef PRINT_CONFIG_CLASS_ELEMENT_EQUAL
+#undef PRINT_CONFIG_CLASS_ELEMENT_LOWER
#undef PRINT_CONFIG_CLASS_ELEMENT_HASH
#undef PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION
#undef PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2
diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp
index 961eaefb0..1b54cfb77 100644
--- a/src/libslic3r/PrintObjectSlice.cpp
+++ b/src/libslic3r/PrintObjectSlice.cpp
@@ -162,12 +162,12 @@ static std::vector<VolumeSlices> slice_volumes_inner(
slicing_ranges.reserve(layer_ranges.size());
MeshSlicingParamsEx params_base;
- params_base.closing_radius = float(print_object_config.slice_closing_radius.value);
+ params_base.closing_radius = scaled<float>(print_object_config.slice_closing_radius.value);
params_base.extra_offset = 0;
params_base.trafo = object_trafo;
params_base.resolution = scaled<double>(print_config.resolution.value);
- const float extra_offset = print_object_config.xy_size_compensation > 0 ? float(print_object_config.xy_size_compensation.value) : 0.f;
+ const auto extra_offset = print_object_config.xy_size_compensation > 0 ? scaled<float>(print_object_config.xy_size_compensation.value) : 0.f;
for (const ModelVolume *model_volume : model_volumes)
if (model_volume_needs_slicing(*model_volume)) {
@@ -208,6 +208,8 @@ static std::vector<VolumeSlices> slice_volumes_inner(
if (! out.empty() && out.back().slices.empty())
out.pop_back();
}
+
+ return out;
}
static inline VolumeSlices& volume_slices_find_by_id(std::vector<VolumeSlices> &volume_slices, const ObjectID id)
@@ -302,7 +304,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
last_volume_idx_of_region.assign(print_object_regions.all_regions.size(), -1);
for (const PrintObjectRegions::VolumeRegion &region : layer_range.volume_regions) {
int region_id = region.region->print_object_region_id();
- layer_range_regions_to_slices.emplace_back(&volume_slices_find_by_id(volume_slices, region.model_volume->id()), last_volume_idx_of_region[region_id]);
+ layer_range_regions_to_slices.push_back({ &volume_slices_find_by_id(volume_slices, region.model_volume->id()), last_volume_idx_of_region[region_id] });
last_volume_idx_of_region[region_id] = &region - layer_range.volume_regions.data();
}
}
@@ -398,105 +400,9 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
}
});
}
-}
-
-#if 0
-// Z ranges are not applicable to modifier meshes, therefore a single volume will be found in volume_w_zrange at most once.
-std::vector<ExPolygons> PrintObject::slice_modifiers(size_t region_id, const std::vector<float> &slice_zs) const
-{
- std::vector<ExPolygons> out;
- if (region_id < m_region_volumes.size())
- {
- std::vector<std::vector<t_layer_height_range>> volume_ranges;
- const PrintRegionVolumes &volumes_and_ranges = m_region_volumes[region_id];
- volume_ranges.reserve(volumes_and_ranges.volumes.size());
- for (size_t i = 0; i < volumes_and_ranges.volumes.size(); ) {
- int volume_id = volumes_and_ranges.volumes[i].volume_idx;
- const ModelVolume *model_volume = this->model_object()->volumes[volume_id];
- if (model_volume->is_modifier()) {
- std::vector<t_layer_height_range> ranges;
- ranges.emplace_back(volumes_and_ranges.volumes[i].layer_height_range);
- size_t j = i + 1;
- for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j) {
- if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges.volumes[j].layer_height_range.first) < EPSILON)
- ranges.back().second = volumes_and_ranges.volumes[j].layer_height_range.second;
- else
- ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range);
- }
- volume_ranges.emplace_back(std::move(ranges));
- i = j;
- } else
- ++ i;
- }
-
- if (! volume_ranges.empty())
- {
- bool equal_ranges = true;
- for (size_t i = 1; i < volume_ranges.size(); ++ i) {
- assert(! volume_ranges[i].empty());
- if (volume_ranges.front() != volume_ranges[i]) {
- equal_ranges = false;
- break;
- }
- }
- if (equal_ranges && volume_ranges.front().size() == 1 && volume_ranges.front().front() == t_layer_height_range(0, DBL_MAX)) {
- // No modifier in this region was split to layer spans.
- std::vector<const ModelVolume*> volumes;
- for (const PrintRegionVolumes::VolumeWithZRange &volume_w_zrange : m_region_volumes[region_id].volumes) {
- const ModelVolume *volume = this->model_object()->volumes[volume_w_zrange.volume_idx];
- if (volume->is_modifier())
- volumes.emplace_back(volume);
- }
- out = this->slice_volumes(slice_zs, MeshSlicingParams::SlicingMode::Regular, volumes);
- } else {
- // Some modifier in this region was split to layer spans.
- std::vector<char> merge;
- for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) {
- const PrintRegionVolumes &volumes_and_ranges = m_region_volumes[region_id];
- for (size_t i = 0; i < volumes_and_ranges.volumes.size(); ) {
- int volume_id = volumes_and_ranges.volumes[i].volume_idx;
- const ModelVolume *model_volume = this->model_object()->volumes[volume_id];
- if (model_volume->is_modifier()) {
- BOOST_LOG_TRIVIAL(debug) << "Slicing modifiers - volume " << volume_id;
- // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume.
- std::vector<t_layer_height_range> ranges;
- ranges.emplace_back(volumes_and_ranges.volumes[i].layer_height_range);
- size_t j = i + 1;
- for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j)
- ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range);
- // slicing in parallel
- std::vector<ExPolygons> this_slices = this->slice_volume(slice_zs, ranges, MeshSlicingParams::SlicingMode::Regular, *model_volume);
- // Variable this_slices could be empty if no value of slice_zs is within any of the ranges of this volume.
- if (out.empty()) {
- out = std::move(this_slices);
- merge.assign(out.size(), false);
- } else if (!this_slices.empty()) {
- assert(out.size() == this_slices.size());
- for (size_t i = 0; i < out.size(); ++ i)
- if (! this_slices[i].empty()) {
- if (! out[i].empty()) {
- append(out[i], this_slices[i]);
- merge[i] = true;
- } else
- out[i] = std::move(this_slices[i]);
- }
- }
- i = j;
- } else
- ++ i;
- }
- }
- for (size_t i = 0; i < merge.size(); ++ i)
- if (merge[i])
- out[i] = union_ex(out[i]);
- }
- }
- }
-
- return out;
+ return slices_by_region;
}
-#endif
std::string fix_slicing_errors(LayerPtrs &layers, const std::function<void()> &throw_if_canceled)
{
@@ -725,8 +631,8 @@ void PrintObject::slice_volumes()
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
{
- // Compensation value, scaled.
- const float xy_compensation_scaled = float(scale_(m_config.xy_size_compensation.value));
+ // Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing.
+ const auto xy_compensation_scaled = scaled<float>(std::min(m_config.xy_size_compensation.value, 0.));
const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ?
// Only enable Elephant foot compensation if printing directly on the print bed.
float(scale_(m_config.elefant_foot_compensation.value)) :
@@ -735,7 +641,7 @@ void PrintObject::slice_volumes()
ExPolygons lslices_1st_layer;
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
- [this, upscaled, clipped, xy_compensation_scaled, elephant_foot_compensation_scaled, &lslices_1st_layer]
+ [this, xy_compensation_scaled, elephant_foot_compensation_scaled, &lslices_1st_layer]
(const tbb::blocked_range<size_t>& range) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
m_print->throw_if_canceled();
@@ -743,8 +649,6 @@ void PrintObject::slice_volumes()
// Apply size compensation and perform clipping of multi-part objects.
float elfoot = (layer_id == 0) ? elephant_foot_compensation_scaled : 0.f;
if (layer->m_regions.size() == 1) {
- assert(! upscaled);
- assert(! clipped);
// Optimized version for a single region layer.
// Single region, growing or shrinking.
LayerRegion *layerm = layer->m_regions.front();
@@ -763,35 +667,15 @@ void PrintObject::slice_volumes()
(delta == 0.f) ? lslices_1st_layer : offset_ex(lslices_1st_layer, delta),
layerm->flow(frExternalPerimeter), unscale<double>(elfoot))),
stInternal);
- if (xy_compensation_scaled != 0.f)
+ if (xy_compensation_scaled < 0.f)
lslices_1st_layer = offset_ex(std::move(lslices_1st_layer), xy_compensation_scaled);
- } else if (xy_compensation_scaled != 0.f) {
+ } else if (xy_compensation_scaled < 0.f) {
// Apply the XY compensation.
layerm->slices.set(
offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), xy_compensation_scaled),
stInternal);
}
} else {
- bool upscale = ! upscaled && xy_compensation_scaled > 0.f;
- bool clip = ! clipped && m_config.clip_multipart_objects.value;
- if (upscale || clip) {
- // Multiple regions, growing or just clipping one region by the other.
- // When clipping the regions, priority is given to the first regions.
- Polygons processed;
- for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) {
- LayerRegion *layerm = layer->m_regions[region_id];
- ExPolygons slices = to_expolygons(std::move(layerm->slices.surfaces));
- if (upscale)
- slices = offset_ex(std::move(slices), xy_compensation_scaled);
- if (region_id > 0 && clip)
- // Trim by the slices of already processed regions.
- slices = diff_ex(slices, processed);
- if (clip && (region_id + 1 < layer->m_regions.size()))
- // Collect the already processed regions to trim the to be processed regions.
- polygons_append(processed, slices);
- layerm->slices.set(std::move(slices), stInternal);
- }
- }
if (xy_compensation_scaled < 0.f || elfoot > 0.f) {
// Apply the negative XY compensation.
Polygons trimming;
diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp
index e27766fb8..dac931724 100644
--- a/src/libslic3r/TriangleMeshSlicer.cpp
+++ b/src/libslic3r/TriangleMeshSlicer.cpp
@@ -1021,9 +1021,17 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c
// double safety_offset = scale_(0.0499);
// 0.0001 is set to satisfy GH #520, #1029, #1364
assert(closing_radius >= 0);
- assert(extra_offset >= 0);
- double offset_out = + scale_(closing_radius + extra_offset);
- double offset_in = - scale_(closing_radius);
+ // Allowing negative extra_offset for shrinking a contour. This likely only makes sense if slicing a single region only.
+ //assert(extra_offset >= 0);
+ double offset_out;
+ double offset_in;
+ if (closing_radius >= extra_offset) {
+ offset_out = + scale_(closing_radius);
+ offset_in = - scale_(closing_radius - extra_offset);
+ } else {
+ offset_out = + scale_(extra_offset);
+ offset_in = 0.;
+ }
/* The following line is commented out because it can generate wrong polygons,
see for example issue #661 */
diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp
index 3a144d834..4b8740d48 100644
--- a/src/libslic3r/TriangleMeshSlicer.hpp
+++ b/src/libslic3r/TriangleMeshSlicer.hpp
@@ -32,9 +32,9 @@ struct MeshSlicingParams
struct MeshSlicingParamsEx : public MeshSlicingParams
{
- // Morphological closing operation when creating output expolygons.
+ // Morphological closing operation when creating output expolygons, scaled!
float closing_radius { 0 };
- // Positive offset applied when creating output expolygons.
+ // Positive offset applied when creating output expolygons, scaled!
float extra_offset { 0 };
// Resolution for contour simplification, scaled!
// 0 = don't simplify.