mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-23 08:41:11 -06:00
Merge branch 'master' into lm_seam_painter_backend
This commit is contained in:
commit
6db2d3a0b2
175 changed files with 7346 additions and 2847 deletions
|
@ -283,7 +283,7 @@ namespace detail {
|
|||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) {
|
||||
return intersect_triangle1(const_cast<double*>(origin.data()), const_cast<double*>(dir.data()),
|
||||
const_cast<double*>(v0.data()), const_cast<double*>(v1.data()), const_cast<double*>(v2.data()),
|
||||
&t, &u, &v);
|
||||
|
@ -291,7 +291,7 @@ namespace detail {
|
|||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && !std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) {
|
||||
using Vector = Eigen::Matrix<double, 3, 1>;
|
||||
Vector w0 = v0.template cast<double>();
|
||||
Vector w1 = v1.template cast<double>();
|
||||
|
@ -302,7 +302,7 @@ namespace detail {
|
|||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) {
|
||||
using Vector = Eigen::Matrix<double, 3, 1>;
|
||||
Vector o = origin.template cast<double>();
|
||||
Vector d = dir.template cast<double>();
|
||||
|
@ -311,7 +311,7 @@ namespace detail {
|
|||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && ! std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) {
|
||||
using Vector = Eigen::Matrix<double, 3, 1>;
|
||||
Vector o = origin.template cast<double>();
|
||||
Vector d = dir.template cast<double>();
|
||||
|
@ -692,6 +692,40 @@ inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set(
|
|||
detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits<Scalar>::infinity(), hit_idx_out, hit_point_out);
|
||||
}
|
||||
|
||||
// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
|
||||
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// Returns true if exists some triangle in defined radius, false otherwise.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool is_any_triangle_in_radius(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Point to which the closest point on the indexed triangle set is searched for.
|
||||
const VectorType &point,
|
||||
// Maximum distance in which triangle is search for
|
||||
typename VectorType::Scalar &max_distance)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
|
||||
size_t hit_idx;
|
||||
VectorType hit_point = VectorType::Ones() * (std::nan(""));
|
||||
|
||||
if(tree.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point);
|
||||
|
||||
return hit_point.allFinite();
|
||||
}
|
||||
|
||||
} // namespace AABBTreeIndirect
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "Exception.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -101,6 +102,9 @@ void AppConfig::set_defaults()
|
|||
if (get("use_inches").empty())
|
||||
set("use_inches", "0");
|
||||
|
||||
if (get("show_splash_screen").empty())
|
||||
set("show_splash_screen", "1");
|
||||
|
||||
// Remove legacy window positions/sizes
|
||||
erase("", "main_frame_maximized");
|
||||
erase("", "main_frame_pos");
|
||||
|
@ -123,7 +127,7 @@ std::string AppConfig::load()
|
|||
// ! But to avoid the use of _utf8 (related to use of wxWidgets)
|
||||
// we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
|
||||
/*
|
||||
throw std::runtime_error(
|
||||
throw Slic3r::RuntimeError(
|
||||
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
|
||||
"Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
|
||||
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
|
||||
|
@ -179,6 +183,11 @@ std::string AppConfig::load()
|
|||
|
||||
void AppConfig::save()
|
||||
{
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
if (!m_save_enabled)
|
||||
return;
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
// The config is first written to a file with a PID suffix and then moved
|
||||
// to avoid race conditions with multiple instances of Slic3r
|
||||
const auto path = config_path();
|
||||
|
|
|
@ -18,6 +18,9 @@ public:
|
|||
AppConfig() :
|
||||
m_dirty(false),
|
||||
m_orig_version(Semver::invalid()),
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
m_save_enabled(true),
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
m_legacy_datadir(false)
|
||||
{
|
||||
this->reset();
|
||||
|
@ -157,6 +160,10 @@ public:
|
|||
bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "swap_yz", swap); }
|
||||
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
void enable_save(bool enable) { m_save_enabled = enable; }
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
static const std::string SECTION_FILAMENTS;
|
||||
static const std::string SECTION_MATERIALS;
|
||||
|
||||
|
@ -183,6 +190,10 @@ private:
|
|||
bool m_dirty;
|
||||
// Original version found in the ini file before it was overwritten
|
||||
Semver m_orig_version;
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
// Whether or not calls to save() should take effect
|
||||
bool m_save_enabled;
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
// Whether the existing version is before system profiles & configuration updating
|
||||
bool m_legacy_datadir;
|
||||
};
|
||||
|
|
|
@ -75,6 +75,7 @@ BoundingBoxBase<PointClass>::merge(const PointClass &point)
|
|||
}
|
||||
}
|
||||
template void BoundingBoxBase<Point>::merge(const Point &point);
|
||||
template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
|
||||
template <class PointClass> void
|
||||
|
@ -101,6 +102,7 @@ BoundingBoxBase<PointClass>::merge(const BoundingBoxBase<PointClass> &bb)
|
|||
}
|
||||
}
|
||||
template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
|
||||
template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
|
||||
template <class PointClass> void
|
||||
|
@ -115,6 +117,7 @@ BoundingBox3Base<PointClass>::merge(const PointClass &point)
|
|||
this->defined = true;
|
||||
}
|
||||
}
|
||||
template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
|
||||
template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
|
||||
|
||||
template <class PointClass> void
|
||||
|
@ -147,6 +150,7 @@ BoundingBoxBase<PointClass>::size() const
|
|||
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1));
|
||||
}
|
||||
template Point BoundingBoxBase<Point>::size() const;
|
||||
template Vec2f BoundingBoxBase<Vec2f>::size() const;
|
||||
template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||
|
||||
template <class PointClass> PointClass
|
||||
|
@ -154,6 +158,7 @@ BoundingBox3Base<PointClass>::size() const
|
|||
{
|
||||
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2));
|
||||
}
|
||||
template Vec3f BoundingBox3Base<Vec3f>::size() const;
|
||||
template Vec3d BoundingBox3Base<Vec3d>::size() const;
|
||||
|
||||
template <class PointClass> double BoundingBoxBase<PointClass>::radius() const
|
||||
|
@ -200,6 +205,7 @@ BoundingBoxBase<PointClass>::center() const
|
|||
return (this->min + this->max) / 2;
|
||||
}
|
||||
template Point BoundingBoxBase<Point>::center() const;
|
||||
template Vec2f BoundingBoxBase<Vec2f>::center() const;
|
||||
template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||
|
||||
template <class PointClass> PointClass
|
||||
|
@ -207,6 +213,7 @@ BoundingBox3Base<PointClass>::center() const
|
|||
{
|
||||
return (this->min + this->max) / 2;
|
||||
}
|
||||
template Vec3f BoundingBox3Base<Vec3f>::center() const;
|
||||
template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||
|
||||
template <class PointClass> coordf_t
|
||||
|
@ -215,6 +222,7 @@ BoundingBox3Base<PointClass>::max_size() const
|
|||
PointClass s = size();
|
||||
return std::max(s(0), std::max(s(1), s(2)));
|
||||
}
|
||||
template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
|
||||
template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||
|
||||
// Align a coordinate to a grid. The coordinate may be negative,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define slic3r_BoundingBox_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Exception.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
|
||||
|
@ -18,11 +19,13 @@ public:
|
|||
BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {}
|
||||
BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
|
||||
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
|
||||
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||
{
|
||||
if (points.empty()) {
|
||||
this->defined = false;
|
||||
// throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor");
|
||||
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
|
||||
} else {
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min = *it;
|
||||
|
@ -65,10 +68,12 @@ public:
|
|||
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
|
||||
BoundingBoxBase<PointClass>(pmin, pmax)
|
||||
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
|
||||
BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
BoundingBoxBase<PointClass>(p1, p1) { merge(p2); merge(p3); }
|
||||
BoundingBox3Base(const std::vector<PointClass>& points)
|
||||
{
|
||||
if (points.empty())
|
||||
throw std::invalid_argument("Empty point set supplied to BoundingBox3Base constructor");
|
||||
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min = *it;
|
||||
this->max = *it;
|
||||
|
@ -109,24 +114,32 @@ extern template void BoundingBoxBase<Vec3d>::scale(double factor);
|
|||
extern template void BoundingBoxBase<Point>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Point>::merge(const Point &point);
|
||||
extern template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
extern template void BoundingBoxBase<Point>::merge(const Points &points);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||
extern template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
|
||||
extern template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
extern template Point BoundingBoxBase<Point>::size() const;
|
||||
extern template Vec2f BoundingBoxBase<Vec2f>::size() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||
extern template double BoundingBoxBase<Point>::radius() const;
|
||||
extern template double BoundingBoxBase<Vec2d>::radius() const;
|
||||
extern template Point BoundingBoxBase<Point>::center() const;
|
||||
extern template Vec2f BoundingBoxBase<Vec2f>::center() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||
extern template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
|
||||
extern template Vec3f BoundingBox3Base<Vec3f>::size() const;
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::size() const;
|
||||
extern template double BoundingBox3Base<Vec3d>::radius() const;
|
||||
extern template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
|
||||
extern template Vec3f BoundingBox3Base<Vec3f>::center() const;
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||
|
||||
class BoundingBox : public BoundingBoxBase<Point>
|
||||
|
|
|
@ -46,6 +46,8 @@ add_library(libslic3r STATIC
|
|||
Fill/Fill.hpp
|
||||
Fill/Fill3DHoneycomb.cpp
|
||||
Fill/Fill3DHoneycomb.hpp
|
||||
Fill/FillAdaptive.cpp
|
||||
Fill/FillAdaptive.hpp
|
||||
Fill/FillBase.cpp
|
||||
Fill/FillBase.hpp
|
||||
Fill/FillConcentric.cpp
|
||||
|
@ -215,7 +217,9 @@ add_library(libslic3r STATIC
|
|||
SimplifyMeshImpl.hpp
|
||||
SimplifyMesh.cpp
|
||||
MarchingSquares.hpp
|
||||
Optimizer.hpp
|
||||
Optimize/Optimizer.hpp
|
||||
Optimize/NLoptOptimizer.hpp
|
||||
Optimize/BruteforceOptimizer.hpp
|
||||
${OpenVDBUtils_SOURCES}
|
||||
SLA/Pad.hpp
|
||||
SLA/Pad.cpp
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <exception> // std::runtime_error
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/erase.hpp>
|
||||
|
@ -218,7 +217,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
|
|||
case coInts: return new ConfigOptionIntsNullable();
|
||||
case coPercents: return new ConfigOptionPercentsNullable();
|
||||
case coBools: return new ConfigOptionBoolsNullable();
|
||||
default: throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label);
|
||||
default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
|
||||
}
|
||||
} else {
|
||||
switch (this->type) {
|
||||
|
@ -238,7 +237,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
|
|||
case coBool: return new ConfigOptionBool();
|
||||
case coBools: return new ConfigOptionBools();
|
||||
case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
|
||||
default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label);
|
||||
default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -535,7 +534,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
|
|||
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()");
|
||||
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
|
||||
}
|
||||
|
||||
// Return an absolute value of a possibly relative config variable.
|
||||
|
@ -546,7 +545,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
|
|||
const ConfigOption *raw_opt = this->option(opt_key);
|
||||
assert(raw_opt != nullptr);
|
||||
if (raw_opt->type() != coFloatOrPercent)
|
||||
throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
|
||||
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
|
||||
// Compute absolute value.
|
||||
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
|
||||
}
|
||||
|
@ -609,7 +608,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
|
|||
std::getline(ifs, firstline);
|
||||
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
|
||||
strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
|
||||
throw std::runtime_error("Not a PrusaSlicer / Slic3r PE generated g-code.");
|
||||
throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
|
||||
}
|
||||
ifs.seekg(0, ifs.end);
|
||||
auto file_length = ifs.tellg();
|
||||
|
@ -621,7 +620,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
|
|||
|
||||
size_t key_value_pairs = load_from_gcode_string(data.data());
|
||||
if (key_value_pairs < 80)
|
||||
throw std::runtime_error(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
|
||||
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
|
||||
}
|
||||
|
||||
// Load the config keys from the given string.
|
||||
|
@ -750,7 +749,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
|
|||
throw NoDefinitionException(opt_key);
|
||||
const ConfigOptionDef *optdef = def->get(opt_key);
|
||||
if (optdef == nullptr)
|
||||
// throw std::runtime_error(std::string("Invalid option name: ") + opt_key);
|
||||
// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
|
||||
// Let the parent decide what to do if the opt_key is not defined by this->def().
|
||||
return nullptr;
|
||||
ConfigOption *opt = optdef->create_default_option();
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <vector>
|
||||
#include "libslic3r.h"
|
||||
#include "clonable_ptr.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
|
@ -34,31 +35,31 @@ extern bool unescape_string_cstyle(const std::string &str, std::string &
|
|||
extern bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &out);
|
||||
|
||||
/// Specialization of std::exception to indicate that an unknown config option has been encountered.
|
||||
class UnknownOptionException : public std::runtime_error {
|
||||
class UnknownOptionException : public Slic3r::RuntimeError {
|
||||
public:
|
||||
UnknownOptionException() :
|
||||
std::runtime_error("Unknown option exception") {}
|
||||
Slic3r::RuntimeError("Unknown option exception") {}
|
||||
UnknownOptionException(const std::string &opt_key) :
|
||||
std::runtime_error(std::string("Unknown option exception: ") + opt_key) {}
|
||||
Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
|
||||
};
|
||||
|
||||
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
|
||||
class NoDefinitionException : public std::runtime_error
|
||||
class NoDefinitionException : public Slic3r::RuntimeError
|
||||
{
|
||||
public:
|
||||
NoDefinitionException() :
|
||||
std::runtime_error("No definition exception") {}
|
||||
Slic3r::RuntimeError("No definition exception") {}
|
||||
NoDefinitionException(const std::string &opt_key) :
|
||||
std::runtime_error(std::string("No definition exception: ") + opt_key) {}
|
||||
Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
|
||||
};
|
||||
|
||||
/// Indicate that an unsupported accessor was called on a config option.
|
||||
class BadOptionTypeException : public std::runtime_error
|
||||
class BadOptionTypeException : public Slic3r::RuntimeError
|
||||
{
|
||||
public:
|
||||
BadOptionTypeException() : std::runtime_error("Bad option type exception") {}
|
||||
BadOptionTypeException(const std::string &message) : std::runtime_error(message) {}
|
||||
BadOptionTypeException(const char* message) : std::runtime_error(message) {}
|
||||
BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
|
||||
BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
|
||||
BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
|
||||
};
|
||||
|
||||
// Type of a configuration value.
|
||||
|
@ -167,7 +168,7 @@ public:
|
|||
void set(const ConfigOption *rhs) override
|
||||
{
|
||||
if (rhs->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type");
|
||||
throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
|
||||
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
|
||||
}
|
||||
|
@ -175,7 +176,7 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types");
|
||||
throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
|
||||
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
|
||||
}
|
||||
|
@ -239,7 +240,7 @@ public:
|
|||
void set(const ConfigOption *rhs) override
|
||||
{
|
||||
if (rhs->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type");
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
|
||||
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
|
||||
}
|
||||
|
@ -256,12 +257,12 @@ public:
|
|||
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");
|
||||
throw Slic3r::RuntimeError("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");
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,12 +281,12 @@ public:
|
|||
// 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");
|
||||
throw Slic3r::RuntimeError("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");
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
|
||||
}
|
||||
|
||||
const T& get_at(size_t i) const
|
||||
|
@ -310,9 +311,9 @@ public:
|
|||
else if (n > this->values.size()) {
|
||||
if (this->values.empty()) {
|
||||
if (opt_default == nullptr)
|
||||
throw std::runtime_error("ConfigOptionVector::resize(): No default value provided.");
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided.");
|
||||
if (opt_default->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type.");
|
||||
throw Slic3r::RuntimeError("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.
|
||||
|
@ -329,7 +330,7 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionVector: Comparing incompatible types");
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
|
||||
return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
|
||||
}
|
||||
|
@ -341,9 +342,9 @@ public:
|
|||
// An option overrides another option if it is not nil and not equal.
|
||||
bool overriden_by(const ConfigOption *rhs) const override {
|
||||
if (this->nullable())
|
||||
throw std::runtime_error("Cannot override a nullable ConfigOption.");
|
||||
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
|
||||
if (rhs->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionVector.overriden_by() applied to different types.");
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
|
||||
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
|
||||
if (! rhs->nullable())
|
||||
// Overridding a non-nullable object with another non-nullable object.
|
||||
|
@ -361,9 +362,9 @@ public:
|
|||
// Apply an override option, possibly a nullable one.
|
||||
bool apply_override(const ConfigOption *rhs) override {
|
||||
if (this->nullable())
|
||||
throw std::runtime_error("Cannot override a nullable ConfigOption.");
|
||||
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
|
||||
if (rhs->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types.");
|
||||
throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
|
||||
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
|
||||
if (! rhs->nullable()) {
|
||||
// Overridding a non-nullable object with another non-nullable object.
|
||||
|
@ -452,7 +453,7 @@ public:
|
|||
bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
|
||||
bool operator==(const ConfigOption &rhs) const override {
|
||||
if (rhs.type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types");
|
||||
throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs));
|
||||
return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values);
|
||||
}
|
||||
|
@ -499,7 +500,7 @@ public:
|
|||
if (NULLABLE)
|
||||
this->values.push_back(nil_value());
|
||||
else
|
||||
throw std::runtime_error("Deserializing nil into a non-nullable object");
|
||||
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
|
||||
} else {
|
||||
std::istringstream iss(item_str);
|
||||
double value;
|
||||
|
@ -524,9 +525,9 @@ protected:
|
|||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw std::runtime_error("Serializing NaN");
|
||||
throw Slic3r::RuntimeError("Serializing NaN");
|
||||
} else
|
||||
throw std::runtime_error("Serializing invalid number");
|
||||
throw Slic3r::RuntimeError("Serializing invalid number");
|
||||
}
|
||||
static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
|
||||
if (NULLABLE) {
|
||||
|
@ -645,7 +646,7 @@ public:
|
|||
if (NULLABLE)
|
||||
this->values.push_back(nil_value());
|
||||
else
|
||||
throw std::runtime_error("Deserializing nil into a non-nullable object");
|
||||
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
|
||||
} else {
|
||||
std::istringstream iss(item_str);
|
||||
int value;
|
||||
|
@ -662,7 +663,7 @@ private:
|
|||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw std::runtime_error("Serializing NaN");
|
||||
throw Slic3r::RuntimeError("Serializing NaN");
|
||||
} else
|
||||
ss << v;
|
||||
}
|
||||
|
@ -847,7 +848,7 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionFloatOrPercent: Comparing incompatible types");
|
||||
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
|
||||
return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
|
||||
}
|
||||
|
@ -858,7 +859,7 @@ public:
|
|||
|
||||
void set(const ConfigOption *rhs) override {
|
||||
if (rhs->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type");
|
||||
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
|
||||
*this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
|
||||
}
|
||||
|
@ -1126,7 +1127,7 @@ public:
|
|||
if (NULLABLE)
|
||||
this->values.push_back(nil_value());
|
||||
else
|
||||
throw std::runtime_error("Deserializing nil into a non-nullable object");
|
||||
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
|
||||
} else
|
||||
this->values.push_back(item_str.compare("1") == 0);
|
||||
}
|
||||
|
@ -1139,7 +1140,7 @@ protected:
|
|||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw std::runtime_error("Serializing NaN");
|
||||
throw Slic3r::RuntimeError("Serializing NaN");
|
||||
} else
|
||||
ss << (v ? "1" : "0");
|
||||
}
|
||||
|
@ -1175,14 +1176,14 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionEnum<T>: Comparing incompatible types");
|
||||
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
return this->value == (T)rhs.getInt();
|
||||
}
|
||||
|
||||
void set(const ConfigOption *rhs) override {
|
||||
if (rhs->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionEnum<T>: Assigning an incompatible type");
|
||||
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
this->value = (T)rhs->getInt();
|
||||
}
|
||||
|
@ -1259,14 +1260,14 @@ public:
|
|||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionEnumGeneric: Comparing incompatible types");
|
||||
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
return this->value == rhs.getInt();
|
||||
}
|
||||
|
||||
void set(const ConfigOption *rhs) override {
|
||||
if (rhs->type() != this->type())
|
||||
throw std::runtime_error("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
this->value = rhs->getInt();
|
||||
}
|
||||
|
@ -1321,7 +1322,7 @@ public:
|
|||
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
|
||||
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
|
||||
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
|
||||
default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
|
||||
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
|
||||
}
|
||||
} else {
|
||||
switch (this->type) {
|
||||
|
@ -1340,7 +1341,7 @@ public:
|
|||
case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; }
|
||||
case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; }
|
||||
case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
|
||||
default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
|
||||
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1352,7 +1353,7 @@ public:
|
|||
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
|
||||
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
|
||||
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
|
||||
default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
|
||||
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
|
||||
}
|
||||
} else {
|
||||
switch (this->type) {
|
||||
|
@ -1371,7 +1372,7 @@ public:
|
|||
case coBool: archive(*static_cast<const ConfigOptionBool*>(opt)); break;
|
||||
case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break;
|
||||
case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); break;
|
||||
default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
|
||||
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
|
||||
}
|
||||
}
|
||||
// Make the compiler happy, shut up the warnings.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Line.hpp"
|
||||
|
@ -435,7 +436,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const
|
|||
std::list<TPPLPoly> output;
|
||||
int res = TPPLPartition().Triangulate_MONO(&input, &output);
|
||||
if (res != 1)
|
||||
throw std::runtime_error("Triangulation failed");
|
||||
throw Slic3r::RuntimeError("Triangulation failed");
|
||||
|
||||
// convert output polygons
|
||||
for (std::list<TPPLPoly>::iterator poly = output.begin(); poly != output.end(); ++poly) {
|
||||
|
@ -548,7 +549,7 @@ void ExPolygon::triangulate_pp(Points *triangles) const
|
|||
int res = TPPLPartition().Triangulate_MONO(&input, &output);
|
||||
// int TPPLPartition::Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles) {
|
||||
if (res != 1)
|
||||
throw std::runtime_error("Triangulation failed");
|
||||
throw Slic3r::RuntimeError("Triangulation failed");
|
||||
*triangles = polypartition_output_to_triangles(output);
|
||||
}
|
||||
|
||||
|
@ -591,7 +592,7 @@ void ExPolygon::triangulate_p2t(Polygons* polygons) const
|
|||
}
|
||||
polygons->push_back(p);
|
||||
}
|
||||
} catch (const std::runtime_error & /* err */) {
|
||||
} catch (const Slic3r::RuntimeError & /* err */) {
|
||||
assert(false);
|
||||
// just ignore, don't triangulate
|
||||
}
|
||||
|
|
|
@ -333,6 +333,14 @@ extern std::list<TPPLPoly> expoly_to_polypartition_input(const ExPolygons &expp)
|
|||
extern std::list<TPPLPoly> expoly_to_polypartition_input(const ExPolygon &ex);
|
||||
extern std::vector<Point> polypartition_output_to_triangles(const std::list<TPPLPoly> &output);
|
||||
|
||||
inline double area(const ExPolygons &polys)
|
||||
{
|
||||
double s = 0.;
|
||||
for (auto &p : polys) s += p.area();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// start Boost
|
||||
|
|
28
src/libslic3r/Exception.hpp
Normal file
28
src/libslic3r/Exception.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef _libslic3r_Exception_h_
|
||||
#define _libslic3r_Exception_h_
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// PrusaSlicer's own exception hierarchy is derived from std::runtime_error.
|
||||
// Base for Slicer's own exceptions.
|
||||
class Exception : public std::runtime_error { using std::runtime_error::runtime_error; };
|
||||
#define SLIC3R_DERIVE_EXCEPTION(DERIVED_EXCEPTION, PARENT_EXCEPTION) \
|
||||
class DERIVED_EXCEPTION : public PARENT_EXCEPTION { using PARENT_EXCEPTION::PARENT_EXCEPTION; }
|
||||
// Critical exception produced by Slicer, such exception shall never propagate up to the UI thread.
|
||||
// If that happens, an ugly fat message box with an ugly fat exclamation mark is displayed.
|
||||
SLIC3R_DERIVE_EXCEPTION(CriticalException, Exception);
|
||||
SLIC3R_DERIVE_EXCEPTION(RuntimeError, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(LogicError, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError);
|
||||
SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError);
|
||||
SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError);
|
||||
// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications.
|
||||
SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception);
|
||||
#undef SLIC3R_DERIVE_EXCEPTION
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // _libslic3r_Exception_h_
|
|
@ -2,6 +2,7 @@
|
|||
#define slic3r_ExtrusionEntityCollection_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Exception.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -107,7 +108,7 @@ public:
|
|||
|
||||
// Following methods shall never be called on an ExtrusionEntityCollection.
|
||||
Polyline as_polyline() const override {
|
||||
throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection");
|
||||
throw Slic3r::RuntimeError("Calling as_polyline() on a ExtrusionEntityCollection");
|
||||
return Polyline();
|
||||
};
|
||||
|
||||
|
@ -117,7 +118,7 @@ public:
|
|||
}
|
||||
|
||||
double length() const override {
|
||||
throw std::runtime_error("Calling length() on a ExtrusionEntityCollection");
|
||||
throw Slic3r::RuntimeError("Calling length() on a ExtrusionEntityCollection");
|
||||
return 0.;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,14 +10,14 @@
|
|||
namespace Slic3r {
|
||||
|
||||
// Generic file parser error, mostly copied from boost::property_tree::file_parser_error
|
||||
class file_parser_error: public std::runtime_error
|
||||
class file_parser_error: public Slic3r::RuntimeError
|
||||
{
|
||||
public:
|
||||
file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) :
|
||||
std::runtime_error(format_what(msg, file, line)),
|
||||
Slic3r::RuntimeError(format_what(msg, file, line)),
|
||||
m_message(msg), m_filename(file), m_line(line) {}
|
||||
file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) :
|
||||
std::runtime_error(format_what(msg, file.string(), line)),
|
||||
Slic3r::RuntimeError(format_what(msg, file.string(), line)),
|
||||
m_message(msg), m_filename(file.string()), m_line(line) {}
|
||||
// gcc 3.4.2 complains about lack of throw specifier on compiler
|
||||
// generated dtor
|
||||
|
@ -35,7 +35,7 @@ private:
|
|||
std::string m_filename;
|
||||
unsigned long m_line;
|
||||
|
||||
// Format error message to be returned by std::runtime_error::what()
|
||||
// Format error message to be returned by Slic3r::RuntimeError::what()
|
||||
static std::string format_what(const std::string &msg, const std::string &file, unsigned long l)
|
||||
{
|
||||
std::stringstream stream;
|
||||
|
|
|
@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
|
|||
#endif
|
||||
|
||||
// friend to Layer
|
||||
void Layer::make_fills()
|
||||
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree)
|
||||
{
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
layerm->fills.clear();
|
||||
|
@ -345,6 +345,7 @@ void Layer::make_fills()
|
|||
f->layer_id = this->id();
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;
|
||||
|
|
720
src/libslic3r/Fill/FillAdaptive.cpp
Normal file
720
src/libslic3r/Fill/FillAdaptive.cpp
Normal file
|
@ -0,0 +1,720 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "../Print.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
|
||||
#include "FillAdaptive.hpp"
|
||||
|
||||
// for indexed_triangle_set
|
||||
#include <admesh/stl.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
|
||||
// Boost pool: Don't use mutexes to synchronize memory allocation.
|
||||
#define BOOST_POOL_NO_MT
|
||||
#include <boost/pool/object_pool.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace FillAdaptive {
|
||||
|
||||
// Derived from https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp
|
||||
// The AABB-Triangle test implementation is based on the pseudo-code in
|
||||
// Christer Ericson's Real-Time Collision Detection, pp. 169-172. It is
|
||||
// practically a standard SAT test.
|
||||
//
|
||||
// Original MathGeoLib benchmark:
|
||||
// Best: 17.282 nsecs / 46.496 ticks, Avg: 17.804 nsecs, Worst: 18.434 nsecs
|
||||
//
|
||||
//FIXME Vojtech: The MathGeoLib contains a vectorized implementation.
|
||||
template<typename Vector>
|
||||
bool triangle_AABB_intersects(const Vector &a, const Vector &b, const Vector &c, const BoundingBoxBase<Vector> &aabb)
|
||||
{
|
||||
using Scalar = typename Vector::Scalar;
|
||||
|
||||
Vector tMin = a.cwiseMin(b.cwiseMin(c));
|
||||
Vector tMax = a.cwiseMax(b.cwiseMax(c));
|
||||
|
||||
if (tMin.x() >= aabb.max.x() || tMax.x() <= aabb.min.x()
|
||||
|| tMin.y() >= aabb.max.y() || tMax.y() <= aabb.min.y()
|
||||
|| tMin.z() >= aabb.max.z() || tMax.z() <= aabb.min.z())
|
||||
return false;
|
||||
|
||||
Vector center = (aabb.min + aabb.max) * 0.5f;
|
||||
Vector h = aabb.max - center;
|
||||
|
||||
const Vector t[3] { b-a, c-a, c-b };
|
||||
|
||||
Vector ac = a - center;
|
||||
|
||||
Vector n = t[0].cross(t[1]);
|
||||
Scalar s = n.dot(ac);
|
||||
Scalar r = std::abs(h.dot(n.cwiseAbs()));
|
||||
if (abs(s) >= r)
|
||||
return false;
|
||||
|
||||
const Vector at[3] = { t[0].cwiseAbs(), t[1].cwiseAbs(), t[2].cwiseAbs() };
|
||||
|
||||
Vector bc = b - center;
|
||||
Vector cc = c - center;
|
||||
|
||||
// SAT test all cross-axes.
|
||||
// The following is a fully unrolled loop of this code, stored here for reference:
|
||||
/*
|
||||
Scalar d1, d2, a1, a2;
|
||||
const Vector e[3] = { DIR_VEC(1, 0, 0), DIR_VEC(0, 1, 0), DIR_VEC(0, 0, 1) };
|
||||
for(int i = 0; i < 3; ++i)
|
||||
for(int j = 0; j < 3; ++j)
|
||||
{
|
||||
Vector axis = Cross(e[i], t[j]);
|
||||
ProjectToAxis(axis, d1, d2);
|
||||
aabb.ProjectToAxis(axis, a1, a2);
|
||||
if (d2 <= a1 || d1 >= a2) return false;
|
||||
}
|
||||
*/
|
||||
|
||||
// eX <cross> t[0]
|
||||
Scalar d1 = t[0].y() * ac.z() - t[0].z() * ac.y();
|
||||
Scalar d2 = t[0].y() * cc.z() - t[0].z() * cc.y();
|
||||
Scalar tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.y() * at[0].z() + h.z() * at[0].y());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// eX <cross> t[1]
|
||||
d1 = t[1].y() * ac.z() - t[1].z() * ac.y();
|
||||
d2 = t[1].y() * bc.z() - t[1].z() * bc.y();
|
||||
tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.y() * at[1].z() + h.z() * at[1].y());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// eX <cross> t[2]
|
||||
d1 = t[2].y() * ac.z() - t[2].z() * ac.y();
|
||||
d2 = t[2].y() * bc.z() - t[2].z() * bc.y();
|
||||
tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.y() * at[2].z() + h.z() * at[2].y());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// eY <cross> t[0]
|
||||
d1 = t[0].z() * ac.x() - t[0].x() * ac.z();
|
||||
d2 = t[0].z() * cc.x() - t[0].x() * cc.z();
|
||||
tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.x() * at[0].z() + h.z() * at[0].x());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// eY <cross> t[1]
|
||||
d1 = t[1].z() * ac.x() - t[1].x() * ac.z();
|
||||
d2 = t[1].z() * bc.x() - t[1].x() * bc.z();
|
||||
tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.x() * at[1].z() + h.z() * at[1].x());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// eY <cross> t[2]
|
||||
d1 = t[2].z() * ac.x() - t[2].x() * ac.z();
|
||||
d2 = t[2].z() * bc.x() - t[2].x() * bc.z();
|
||||
tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.x() * at[2].z() + h.z() * at[2].x());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// eZ <cross> t[0]
|
||||
d1 = t[0].x() * ac.y() - t[0].y() * ac.x();
|
||||
d2 = t[0].x() * cc.y() - t[0].y() * cc.x();
|
||||
tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.y() * at[0].x() + h.x() * at[0].y());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// eZ <cross> t[1]
|
||||
d1 = t[1].x() * ac.y() - t[1].y() * ac.x();
|
||||
d2 = t[1].x() * bc.y() - t[1].y() * bc.x();
|
||||
tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.y() * at[1].x() + h.x() * at[1].y());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// eZ <cross> t[2]
|
||||
d1 = t[2].x() * ac.y() - t[2].y() * ac.x();
|
||||
d2 = t[2].x() * bc.y() - t[2].y() * bc.x();
|
||||
tc = (d1 + d2) * 0.5f;
|
||||
r = std::abs(h.y() * at[2].x() + h.x() * at[2].y());
|
||||
if (r + std::abs(tc - d1) < std::abs(tc))
|
||||
return false;
|
||||
|
||||
// No separating axis exists, the AABB and triangle intersect.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ordering of children cubes.
|
||||
static const std::array<Vec3d, 8> child_centers {
|
||||
Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1),
|
||||
Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1)
|
||||
};
|
||||
|
||||
// Traversal order of octree children cells for three infill directions,
|
||||
// so that a single line will be discretized in a strictly monotonous order.
|
||||
static constexpr std::array<std::array<int, 8>, 3> child_traversal_order {
|
||||
std::array<int, 8>{ 2, 3, 0, 1, 6, 7, 4, 5 },
|
||||
std::array<int, 8>{ 4, 0, 6, 2, 5, 1, 7, 3 },
|
||||
std::array<int, 8>{ 1, 5, 0, 4, 3, 7, 2, 6 },
|
||||
};
|
||||
|
||||
struct Cube
|
||||
{
|
||||
Vec3d center;
|
||||
#ifndef NDEBUG
|
||||
Vec3d center_octree;
|
||||
#endif // NDEBUG
|
||||
std::array<Cube*, 8> children {}; // initialized to nullptrs
|
||||
Cube(const Vec3d ¢er) : center(center) {}
|
||||
};
|
||||
|
||||
struct CubeProperties
|
||||
{
|
||||
double edge_length; // Lenght of edge of a cube
|
||||
double height; // Height of rotated cube (standing on the corner)
|
||||
double diagonal_length; // Length of diagonal of a cube a face
|
||||
double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created
|
||||
double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created
|
||||
};
|
||||
|
||||
struct Octree
|
||||
{
|
||||
// Octree will allocate its Cubes from the pool. The pool only supports deletion of the complete pool,
|
||||
// perfect for building up our octree.
|
||||
boost::object_pool<Cube> pool;
|
||||
Cube* root_cube { nullptr };
|
||||
Vec3d origin;
|
||||
std::vector<CubeProperties> cubes_properties;
|
||||
|
||||
Octree(const Vec3d &origin, const std::vector<CubeProperties> &cubes_properties)
|
||||
: root_cube(pool.construct(origin)), origin(origin), cubes_properties(cubes_properties) {}
|
||||
|
||||
void insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth);
|
||||
};
|
||||
|
||||
void OctreeDeleter::operator()(Octree *p) {
|
||||
delete p;
|
||||
}
|
||||
|
||||
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object)
|
||||
{
|
||||
// Output, spacing for icAdaptiveCubic and icSupportCubic
|
||||
double adaptive_line_spacing = 0.;
|
||||
double support_line_spacing = 0.;
|
||||
|
||||
enum class Tristate {
|
||||
Yes,
|
||||
No,
|
||||
Maybe
|
||||
};
|
||||
struct RegionFillData {
|
||||
Tristate has_adaptive_infill;
|
||||
Tristate has_support_infill;
|
||||
double density;
|
||||
double extrusion_width;
|
||||
};
|
||||
std::vector<RegionFillData> region_fill_data;
|
||||
region_fill_data.reserve(print_object.print()->regions().size());
|
||||
bool build_octree = false;
|
||||
for (const PrintRegion *region : print_object.print()->regions()) {
|
||||
const PrintRegionConfig &config = region->config();
|
||||
bool nonempty = config.fill_density > 0;
|
||||
bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic;
|
||||
bool has_support_infill = nonempty && config.fill_pattern == ipSupportCubic;
|
||||
region_fill_data.push_back(RegionFillData({
|
||||
has_adaptive_infill ? Tristate::Maybe : Tristate::No,
|
||||
has_support_infill ? Tristate::Maybe : Tristate::No,
|
||||
config.fill_density,
|
||||
config.infill_extrusion_width
|
||||
}));
|
||||
build_octree |= has_adaptive_infill || has_support_infill;
|
||||
}
|
||||
|
||||
if (build_octree) {
|
||||
// Compute the average of above parameters over all layers
|
||||
for (const Layer *layer : print_object.layers())
|
||||
for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) {
|
||||
RegionFillData &rd = region_fill_data[region_id];
|
||||
if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
|
||||
rd.has_adaptive_infill = Tristate::Yes;
|
||||
if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
|
||||
rd.has_support_infill = Tristate::Yes;
|
||||
}
|
||||
|
||||
double adaptive_fill_density = 0.;
|
||||
double adaptive_infill_extrusion_width = 0.;
|
||||
int adaptive_cnt = 0;
|
||||
double support_fill_density = 0.;
|
||||
double support_infill_extrusion_width = 0.;
|
||||
int support_cnt = 0;
|
||||
|
||||
for (const RegionFillData &rd : region_fill_data) {
|
||||
if (rd.has_adaptive_infill == Tristate::Yes) {
|
||||
adaptive_fill_density += rd.density;
|
||||
adaptive_infill_extrusion_width += rd.extrusion_width;
|
||||
++ adaptive_cnt;
|
||||
} else if (rd.has_support_infill == Tristate::Yes) {
|
||||
support_fill_density += rd.density;
|
||||
support_infill_extrusion_width += rd.extrusion_width;
|
||||
++ support_cnt;
|
||||
}
|
||||
}
|
||||
|
||||
auto to_line_spacing = [](int cnt, double density, double extrusion_width) {
|
||||
if (cnt) {
|
||||
density /= double(cnt);
|
||||
extrusion_width /= double(cnt);
|
||||
return extrusion_width / ((density / 100.0f) * 0.333333333f);
|
||||
} else
|
||||
return 0.;
|
||||
};
|
||||
adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width);
|
||||
support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width);
|
||||
}
|
||||
|
||||
return std::make_pair(adaptive_line_spacing, support_line_spacing);
|
||||
}
|
||||
|
||||
// Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion
|
||||
// (Digital Differential Analyzer).
|
||||
struct FillContext
|
||||
{
|
||||
// The angles have to agree with child_traversal_order.
|
||||
static constexpr double direction_angles[3] {
|
||||
0.,
|
||||
(2.0 * M_PI) / 3.0,
|
||||
-(2.0 * M_PI) / 3.0
|
||||
};
|
||||
|
||||
FillContext(const Octree &octree, double z_position, int direction_idx) :
|
||||
origin_world(octree.origin),
|
||||
cubes_properties(octree.cubes_properties),
|
||||
z_position(z_position),
|
||||
traversal_order(child_traversal_order[direction_idx]),
|
||||
cos_a(cos(direction_angles[direction_idx])),
|
||||
sin_a(sin(direction_angles[direction_idx]))
|
||||
{
|
||||
static constexpr auto unused = std::numeric_limits<coord_t>::max();
|
||||
temp_lines.assign((1 << octree.cubes_properties.size()) - 1, Line(Point(unused, unused), Point(unused, unused)));
|
||||
}
|
||||
|
||||
// Rotate the point, uses the same convention as Point::rotate().
|
||||
Vec2d rotate(const Vec2d& v) { return Vec2d(this->cos_a * v.x() - this->sin_a * v.y(), this->sin_a * v.x() + this->cos_a * v.y()); }
|
||||
|
||||
// Center of the root cube in the Octree coordinate system.
|
||||
const Vec3d origin_world;
|
||||
const std::vector<CubeProperties> &cubes_properties;
|
||||
// Top of the current layer.
|
||||
const double z_position;
|
||||
// Order of traversal for this line direction.
|
||||
const std::array<int, 8> traversal_order;
|
||||
// Rotation of the generated line for this line direction.
|
||||
const double cos_a;
|
||||
const double sin_a;
|
||||
|
||||
// Linearized tree spanning a single Octree wall, used to connect lines spanning
|
||||
// neighboring Octree cells. Unused lines have the Line::a::x set to infinity.
|
||||
std::vector<Line> temp_lines;
|
||||
// Final output
|
||||
std::vector<Line> output_lines;
|
||||
};
|
||||
|
||||
static constexpr double octree_rot[3] = { 5.0 * M_PI / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0 };
|
||||
|
||||
Eigen::Quaterniond transform_to_world()
|
||||
{
|
||||
return Eigen::AngleAxisd(octree_rot[2], Vec3d::UnitZ()) * Eigen::AngleAxisd(octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(octree_rot[0], Vec3d::UnitX());
|
||||
}
|
||||
|
||||
Eigen::Quaterniond transform_to_octree()
|
||||
{
|
||||
return Eigen::AngleAxisd(- octree_rot[0], Vec3d::UnitX()) * Eigen::AngleAxisd(- octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(- octree_rot[2], Vec3d::UnitZ());
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify that the traversal order of the octree children matches the line direction,
|
||||
// therefore the infill line may get extended with O(1) time & space complexity.
|
||||
static bool verify_traversal_order(
|
||||
FillContext &context,
|
||||
const Cube *cube,
|
||||
int depth,
|
||||
const Vec2d &line_from,
|
||||
const Vec2d &line_to)
|
||||
{
|
||||
std::array<Vec3d, 8> c;
|
||||
Eigen::Quaterniond to_world = transform_to_world();
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
int j = context.traversal_order[i];
|
||||
Vec3d cntr = to_world * (cube->center_octree + (child_centers[j] * (context.cubes_properties[depth].edge_length / 4.)));
|
||||
assert(!cube->children[j] || cube->children[j]->center.isApprox(cntr));
|
||||
c[i] = cntr;
|
||||
}
|
||||
std::array<Vec3d, 10> dirs = {
|
||||
c[1] - c[0], c[2] - c[0], c[3] - c[1], c[3] - c[2], c[3] - c[0],
|
||||
c[5] - c[4], c[6] - c[4], c[7] - c[5], c[7] - c[6], c[7] - c[4]
|
||||
};
|
||||
assert(std::abs(dirs[4].z()) < 0.001);
|
||||
assert(std::abs(dirs[9].z()) < 0.001);
|
||||
assert(dirs[0].isApprox(dirs[3]));
|
||||
assert(dirs[1].isApprox(dirs[2]));
|
||||
assert(dirs[5].isApprox(dirs[8]));
|
||||
assert(dirs[6].isApprox(dirs[7]));
|
||||
Vec3d line_dir = Vec3d(line_to.x() - line_from.x(), line_to.y() - line_from.y(), 0.).normalized();
|
||||
for (auto& dir : dirs) {
|
||||
double d = dir.normalized().dot(line_dir);
|
||||
assert(d > 0.7);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
static void generate_infill_lines_recursive(
|
||||
FillContext &context,
|
||||
const Cube *cube,
|
||||
// Address of this wall in the octree, used to address context.temp_lines.
|
||||
int address,
|
||||
int depth)
|
||||
{
|
||||
assert(cube != nullptr);
|
||||
|
||||
const std::vector<CubeProperties> &cubes_properties = context.cubes_properties;
|
||||
const double z_diff = context.z_position - cube->center.z();
|
||||
const double z_diff_abs = std::abs(z_diff);
|
||||
|
||||
if (z_diff_abs > cubes_properties[depth].height / 2.)
|
||||
return;
|
||||
|
||||
if (z_diff_abs < cubes_properties[depth].line_z_distance) {
|
||||
// Discretize a single wall splitting the cube into two.
|
||||
const double zdist = cubes_properties[depth].line_z_distance;
|
||||
Vec2d from(
|
||||
0.5 * cubes_properties[depth].diagonal_length * (zdist - z_diff_abs) / zdist,
|
||||
cubes_properties[depth].line_xy_distance - (zdist + z_diff) / sqrt(2.));
|
||||
Vec2d to(-from.x(), from.y());
|
||||
from = context.rotate(from);
|
||||
to = context.rotate(to);
|
||||
// Relative to cube center
|
||||
Vec2d offset(cube->center.x() - context.origin_world.x(), cube->center.y() - context.origin_world.y());
|
||||
from += offset;
|
||||
to += offset;
|
||||
// Verify that the traversal order of the octree children matches the line direction,
|
||||
// therefore the infill line may get extended with O(1) time & space complexity.
|
||||
assert(verify_traversal_order(context, cube, depth, from, to));
|
||||
// Either extend an existing line or start a new one.
|
||||
Line &last_line = context.temp_lines[address];
|
||||
Line new_line(Point::new_scale(from), Point::new_scale(to));
|
||||
if (last_line.a.x() == std::numeric_limits<coord_t>::max()) {
|
||||
last_line.a = new_line.a;
|
||||
} else if ((new_line.a - last_line.b).cwiseAbs().maxCoeff() > 300) { // SCALED_EPSILON is 100 and it is not enough) {
|
||||
context.output_lines.emplace_back(last_line);
|
||||
last_line.a = new_line.a;
|
||||
}
|
||||
last_line.b = new_line.b;
|
||||
}
|
||||
|
||||
// left child index
|
||||
address = address * 2 + 1;
|
||||
-- depth;
|
||||
size_t i = 0;
|
||||
for (const int child_idx : context.traversal_order) {
|
||||
const Cube *child = cube->children[child_idx];
|
||||
if (child != nullptr)
|
||||
generate_infill_lines_recursive(context, child, address, depth);
|
||||
if (++ i == 4)
|
||||
// right child index
|
||||
++ address;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Collect the line segments.
|
||||
static Polylines chain_lines(const std::vector<Line> &lines, const double point_distance_epsilon)
|
||||
{
|
||||
// Create line end point lookup.
|
||||
struct LineEnd {
|
||||
LineEnd(Line *line, bool start) : line(line), start(start) {}
|
||||
Line *line;
|
||||
// Is it the start or end point?
|
||||
bool start;
|
||||
const Point& point() const { return start ? line->a : line->b; }
|
||||
const Point& other_point() const { return start ? line->b : line->a; }
|
||||
LineEnd other_end() const { return LineEnd(line, ! start); }
|
||||
bool operator==(const LineEnd &rhs) const { return this->line == rhs.line && this->start == rhs.start; }
|
||||
};
|
||||
struct LineEndAccessor {
|
||||
const Point* operator()(const LineEnd &pt) const { return &pt.point(); }
|
||||
};
|
||||
typedef ClosestPointInRadiusLookup<LineEnd, LineEndAccessor> ClosestPointLookupType;
|
||||
ClosestPointLookupType closest_end_point_lookup(point_distance_epsilon);
|
||||
for (const Line &line : lines) {
|
||||
closest_end_point_lookup.insert(LineEnd(&line, true));
|
||||
closest_end_point_lookup.insert(LineEnd(&line, false));
|
||||
}
|
||||
|
||||
// Chain the lines.
|
||||
std::vector<char> line_consumed(lines.size(), false);
|
||||
static const double point_distance_epsilon2 = point_distance_epsilon * point_distance_epsilon;
|
||||
Polylines out;
|
||||
for (const Line &seed : lines)
|
||||
if (! line_consumed[&seed - lines.data()]) {
|
||||
line_consumed[&seed - lines.data()] = true;
|
||||
closest_end_point_lookup.erase(LineEnd(&seed, false));
|
||||
closest_end_point_lookup.erase(LineEnd(&seed, true));
|
||||
Polyline pl { seed.a, seed.b };
|
||||
for (size_t round = 0; round < 2; ++ round) {
|
||||
for (;;) {
|
||||
auto [line_end, dist2] = closest_end_point_lookup.find(pl.last_point());
|
||||
if (line_end == nullptr || dist2 >= point_distance_epsilon2)
|
||||
// Cannot extent in this direction.
|
||||
break;
|
||||
// Average the last point.
|
||||
pl.points.back() = 0.5 * (pl.points.back() + line_end->point());
|
||||
// and extend with the new line segment.
|
||||
pl.points.emplace_back(line_end->other_point());
|
||||
closest_end_point_lookup.erase(line_end);
|
||||
closest_end_point_lookup.erase(line_end->other_end());
|
||||
line_consumed[line_end->line - lines.data()] = true;
|
||||
}
|
||||
// reverse and try the oter direction.
|
||||
pl.reverse();
|
||||
}
|
||||
out.emplace_back(std::move(pl));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
|
||||
#endif
|
||||
|
||||
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
|
||||
static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path)
|
||||
{
|
||||
BoundingBox bbox = get_extents(expoly);
|
||||
bbox.offset(scale_(3.));
|
||||
|
||||
::Slic3r::SVG svg(path, bbox);
|
||||
svg.draw(expoly);
|
||||
svg.draw_outline(expoly, "green");
|
||||
svg.draw(polylines, "red");
|
||||
static constexpr double trim_length = scale_(0.4);
|
||||
for (Polyline polyline : polylines) {
|
||||
Vec2d a = polyline.points.front().cast<double>();
|
||||
Vec2d d = polyline.points.back().cast<double>();
|
||||
if (polyline.size() == 2) {
|
||||
Vec2d v = d - a;
|
||||
double l = v.norm();
|
||||
if (l > 2. * trim_length) {
|
||||
a += v * trim_length / l;
|
||||
d -= v * trim_length / l;
|
||||
polyline.points.front() = a.cast<coord_t>();
|
||||
polyline.points.back() = d.cast<coord_t>();
|
||||
} else
|
||||
polyline.points.clear();
|
||||
} else if (polyline.size() > 2) {
|
||||
Vec2d b = polyline.points[1].cast<double>();
|
||||
Vec2d c = polyline.points[polyline.points.size() - 2].cast<double>();
|
||||
Vec2d v = b - a;
|
||||
double l = v.norm();
|
||||
if (l > trim_length) {
|
||||
a += v * trim_length / l;
|
||||
polyline.points.front() = a.cast<coord_t>();
|
||||
} else
|
||||
polyline.points.erase(polyline.points.begin());
|
||||
v = d - c;
|
||||
l = v.norm();
|
||||
if (l > trim_length)
|
||||
polyline.points.back() = (d - v * trim_length / l).cast<coord_t>();
|
||||
else
|
||||
polyline.points.pop_back();
|
||||
}
|
||||
svg.draw(polyline, "black");
|
||||
}
|
||||
}
|
||||
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
|
||||
|
||||
void Filler::_fill_surface_single(
|
||||
const FillParams & params,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon &expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
assert (this->adapt_fill_octree);
|
||||
|
||||
Polylines all_polylines;
|
||||
{
|
||||
// 3 contexts for three directions of infill lines
|
||||
std::array<FillContext, 3> contexts {
|
||||
FillContext { *adapt_fill_octree, this->z, 0 },
|
||||
FillContext { *adapt_fill_octree, this->z, 1 },
|
||||
FillContext { *adapt_fill_octree, this->z, 2 }
|
||||
};
|
||||
// Generate the infill lines along the octree cells, merge touching lines of the same direction.
|
||||
size_t num_lines = 0;
|
||||
for (auto &context : contexts) {
|
||||
generate_infill_lines_recursive(context, adapt_fill_octree->root_cube, 0, int(adapt_fill_octree->cubes_properties.size()) - 1);
|
||||
num_lines += context.output_lines.size() + context.temp_lines.size();
|
||||
}
|
||||
// Collect the lines.
|
||||
std::vector<Line> lines;
|
||||
lines.reserve(num_lines);
|
||||
for (auto &context : contexts) {
|
||||
append(lines, context.output_lines);
|
||||
for (const Line &line : context.temp_lines)
|
||||
if (line.a.x() != std::numeric_limits<coord_t>::max())
|
||||
lines.emplace_back(line);
|
||||
}
|
||||
// Convert lines to polylines.
|
||||
//FIXME chain the lines
|
||||
all_polylines.reserve(lines.size());
|
||||
std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; });
|
||||
}
|
||||
|
||||
// Crop all polylines
|
||||
all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon));
|
||||
|
||||
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++));
|
||||
}
|
||||
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
|
||||
|
||||
if (params.dont_connect)
|
||||
append(polylines_out, std::move(all_polylines));
|
||||
else
|
||||
connect_infill(chain_polylines(std::move(all_polylines)), expolygon, polylines_out, this->spacing, params);
|
||||
|
||||
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_infill_lines_to_svg(expolygon, polylines_out, debug_out_path("FillAdaptive-final-%d.svg", iRun ++));
|
||||
}
|
||||
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
|
||||
}
|
||||
|
||||
static double bbox_max_radius(const BoundingBoxf3 &bbox, const Vec3d ¢er)
|
||||
{
|
||||
const auto p = (bbox.min - center);
|
||||
const auto s = bbox.size();
|
||||
double r2max = 0.;
|
||||
for (int i = 0; i < 8; ++ i)
|
||||
r2max = std::max(r2max, (p + Vec3d(s.x() * double(i & 1), s.y() * double(i & 2), s.z() * double(i & 4))).squaredNorm());
|
||||
return sqrt(r2max);
|
||||
}
|
||||
|
||||
static std::vector<CubeProperties> make_cubes_properties(double max_cube_edge_length, double line_spacing)
|
||||
{
|
||||
max_cube_edge_length += EPSILON;
|
||||
|
||||
std::vector<CubeProperties> cubes_properties;
|
||||
for (double edge_length = line_spacing * 2.;; edge_length *= 2.)
|
||||
{
|
||||
CubeProperties props{};
|
||||
props.edge_length = edge_length;
|
||||
props.height = edge_length * sqrt(3);
|
||||
props.diagonal_length = edge_length * sqrt(2);
|
||||
props.line_z_distance = edge_length / sqrt(3);
|
||||
props.line_xy_distance = edge_length / sqrt(6);
|
||||
cubes_properties.emplace_back(props);
|
||||
if (edge_length > max_cube_edge_length)
|
||||
break;
|
||||
}
|
||||
return cubes_properties;
|
||||
}
|
||||
|
||||
static inline bool is_overhang_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &up)
|
||||
{
|
||||
// Calculate triangle normal.
|
||||
auto n = (b - a).cross(c - b);
|
||||
return n.dot(up) > 0.707 * n.norm();
|
||||
}
|
||||
|
||||
static void transform_center(Cube *current_cube, const Eigen::Matrix3d &rot)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
current_cube->center_octree = current_cube->center;
|
||||
#endif // NDEBUG
|
||||
current_cube->center = rot * current_cube->center;
|
||||
for (auto *child : current_cube->children)
|
||||
if (child)
|
||||
transform_center(child, rot);
|
||||
}
|
||||
|
||||
OctreePtr build_octree(const indexed_triangle_set &triangle_mesh, coordf_t line_spacing, bool support_overhangs_only)
|
||||
{
|
||||
assert(line_spacing > 0);
|
||||
assert(! std::isnan(line_spacing));
|
||||
|
||||
BoundingBox3Base<Vec3f> bbox(triangle_mesh.vertices);
|
||||
Vec3d cube_center = bbox.center().cast<double>();
|
||||
std::vector<CubeProperties> cubes_properties = make_cubes_properties(double(bbox.size().maxCoeff()), line_spacing);
|
||||
auto octree = OctreePtr(new Octree(cube_center, cubes_properties));
|
||||
|
||||
if (cubes_properties.size() > 1) {
|
||||
auto up_vector = support_overhangs_only ? Vec3d(transform_to_octree() * Vec3d(0., 0., 1.)) : Vec3d();
|
||||
for (auto &tri : triangle_mesh.indices) {
|
||||
auto a = triangle_mesh.vertices[tri[0]].cast<double>();
|
||||
auto b = triangle_mesh.vertices[tri[1]].cast<double>();
|
||||
auto c = triangle_mesh.vertices[tri[2]].cast<double>();
|
||||
if (support_overhangs_only && ! is_overhang_triangle(a, b, c, up_vector))
|
||||
continue;
|
||||
double edge_length_half = 0.5 * cubes_properties.back().edge_length;
|
||||
Vec3d diag_half(edge_length_half, edge_length_half, edge_length_half);
|
||||
octree->insert_triangle(
|
||||
a, b, c,
|
||||
octree->root_cube,
|
||||
BoundingBoxf3(octree->root_cube->center - diag_half, octree->root_cube->center + diag_half),
|
||||
int(cubes_properties.size()) - 1);
|
||||
}
|
||||
{
|
||||
// Transform the octree to world coordinates to reduce computation when extracting infill lines.
|
||||
auto rot = transform_to_world().toRotationMatrix();
|
||||
transform_center(octree->root_cube, rot);
|
||||
octree->origin = rot * octree->origin;
|
||||
}
|
||||
}
|
||||
|
||||
return octree;
|
||||
}
|
||||
|
||||
void Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth)
|
||||
{
|
||||
assert(current_cube);
|
||||
assert(depth > 0);
|
||||
|
||||
for (size_t i = 0; i < 8; ++ i) {
|
||||
const Vec3d &child_center = child_centers[i];
|
||||
// Calculate a slightly expanded bounding box of a child cube to cope with triangles touching a cube wall and other numeric errors.
|
||||
// We will rather densify the octree a bit more than necessary instead of missing a triangle.
|
||||
BoundingBoxf3 bbox;
|
||||
for (int k = 0; k < 3; ++ k) {
|
||||
if (child_center[k] == -1.) {
|
||||
bbox.min[k] = current_bbox.min[k];
|
||||
bbox.max[k] = current_cube->center[k] + EPSILON;
|
||||
} else {
|
||||
bbox.min[k] = current_cube->center[k] - EPSILON;
|
||||
bbox.max[k] = current_bbox.max[k];
|
||||
}
|
||||
}
|
||||
if (triangle_AABB_intersects(a, b, c, bbox)) {
|
||||
if (! current_cube->children[i])
|
||||
current_cube->children[i] = this->pool.construct(current_cube->center + (child_center * (this->cubes_properties[depth].edge_length / 4)));
|
||||
if (depth > 1)
|
||||
this->insert_triangle(a, b, c, current_cube->children[i], bbox, depth - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FillAdaptive
|
||||
} // namespace Slic3r
|
72
src/libslic3r/Fill/FillAdaptive.hpp
Normal file
72
src/libslic3r/Fill/FillAdaptive.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Adaptive cubic infill was inspired by the work of @mboerwinkle
|
||||
// as implemented for Cura.
|
||||
// https://github.com/Ultimaker/CuraEngine/issues/381
|
||||
// https://github.com/Ultimaker/CuraEngine/pull/401
|
||||
//
|
||||
// Our implementation is more accurate (discretizes a bit less cubes than Cura's)
|
||||
// by splitting only such cubes which contain a triangle.
|
||||
// Our line extraction is time optimal instead of O(n^2) when connecting extracted lines,
|
||||
// and we also implemented adaptivity for supporting internal overhangs only.
|
||||
|
||||
#ifndef slic3r_FillAdaptive_hpp_
|
||||
#define slic3r_FillAdaptive_hpp_
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
struct indexed_triangle_set;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
|
||||
namespace FillAdaptive
|
||||
{
|
||||
|
||||
struct Octree;
|
||||
// To keep the definition of Octree opaque, we have to define a custom deleter.
|
||||
struct OctreeDeleter { void operator()(Octree *p); };
|
||||
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
|
||||
|
||||
// Calculate line spacing for
|
||||
// 1) adaptive cubic infill
|
||||
// 2) adaptive internal support cubic infill
|
||||
// Returns zero for a particular infill type if no such infill is to be generated.
|
||||
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object);
|
||||
|
||||
// Rotation of the octree to stand on one of its corners.
|
||||
Eigen::Quaterniond transform_to_world();
|
||||
// Inverse roation of the above.
|
||||
Eigen::Quaterniond transform_to_octree();
|
||||
|
||||
FillAdaptive::OctreePtr build_octree(
|
||||
// Mesh is rotated to the coordinate system of the octree.
|
||||
const indexed_triangle_set &triangle_mesh,
|
||||
coordf_t line_spacing,
|
||||
// If true, octree is densified below internal overhangs only.
|
||||
bool support_overhangs_only);
|
||||
|
||||
//
|
||||
// Some of the algorithms used by class FillAdaptive were inspired by
|
||||
// Cura Engine's class SubDivCube
|
||||
// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h
|
||||
//
|
||||
class Filler : public Slic3r::Fill
|
||||
{
|
||||
public:
|
||||
virtual ~Filler() {}
|
||||
|
||||
protected:
|
||||
virtual Fill* clone() const { return new Filler(*this); };
|
||||
virtual void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon &expolygon,
|
||||
Polylines &polylines_out);
|
||||
virtual bool no_sort() const { return true; }
|
||||
};
|
||||
|
||||
}; // namespace FillAdaptive
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillAdaptive_hpp_
|
|
@ -16,6 +16,7 @@
|
|||
#include "FillRectilinear.hpp"
|
||||
#include "FillRectilinear2.hpp"
|
||||
#include "FillRectilinear3.hpp"
|
||||
#include "FillAdaptive.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -37,7 +38,9 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
|||
case ipArchimedeanChords: return new FillArchimedeanChords();
|
||||
case ipHilbertCurve: return new FillHilbertCurve();
|
||||
case ipOctagramSpiral: return new FillOctagramSpiral();
|
||||
default: throw std::invalid_argument("unknown type");
|
||||
case ipAdaptiveCubic: return new FillAdaptive::Filler();
|
||||
case ipSupportCubic: return new FillAdaptive::Filler();
|
||||
default: throw Slic3r::InvalidArgument("unknown type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -844,8 +847,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
|
|||
boundary.assign(boundary_src.holes.size() + 1, Points());
|
||||
boundary_data.assign(boundary_src.holes.size() + 1, std::vector<ContourPointData>());
|
||||
// Mapping the infill_ordered end point to a (contour, point) of boundary.
|
||||
std::vector<std::pair<size_t, size_t>> map_infill_end_point_to_boundary;
|
||||
map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair<size_t, size_t>(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()));
|
||||
std::vector<std::pair<size_t, size_t>> map_infill_end_point_to_boundary;
|
||||
static constexpr auto boundary_idx_unconnected = std::numeric_limits<size_t>::max();
|
||||
map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair<size_t, size_t>(boundary_idx_unconnected, boundary_idx_unconnected));
|
||||
{
|
||||
// Project the infill_ordered end points onto boundary_src.
|
||||
std::vector<std::pair<EdgeGrid::Grid::ClosestPointResult, size_t>> intersection_points;
|
||||
|
@ -895,13 +899,14 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
|
|||
contour_data.front().param = contour_data.back().param + (contour_dst.back().cast<float>() - contour_dst.front().cast<float>()).norm();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
assert(boundary.size() == boundary_src.num_contours());
|
||||
assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(),
|
||||
#if 0
|
||||
// Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary.
|
||||
assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(),
|
||||
[&boundary](const std::pair<size_t, size_t> &contour_point) {
|
||||
return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size();
|
||||
}));
|
||||
#endif /* NDEBUG */
|
||||
#endif
|
||||
}
|
||||
|
||||
// Mark the points and segments of split boundary as consumed if they are very close to some of the infill line.
|
||||
|
@ -932,9 +937,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
|
|||
const Polyline &pl2 = infill_ordered[idx_chain];
|
||||
const std::pair<size_t, size_t> *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1];
|
||||
const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2];
|
||||
const std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
|
||||
if (cp1->first == cp2->first) {
|
||||
if (cp1->first != boundary_idx_unconnected && cp1->first == cp2->first) {
|
||||
// End points on the same contour. Try to connect them.
|
||||
const std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
|
||||
float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param;
|
||||
float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param;
|
||||
float param_end = contour_data.front().param;
|
||||
|
@ -961,7 +966,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
|
|||
const std::pair<size_t, size_t> *cp1prev = cp1 - 1;
|
||||
const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2];
|
||||
const std::pair<size_t, size_t> *cp2next = cp2 + 1;
|
||||
assert(cp1->first == cp2->first);
|
||||
assert(cp1->first == cp2->first && cp1->first != boundary_idx_unconnected);
|
||||
std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
|
||||
if (connection_cost.reversed)
|
||||
std::swap(cp1, cp2);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "../libslic3r.h"
|
||||
#include "../BoundingBox.hpp"
|
||||
#include "../Exception.hpp"
|
||||
#include "../Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -19,9 +20,14 @@ class ExPolygon;
|
|||
class Surface;
|
||||
enum InfillPattern : int;
|
||||
|
||||
class InfillFailedException : public std::runtime_error {
|
||||
namespace FillAdaptive {
|
||||
struct Octree;
|
||||
};
|
||||
|
||||
// Infill shall never fail, therefore the error is classified as RuntimeError, not SlicingError.
|
||||
class InfillFailedException : public Slic3r::RuntimeError {
|
||||
public:
|
||||
InfillFailedException() : std::runtime_error("Infill failed") {}
|
||||
InfillFailedException() : Slic3r::RuntimeError("Infill failed") {}
|
||||
};
|
||||
|
||||
struct FillParams
|
||||
|
@ -69,6 +75,9 @@ public:
|
|||
// In scaled coordinates. Bounding box of the 2D projection of the object.
|
||||
BoundingBox bounding_box;
|
||||
|
||||
// Octree builds on mesh for usage in the adaptive cubic infill
|
||||
FillAdaptive::Octree* adapt_fill_octree = nullptr;
|
||||
|
||||
public:
|
||||
virtual ~Fill() {}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
|
|||
else if (opt_key == "support_material_extrusion_width")
|
||||
return frSupportMaterial;
|
||||
else
|
||||
throw std::runtime_error("opt_key_to_flow_role: invalid argument");
|
||||
throw Slic3r::RuntimeError("opt_key_to_flow_role: invalid argument");
|
||||
};
|
||||
|
||||
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
|
||||
|
@ -126,7 +126,7 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent
|
|||
{
|
||||
// we need layer height unless it's a bridge
|
||||
if (height <= 0 && bridge_flow_ratio == 0)
|
||||
throw std::invalid_argument("Invalid flow height supplied to new_from_config_width()");
|
||||
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
|
||||
|
||||
float w;
|
||||
if (bridge_flow_ratio > 0) {
|
||||
|
@ -151,7 +151,7 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height,
|
|||
{
|
||||
// we need layer height unless it's a bridge
|
||||
if (height <= 0 && !bridge)
|
||||
throw std::invalid_argument("Invalid flow height supplied to new_from_spacing()");
|
||||
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()");
|
||||
// Calculate width from spacing.
|
||||
// For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
|
||||
// For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "libslic3r.h"
|
||||
#include "Config.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -27,11 +28,11 @@ enum FlowRole {
|
|||
frSupportMaterialInterface,
|
||||
};
|
||||
|
||||
class FlowError : public std::invalid_argument
|
||||
class FlowError : public Slic3r::InvalidArgument
|
||||
{
|
||||
public:
|
||||
FlowError(const std::string& what_arg) : invalid_argument(what_arg) {}
|
||||
FlowError(const char* what_arg) : invalid_argument(what_arg) {}
|
||||
FlowError(const std::string& what_arg) : Slic3r::InvalidArgument(what_arg) {}
|
||||
FlowError(const char* what_arg) : Slic3r::InvalidArgument(what_arg) {}
|
||||
};
|
||||
|
||||
class FlowErrorNegativeSpacing : public FlowError
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "../libslic3r.h"
|
||||
#include "../Exception.hpp"
|
||||
#include "../Model.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "../GCode.hpp"
|
||||
|
@ -123,11 +124,11 @@ const char* INVALID_OBJECT_TYPES[] =
|
|||
"other"
|
||||
};
|
||||
|
||||
class version_error : public std::runtime_error
|
||||
class version_error : public Slic3r::FileIOError
|
||||
{
|
||||
public:
|
||||
version_error(const std::string& what_arg) : std::runtime_error(what_arg) {}
|
||||
version_error(const char* what_arg) : std::runtime_error(what_arg) {}
|
||||
version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {}
|
||||
version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {}
|
||||
};
|
||||
|
||||
const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key)
|
||||
|
@ -607,7 +608,7 @@ namespace Slic3r {
|
|||
{
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
throw std::runtime_error(e.what());
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -780,7 +781,7 @@ namespace Slic3r {
|
|||
{
|
||||
char error_buf[1024];
|
||||
::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser));
|
||||
throw std::runtime_error(error_buf);
|
||||
throw Slic3r::FileIOError(error_buf);
|
||||
}
|
||||
|
||||
return n;
|
||||
|
@ -789,7 +790,7 @@ namespace Slic3r {
|
|||
catch (const version_error& e)
|
||||
{
|
||||
// rethrow the exception
|
||||
throw std::runtime_error(e.what());
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -2360,9 +2361,9 @@ namespace Slic3r {
|
|||
continue;
|
||||
|
||||
if (!volume->mesh().repaired)
|
||||
throw std::runtime_error("store_3mf() requires repair()");
|
||||
throw Slic3r::FileIOError("store_3mf() requires repair()");
|
||||
if (!volume->mesh().has_shared_vertices())
|
||||
throw std::runtime_error("store_3mf() requires shared vertices");
|
||||
throw Slic3r::FileIOError("store_3mf() requires shared vertices");
|
||||
|
||||
volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Exception.hpp"
|
||||
#include "../Model.hpp"
|
||||
#include "../GCode.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
@ -923,7 +924,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
|
|||
{
|
||||
char error_buf[1024];
|
||||
::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser));
|
||||
throw std::runtime_error(error_buf);
|
||||
throw Slic3r::FileIOError(error_buf);
|
||||
}
|
||||
|
||||
return n;
|
||||
|
@ -948,9 +949,9 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
|
|||
if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE))
|
||||
{
|
||||
// std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
|
||||
// throw std::runtime_error(msg.c_str());
|
||||
// throw Slic3r::FileIOError(msg.c_str());
|
||||
const std::string msg = (boost::format(_(L("The selected amf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str();
|
||||
throw std::runtime_error(msg);
|
||||
throw Slic3r::FileIOError(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -994,7 +995,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
|
|||
{
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
throw std::runtime_error(e.what());
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -1147,9 +1148,9 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
|
|||
for (ModelVolume *volume : object->volumes) {
|
||||
vertices_offsets.push_back(num_vertices);
|
||||
if (! volume->mesh().repaired)
|
||||
throw std::runtime_error("store_amf() requires repair()");
|
||||
throw Slic3r::FileIOError("store_amf() requires repair()");
|
||||
if (! volume->mesh().has_shared_vertices())
|
||||
throw std::runtime_error("store_amf() requires shared vertices");
|
||||
throw Slic3r::FileIOError("store_amf() requires shared vertices");
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
const Transform3d& matrix = volume->get_matrix();
|
||||
for (size_t i = 0; i < its.vertices.size(); ++i) {
|
||||
|
|
|
@ -147,7 +147,7 @@ static void extract_model_from_archive(
|
|||
}
|
||||
}
|
||||
if (! trafo_set)
|
||||
throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name);
|
||||
throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name);
|
||||
|
||||
// Extract the STL.
|
||||
StlHeader header;
|
||||
|
@ -266,7 +266,7 @@ static void extract_model_from_archive(
|
|||
}
|
||||
|
||||
if (! mesh_valid)
|
||||
throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid mesh for " + name);
|
||||
throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid mesh for " + name);
|
||||
|
||||
// Add this mesh to the model.
|
||||
ModelVolume *volume = nullptr;
|
||||
|
@ -303,7 +303,7 @@ bool load_prus(const char *path, Model *model)
|
|||
mz_bool res = MZ_FALSE;
|
||||
try {
|
||||
if (!open_zip_reader(&archive, path))
|
||||
throw std::runtime_error(std::string("Unable to init zip reader for ") + path);
|
||||
throw Slic3r::FileIOError(std::string("Unable to init zip reader for ") + path);
|
||||
std::vector<char> scene_xml_data;
|
||||
// For grouping multiple STLs into a single ModelObject for multi-material prints.
|
||||
std::map<int, ModelObject*> group_to_model_object;
|
||||
|
@ -316,10 +316,10 @@ bool load_prus(const char *path, Model *model)
|
|||
buffer.assign((size_t)stat.m_uncomp_size, 0);
|
||||
res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||
if (res == MZ_FALSE)
|
||||
std::runtime_error(std::string("Error while extracting a file from ") + path);
|
||||
throw Slic3r::FileIOError(std::string("Error while extracting a file from ") + path);
|
||||
if (strcmp(stat.m_filename, "scene.xml") == 0) {
|
||||
if (! scene_xml_data.empty())
|
||||
throw std::runtime_error(std::string("Multiple scene.xml were found in the archive.") + path);
|
||||
throw Slic3r::FileIOError(std::string("Multiple scene.xml were found in the archive.") + path);
|
||||
scene_xml_data = std::move(buffer);
|
||||
} else if (boost::iends_with(stat.m_filename, ".stl")) {
|
||||
// May throw std::exception
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <sstream>
|
||||
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
||||
#include "libslic3r/MarchingSquares.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
|
@ -64,7 +65,7 @@ boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
|
|||
|
||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw std::runtime_error(zip.get_errorstr());
|
||||
throw Slic3r::FileIOError(zip.get_errorstr());
|
||||
|
||||
boost::property_tree::ptree tree;
|
||||
std::stringstream ss(buf);
|
||||
|
@ -80,7 +81,7 @@ PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
|
|||
|
||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw std::runtime_error(zip.get_errorstr());
|
||||
throw Slic3r::FileIOError(zip.get_errorstr());
|
||||
|
||||
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
|
||||
}
|
||||
|
@ -94,7 +95,7 @@ ArchiveData extract_sla_archive(const std::string &zipfname,
|
|||
struct Arch: public MZ_Archive {
|
||||
Arch(const std::string &fname) {
|
||||
if (!open_zip_reader(&arch, fname))
|
||||
throw std::runtime_error(get_errorstr());
|
||||
throw Slic3r::FileIOError(get_errorstr());
|
||||
}
|
||||
|
||||
~Arch() { close_zip_reader(&arch); }
|
||||
|
@ -202,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg)
|
|||
|
||||
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
|
||||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
|
||||
throw std::runtime_error("Invalid SL1 file");
|
||||
throw Slic3r::FileIOError("Invalid SL1 file");
|
||||
|
||||
RasterParams rstp;
|
||||
|
||||
|
@ -228,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg)
|
|||
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
||||
|
||||
if (!opt_layerh || !opt_init_layerh)
|
||||
throw std::runtime_error("Invalid SL1 file");
|
||||
throw Slic3r::FileIOError("Invalid SL1 file");
|
||||
|
||||
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "libslic3r.h"
|
||||
#include "I18N.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "Geometry.hpp"
|
||||
|
@ -287,7 +288,7 @@ namespace Slic3r {
|
|||
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
|
||||
{
|
||||
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
|
||||
throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
|
||||
throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
|
||||
|
||||
std::string gcode;
|
||||
|
||||
|
@ -540,7 +541,7 @@ namespace Slic3r {
|
|||
if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
|
||||
if (m_layer_idx < (int)m_tool_changes.size()) {
|
||||
if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
|
||||
throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer.");
|
||||
throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer.");
|
||||
|
||||
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
|
@ -629,7 +630,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
|||
// Check that there are extrusions on the very first layer.
|
||||
if (layers_to_print.size() == 1u) {
|
||||
if (!has_extrusions)
|
||||
throw std::runtime_error(_(L("There is an object with no extrusions on the first layer.")));
|
||||
throw Slic3r::RuntimeError(_(L("There is an object with no extrusions on the first layer.")));
|
||||
}
|
||||
|
||||
// In case there are extrusions on this layer, check there is a layer to lay it on.
|
||||
|
@ -721,9 +722,9 @@ namespace DoExport {
|
|||
static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics)
|
||||
{
|
||||
const GCodeProcessor::Result& result = processor.get_result();
|
||||
print_statistics.estimated_normal_print_time = get_time_dhm(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time);
|
||||
print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time);
|
||||
print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
|
||||
get_time_dhm(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A";
|
||||
get_time_dhms(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A";
|
||||
}
|
||||
} // namespace DoExport
|
||||
|
||||
|
@ -750,7 +751,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
|
|||
|
||||
FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb");
|
||||
if (file == nullptr)
|
||||
throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
|
||||
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
|
||||
|
||||
#if !ENABLE_GCODE_VIEWER
|
||||
m_enable_analyzer = preview_data != nullptr;
|
||||
|
@ -763,7 +764,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
|
|||
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");
|
||||
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
|
||||
}
|
||||
} catch (std::exception & /* ex */) {
|
||||
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
|
||||
|
@ -784,14 +785,16 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
|
|||
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);
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
m_processor.process_file(path_tmp);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info();
|
||||
m_processor.process_file(path_tmp, [print]() { print->throw_if_canceled(); });
|
||||
DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics);
|
||||
if (result != nullptr)
|
||||
*result = std::move(m_processor.extract_result());
|
||||
BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info();
|
||||
#else
|
||||
GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data();
|
||||
GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data();
|
||||
|
@ -816,7 +819,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
|
|||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
if (rename_file(path_tmp, path))
|
||||
throw std::runtime_error(
|
||||
throw Slic3r::RuntimeError(
|
||||
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
|
||||
"Is " + path_tmp + " locked?" + '\n');
|
||||
|
||||
|
@ -1944,8 +1947,8 @@ namespace ProcessLayer
|
|||
#if !ENABLE_GCODE_VIEWER
|
||||
// add tag for time estimator
|
||||
gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
|
||||
gcode += config.pause_print_gcode;
|
||||
#endif // !ENABLE_GCODE_VIEWER
|
||||
gcode += config.pause_print_gcode;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2457,14 +2460,17 @@ void GCode::process_layer(
|
|||
#endif /* HAS_PRESSURE_EQUALIZER */
|
||||
|
||||
_write(file, gcode);
|
||||
#if !ENABLE_GCODE_VIEWER
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
|
||||
log_memory_info();
|
||||
#else
|
||||
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
|
||||
", time estimator memory: " <<
|
||||
format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) <<
|
||||
", analyzer memory: " <<
|
||||
format_memsize_MB(m_analyzer.memory_used()) <<
|
||||
log_memory_info();
|
||||
#endif // !ENABLE_GCODE_VIEWER
|
||||
format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) <<
|
||||
", analyzer memory: " <<
|
||||
format_memsize_MB(m_analyzer.memory_used()) <<
|
||||
log_memory_info();
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
}
|
||||
|
||||
void GCode::apply_print_config(const PrintConfig &print_config)
|
||||
|
@ -2704,7 +2710,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
|
|||
else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity))
|
||||
return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid);
|
||||
else
|
||||
throw std::invalid_argument("Invalid argument supplied to extrude()");
|
||||
throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()");
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -2909,7 +2915,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||
} else if (path.role() == erGapFill) {
|
||||
speed = m_config.get_abs_value("gap_fill_speed");
|
||||
} else {
|
||||
throw std::invalid_argument("Invalid speed");
|
||||
throw Slic3r::InvalidArgument("Invalid speed");
|
||||
}
|
||||
}
|
||||
if (this->on_first_layer())
|
||||
|
@ -3330,7 +3336,7 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr
|
|||
perimeters_or_infills_overrides = &infills_overrides;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("Unknown parameter!");
|
||||
throw Slic3r::InvalidArgument("Unknown parameter!");
|
||||
}
|
||||
|
||||
// First we append the entities, there are eec->entities.size() of them:
|
||||
|
|
|
@ -11,10 +11,7 @@
|
|||
#include <assert.h>
|
||||
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
#include <chrono>
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
static const float INCHES_TO_MM = 25.4f;
|
||||
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
|
||||
|
@ -322,13 +319,13 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
|
|||
{
|
||||
boost::nowide::ifstream in(filename);
|
||||
if (!in.good())
|
||||
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
|
||||
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
|
||||
|
||||
// temporary file to contain modified gcode
|
||||
std::string out_path = filename + ".postprocess";
|
||||
FILE* out = boost::nowide::fopen(out_path.c_str(), "wb");
|
||||
if (out == nullptr)
|
||||
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
|
||||
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
|
||||
|
||||
auto time_in_minutes = [](float time_in_seconds) {
|
||||
return int(::roundf(time_in_seconds / 60.0f));
|
||||
|
@ -421,7 +418,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
|
|||
in.close();
|
||||
fclose(out);
|
||||
boost::nowide::remove(out_path.c_str());
|
||||
throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
|
||||
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
|
||||
}
|
||||
export_line.clear();
|
||||
};
|
||||
|
@ -429,7 +426,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
|
|||
while (std::getline(in, gcode_line)) {
|
||||
if (!in.good()) {
|
||||
fclose(out);
|
||||
throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
|
||||
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
|
||||
}
|
||||
|
||||
gcode_line += "\n";
|
||||
|
@ -463,7 +460,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
|
|||
in.close();
|
||||
|
||||
if (rename_file(out_path, filename))
|
||||
throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' +
|
||||
throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' +
|
||||
"Is " + out_path + " locked?" + '\n');
|
||||
}
|
||||
|
||||
|
@ -730,8 +727,10 @@ void GCodeProcessor::reset()
|
|||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_file(const std::string& filename)
|
||||
void GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
{
|
||||
auto last_cancel_callback_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
@ -758,9 +757,21 @@ void GCodeProcessor::process_file(const std::string& filename)
|
|||
}
|
||||
}
|
||||
|
||||
// process gcode
|
||||
m_result.id = ++s_result_id;
|
||||
// 1st move must be a dummy move
|
||||
m_result.moves.emplace_back(MoveVertex());
|
||||
m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); });
|
||||
m_parser.parse_file(filename, [this, cancel_callback, &last_cancel_callback_time](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
|
||||
if (cancel_callback != nullptr) {
|
||||
// call the cancel callback every 100 ms
|
||||
auto curr_time = std::chrono::high_resolution_clock::now();
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - last_cancel_callback_time).count() > 100) {
|
||||
cancel_callback();
|
||||
last_cancel_callback_time = curr_time;
|
||||
}
|
||||
}
|
||||
process_gcode_line(line);
|
||||
});
|
||||
|
||||
// process the time blocks
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
|
||||
|
@ -936,6 +947,20 @@ void GCodeProcessor::process_tags(const std::string& comment)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) {
|
||||
// height tag
|
||||
pos = comment.find(Height_Tag);
|
||||
if (pos != comment.npos) {
|
||||
try {
|
||||
m_height = std::stof(comment.substr(pos + Height_Tag.length()));
|
||||
}
|
||||
catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
// width tag
|
||||
pos = comment.find(Width_Tag);
|
||||
|
@ -948,18 +973,6 @@ void GCodeProcessor::process_tags(const std::string& comment)
|
|||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// height tag
|
||||
pos = comment.find(Height_Tag);
|
||||
if (pos != comment.npos) {
|
||||
try {
|
||||
m_height_compare.last_tag_value = std::stof(comment.substr(pos + Height_Tag.length()));
|
||||
}
|
||||
catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
// color change tag
|
||||
|
@ -1408,12 +1421,12 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
type = EMoveType::Travel;
|
||||
|
||||
if (type == EMoveType::Extrude) {
|
||||
float d_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
|
||||
float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
|
||||
float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_filament_diameters.size()) ? m_filament_diameters[m_extruder_id] : m_filament_diameters.back();
|
||||
float filament_radius = 0.5f * filament_diameter;
|
||||
float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
|
||||
float volume_extruded_filament = area_filament_cross_section * delta_pos[E];
|
||||
float area_toolpath_cross_section = volume_extruded_filament / d_xyz;
|
||||
float area_toolpath_cross_section = volume_extruded_filament / delta_xyz;
|
||||
|
||||
// volume extruded filament / tool displacement = area toolpath cross section
|
||||
m_mm3_per_mm = area_toolpath_cross_section;
|
||||
|
@ -1421,23 +1434,28 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role);
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
if (m_end_position[Z] > m_extruded_last_z + EPSILON) {
|
||||
m_height = m_end_position[Z] - m_extruded_last_z;
|
||||
if ((m_producers_enabled && m_producer != EProducer::PrusaSlicer) || m_height == 0.0f) {
|
||||
if (m_end_position[Z] > m_extruded_last_z + EPSILON) {
|
||||
m_height = m_end_position[Z] - m_extruded_last_z;
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_height_compare.update(m_height, m_extrusion_role);
|
||||
m_height_compare.update(m_height, m_extrusion_role);
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_extruded_last_z = m_end_position[Z];
|
||||
m_extruded_last_z = m_end_position[Z];
|
||||
}
|
||||
}
|
||||
|
||||
if (m_extrusion_role == erExternalPerimeter)
|
||||
// cross section: rectangle
|
||||
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height);
|
||||
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05 * filament_radius)) / (delta_xyz * m_height);
|
||||
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone)
|
||||
// cross section: circle
|
||||
m_width = static_cast<float>(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz);
|
||||
m_width = static_cast<float>(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz);
|
||||
else
|
||||
// cross section: rectangle + 2 semicircles
|
||||
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
|
||||
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
|
||||
|
||||
// clamp width to avoid artifacts which may arise from wrong values of m_height
|
||||
m_width = std::min(m_width, 4.0f * m_height);
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_width_compare.update(m_width, m_extrusion_role);
|
||||
|
|
|
@ -419,7 +419,8 @@ namespace Slic3r {
|
|||
Result&& extract_result() { return std::move(m_result); }
|
||||
|
||||
// Process the gcode contained in the file with the given filename
|
||||
void process_file(const std::string& filename);
|
||||
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
|
||||
void process_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
|
||||
|
||||
float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
|
||||
std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const;
|
||||
|
|
|
@ -79,7 +79,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line)
|
|||
if (! ::CreateProcessW(
|
||||
nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */,
|
||||
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info))
|
||||
throw std::runtime_error(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
|
||||
throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
|
||||
::WaitForSingleObject(process_info.hProcess, INFINITE);
|
||||
ULONG rc = 0;
|
||||
::GetExitCodeProcess(process_info.hProcess, &rc);
|
||||
|
@ -98,13 +98,13 @@ static int run_script(const std::string &script, const std::string &gcode, std::
|
|||
LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs);
|
||||
if (szArglist == nullptr || nArgs <= 0) {
|
||||
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
|
||||
throw std::runtime_error(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
|
||||
throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
|
||||
}
|
||||
|
||||
std::wstring command_line;
|
||||
std::wstring command = szArglist[0];
|
||||
if (! boost::filesystem::exists(boost::filesystem::path(command)))
|
||||
throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
|
||||
throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
|
||||
if (boost::iends_with(command, L".pl")) {
|
||||
// This is a perl script. Run it through the perl interpreter.
|
||||
// The current process may be slic3r.exe or slic3r-console.exe.
|
||||
|
@ -115,7 +115,7 @@ static int run_script(const std::string &script, const std::string &gcode, std::
|
|||
boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe";
|
||||
if (! boost::filesystem::exists(path_perl)) {
|
||||
LocalFree(szArglist);
|
||||
throw std::runtime_error(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
|
||||
throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
|
||||
}
|
||||
// Replace it with the current perl interpreter.
|
||||
quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line);
|
||||
|
@ -187,7 +187,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
|
|||
config.setenv_();
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (! boost::filesystem::exists(gcode_file))
|
||||
throw std::runtime_error(std::string("Post-processor can't find exported gcode file"));
|
||||
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
|
||||
|
||||
for (const std::string &scripts : config.post_process.values) {
|
||||
std::vector<std::string> lines;
|
||||
|
@ -205,7 +205,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
|
|||
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
|
||||
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
throw std::runtime_error(msg);
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ static inline int parse_int(const char *&line)
|
|||
char *endptr = NULL;
|
||||
long result = strtol(line, &endptr, 10);
|
||||
if (endptr == NULL || !is_ws_or_eol(*endptr))
|
||||
throw std::runtime_error("PressureEqualizer: Error parsing an int");
|
||||
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing an int");
|
||||
line = endptr;
|
||||
return int(result);
|
||||
};
|
||||
|
@ -160,7 +160,7 @@ static inline float parse_float(const char *&line)
|
|||
char *endptr = NULL;
|
||||
float result = strtof(line, &endptr);
|
||||
if (endptr == NULL || !is_ws_or_eol(*endptr))
|
||||
throw std::runtime_error("PressureEqualizer: Error parsing a float");
|
||||
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float");
|
||||
line = endptr;
|
||||
return result;
|
||||
};
|
||||
|
@ -229,7 +229,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi
|
|||
assert(false);
|
||||
}
|
||||
if (i == -1)
|
||||
throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
|
||||
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
|
||||
buf.pos_provided[i] = true;
|
||||
new_pos[i] = parse_float(line);
|
||||
if (i == 3 && m_config->use_relative_e_distances.value)
|
||||
|
@ -298,7 +298,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi
|
|||
set = true;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
|
||||
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
|
||||
}
|
||||
eatws(line);
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_ent
|
|||
auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
|
||||
if (extrusion_entity_collection != nullptr)
|
||||
return extrusionentity_extents(*extrusion_entity_collection);
|
||||
throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()");
|
||||
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in extrusionentity_extents()");
|
||||
return BoundingBoxf();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#include "GCodeReader.hpp"
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
@ -113,7 +116,11 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co
|
|||
|
||||
void GCodeReader::parse_file(const std::string &file, callback_t callback)
|
||||
{
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
boost::nowide::ifstream f(file);
|
||||
#else
|
||||
std::ifstream f(file);
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
std::string line;
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
m_parsing_file = true;
|
||||
|
|
|
@ -153,7 +153,7 @@ GCodeSender::set_baud_rate(unsigned int baud_rate)
|
|||
if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0)
|
||||
printf("Failed to set baud rate: %s\n", strerror(errno));
|
||||
#else
|
||||
//throw invalid_argument ("OS does not currently support custom bauds");
|
||||
//throw Slic3r::InvalidArgument("OS does not currently support custom bauds");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Exception.hpp"
|
||||
#include "GCodeTimeEstimator.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include <boost/bind.hpp>
|
||||
|
@ -254,13 +255,13 @@ namespace Slic3r {
|
|||
{
|
||||
boost::nowide::ifstream in(filename);
|
||||
if (!in.good())
|
||||
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
|
||||
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
|
||||
|
||||
std::string path_tmp = filename + ".postprocess";
|
||||
|
||||
FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb");
|
||||
if (out == nullptr)
|
||||
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
|
||||
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
|
||||
|
||||
std::string normal_time_mask = "M73 P%s R%s\n";
|
||||
std::string silent_time_mask = "M73 Q%s S%s\n";
|
||||
|
@ -278,7 +279,7 @@ namespace Slic3r {
|
|||
in.close();
|
||||
fclose(out);
|
||||
boost::nowide::remove(path_tmp.c_str());
|
||||
throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
|
||||
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
|
||||
}
|
||||
export_line.clear();
|
||||
};
|
||||
|
@ -326,7 +327,7 @@ namespace Slic3r {
|
|||
if (!in.good())
|
||||
{
|
||||
fclose(out);
|
||||
throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
|
||||
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
|
||||
}
|
||||
|
||||
// check tags
|
||||
|
@ -383,7 +384,7 @@ namespace Slic3r {
|
|||
in.close();
|
||||
|
||||
if (rename_file(path_tmp, filename))
|
||||
throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
|
||||
throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
|
||||
"Is " + path_tmp + " locked?" + '\n');
|
||||
|
||||
return true;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "libslic3r.h"
|
||||
#include "Exception.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
|
@ -471,7 +472,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo
|
|||
size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0)));
|
||||
size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1)));
|
||||
if (num_parts > cellw * cellh)
|
||||
throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts);
|
||||
throw Slic3r::InvalidArgument("%zu parts won't fit in your print area!\n", num_parts);
|
||||
|
||||
// Get a bounding box of cellw x cellh cells, centered at the center of the bed.
|
||||
Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap);
|
||||
|
|
|
@ -281,7 +281,7 @@ bool directions_parallel(double angle1, double angle2, double max_diff = 0);
|
|||
template<class T> bool contains(const std::vector<T> &vector, const Point &point);
|
||||
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
|
||||
double rad2deg_dir(double angle);
|
||||
template<typename T> T deg2rad(T angle) { return T(PI) * angle / T(180.0); }
|
||||
template<typename T> constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); }
|
||||
template<typename T> T angle_to_0_2PI(T angle)
|
||||
{
|
||||
static const T TWO_PI = T(2) * T(PI);
|
||||
|
|
|
@ -13,6 +13,10 @@ class Layer;
|
|||
class PrintRegion;
|
||||
class PrintObject;
|
||||
|
||||
namespace FillAdaptive {
|
||||
struct Octree;
|
||||
};
|
||||
|
||||
class LayerRegion
|
||||
{
|
||||
public:
|
||||
|
@ -134,7 +138,8 @@ public:
|
|||
return false;
|
||||
}
|
||||
void make_perimeters();
|
||||
void make_fills();
|
||||
void make_fills() { this->make_fills(nullptr, nullptr); };
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree);
|
||||
void make_ironing();
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Exception.hpp"
|
||||
#include "MeshBoolean.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#undef PI
|
||||
|
@ -136,7 +137,7 @@ template<class _Mesh> void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &o
|
|||
if(CGAL::is_closed(out))
|
||||
CGALProc::orient_to_bound_a_volume(out);
|
||||
else
|
||||
std::runtime_error("Mesh not watertight");
|
||||
throw Slic3r::RuntimeError("Mesh not watertight");
|
||||
}
|
||||
|
||||
inline Vec3d to_vec3d(const _EpicMesh::Point &v)
|
||||
|
@ -222,7 +223,7 @@ template<class Op> void _cgal_do(Op &&op, CGALMesh &A, CGALMesh &B)
|
|||
}
|
||||
|
||||
if (! success)
|
||||
throw std::runtime_error("CGAL mesh boolean operation failed.");
|
||||
throw Slic3r::RuntimeError("CGAL mesh boolean operation failed.");
|
||||
}
|
||||
|
||||
void minus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_diff, A, B); }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Exception.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "ModelArrange.hpp"
|
||||
#include "Geometry.hpp"
|
||||
|
@ -116,13 +117,13 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
|
|||
else if (boost::algorithm::iends_with(input_file, ".prusa"))
|
||||
result = load_prus(input_file.c_str(), &model);
|
||||
else
|
||||
throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");
|
||||
throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");
|
||||
|
||||
if (! result)
|
||||
throw std::runtime_error("Loading of a model file failed.");
|
||||
throw Slic3r::RuntimeError("Loading of a model file failed.");
|
||||
|
||||
if (model.objects.empty())
|
||||
throw std::runtime_error("The supplied file couldn't be read because it's empty");
|
||||
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
|
||||
|
||||
for (ModelObject *o : model.objects)
|
||||
o->input_file = input_file;
|
||||
|
@ -146,13 +147,13 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
|||
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
|
||||
result = load_amf(input_file.c_str(), config, &model, check_version);
|
||||
else
|
||||
throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension.");
|
||||
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
|
||||
|
||||
if (!result)
|
||||
throw std::runtime_error("Loading of a model file failed.");
|
||||
throw Slic3r::RuntimeError("Loading of a model file failed.");
|
||||
|
||||
if (model.objects.empty())
|
||||
throw std::runtime_error("The supplied file couldn't be read because it's empty");
|
||||
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
|
||||
|
||||
for (ModelObject *o : model.objects)
|
||||
{
|
||||
|
@ -776,6 +777,38 @@ TriangleMesh ModelObject::raw_mesh() const
|
|||
return mesh;
|
||||
}
|
||||
|
||||
// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
|
||||
// Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater
|
||||
// and to display the object statistics at ModelObject::print_info().
|
||||
indexed_triangle_set ModelObject::raw_indexed_triangle_set() const
|
||||
{
|
||||
size_t num_vertices = 0;
|
||||
size_t num_faces = 0;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part()) {
|
||||
num_vertices += v->mesh().its.vertices.size();
|
||||
num_faces += v->mesh().its.indices.size();
|
||||
}
|
||||
indexed_triangle_set out;
|
||||
out.vertices.reserve(num_vertices);
|
||||
out.indices.reserve(num_faces);
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part()) {
|
||||
size_t i = out.vertices.size();
|
||||
size_t j = out.indices.size();
|
||||
append(out.vertices, v->mesh().its.vertices);
|
||||
append(out.indices, v->mesh().its.indices);
|
||||
auto m = v->get_matrix();
|
||||
for (; i < out.vertices.size(); ++ i)
|
||||
out.vertices[i] = (m * out.vertices[i].cast<double>()).cast<float>().eval();
|
||||
if (v->is_left_handed()) {
|
||||
for (; j < out.indices.size(); ++ j)
|
||||
std::swap(out.indices[j][0], out.indices[j][1]);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
|
||||
TriangleMesh ModelObject::full_raw_mesh() const
|
||||
{
|
||||
|
@ -817,7 +850,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
|
|||
m_raw_bounding_box_valid = true;
|
||||
m_raw_bounding_box.reset();
|
||||
if (this->instances.empty())
|
||||
throw std::invalid_argument("Can't call raw_bounding_box() with no instances");
|
||||
throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances");
|
||||
|
||||
const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
|
|
|
@ -244,6 +244,8 @@ public:
|
|||
// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
|
||||
// Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater.
|
||||
TriangleMesh raw_mesh() const;
|
||||
// The same as above, but producing a lightweight indexed_triangle_set.
|
||||
indexed_triangle_set raw_indexed_triangle_set() const;
|
||||
// Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
|
||||
TriangleMesh full_raw_mesh() const;
|
||||
// A transformed snug bounding box around the non-modifier object volumes, without the translation applied.
|
||||
|
|
|
@ -20,7 +20,7 @@ using VirtualBedFn = std::function<void(arrangement::ArrangePolygon&)>;
|
|||
|
||||
[[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&)
|
||||
{
|
||||
throw std::runtime_error("Objects could not fit on the bed");
|
||||
throw Slic3r::RuntimeError("Objects could not fit on the bed");
|
||||
}
|
||||
|
||||
ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "OpenVDBUtils.hpp"
|
||||
#include <openvdb/tools/MeshToVolume.h>
|
||||
#include <openvdb/tools/VolumeToMesh.h>
|
||||
#include <openvdb/tools/Composite.h>
|
||||
#include <openvdb/tools/LevelSetRebuild.h>
|
||||
|
||||
//#include "MTUtils.hpp"
|
||||
|
@ -57,7 +58,6 @@ void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
|
|||
// TODO: Do I need to call initialize? Seems to work without it as well but the
|
||||
// docs say it should be called ones. It does a mutex lock-unlock sequence all
|
||||
// even if was called previously.
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
|
||||
const openvdb::math::Transform &tr,
|
||||
float exteriorBandWidth,
|
||||
|
@ -65,9 +65,38 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
|
|||
int flags)
|
||||
{
|
||||
openvdb::initialize();
|
||||
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth,
|
||||
interiorBandWidth, flags);
|
||||
|
||||
TriangleMeshPtrs meshparts = mesh.split();
|
||||
|
||||
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
|
||||
[](TriangleMesh *m){
|
||||
m->require_shared_vertices();
|
||||
return !m->is_manifold() || m->volume() < EPSILON;
|
||||
});
|
||||
|
||||
meshparts.erase(it, meshparts.end());
|
||||
|
||||
openvdb::FloatGrid::Ptr grid;
|
||||
for (TriangleMesh *m : meshparts) {
|
||||
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth,
|
||||
interiorBandWidth, flags);
|
||||
|
||||
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
|
||||
else if (subgrid) grid = std::move(subgrid);
|
||||
}
|
||||
|
||||
if (grid) {
|
||||
grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth,
|
||||
interiorBandWidth);
|
||||
} else if(meshparts.empty()) {
|
||||
// Splitting failed, fall back to hollow the original mesh
|
||||
grid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth,
|
||||
interiorBandWidth, flags);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,
|
||||
|
|
140
src/libslic3r/Optimize/BruteforceOptimizer.hpp
Normal file
140
src/libslic3r/Optimize/BruteforceOptimizer.hpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
#ifndef BRUTEFORCEOPTIMIZER_HPP
|
||||
#define BRUTEFORCEOPTIMIZER_HPP
|
||||
|
||||
#include <libslic3r/Optimize/Optimizer.hpp>
|
||||
|
||||
namespace Slic3r { namespace opt {
|
||||
|
||||
namespace detail {
|
||||
// Implementing a bruteforce optimizer
|
||||
|
||||
// Return the number of iterations needed to reach a specific grid position (idx)
|
||||
template<size_t N>
|
||||
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
|
||||
{
|
||||
long ret = 0;
|
||||
for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Implementation of a grid search where the search interval is sampled in
|
||||
// equidistant points for each dimension. Grid size determines the number of
|
||||
// samples for one dimension so the number of function calls is gridsize ^ dimension.
|
||||
struct AlgBurteForce {
|
||||
bool to_min;
|
||||
StopCriteria stc;
|
||||
size_t gridsz;
|
||||
|
||||
AlgBurteForce(const StopCriteria &cr, size_t gs): stc{cr}, gridsz{gs} {}
|
||||
|
||||
// This function is called recursively for each dimension and generates
|
||||
// the grid values for the particular dimension. If D is less than zero,
|
||||
// the object function input values are generated for each dimension and it
|
||||
// can be evaluated. The current best score is compared with the newly
|
||||
// returned score and changed appropriately.
|
||||
template<int D, size_t N, class Fn, class Cmp>
|
||||
bool run(std::array<size_t, N> &idx,
|
||||
Result<N> &result,
|
||||
const Bounds<N> &bounds,
|
||||
Fn &&fn,
|
||||
Cmp &&cmp)
|
||||
{
|
||||
if (stc.stop_condition()) return false;
|
||||
|
||||
if constexpr (D < 0) { // Let's evaluate fn
|
||||
Input<N> inp;
|
||||
|
||||
auto max_iter = stc.max_iterations();
|
||||
if (max_iter && num_iter(idx, gridsz) >= max_iter)
|
||||
return false;
|
||||
|
||||
for (size_t d = 0; d < N; ++d) {
|
||||
const Bound &b = bounds[d];
|
||||
double step = (b.max() - b.min()) / (gridsz - 1);
|
||||
inp[d] = b.min() + idx[d] * step;
|
||||
}
|
||||
|
||||
auto score = fn(inp);
|
||||
if (cmp(score, result.score)) { // Change current score to the new
|
||||
double absdiff = std::abs(score - result.score);
|
||||
|
||||
result.score = score;
|
||||
result.optimum = inp;
|
||||
|
||||
// Check if the required precision is reached.
|
||||
if (absdiff < stc.abs_score_diff() ||
|
||||
absdiff < stc.rel_score_diff() * std::abs(score))
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
for (size_t i = 0; i < gridsz; ++i) {
|
||||
idx[D] = i; // Mark the current grid position and dig down
|
||||
if (!run<D - 1>(idx, result, bounds, std::forward<Fn>(fn),
|
||||
std::forward<Cmp>(cmp)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Fn, size_t N>
|
||||
Result<N> optimize(Fn&& fn,
|
||||
const Input<N> &/*initvals*/,
|
||||
const Bounds<N>& bounds)
|
||||
{
|
||||
std::array<size_t, N> idx = {};
|
||||
Result<N> result;
|
||||
|
||||
if (to_min) {
|
||||
result.score = std::numeric_limits<double>::max();
|
||||
run<int(N) - 1>(idx, result, bounds, std::forward<Fn>(fn),
|
||||
std::less<double>{});
|
||||
}
|
||||
else {
|
||||
result.score = std::numeric_limits<double>::lowest();
|
||||
run<int(N) - 1>(idx, result, bounds, std::forward<Fn>(fn),
|
||||
std::greater<double>{});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
using AlgBruteForce = detail::AlgBurteForce;
|
||||
|
||||
template<>
|
||||
class Optimizer<AlgBruteForce> {
|
||||
AlgBruteForce m_alg;
|
||||
|
||||
public:
|
||||
|
||||
Optimizer(const StopCriteria &cr = {}, size_t gridsz = 100)
|
||||
: m_alg{cr, gridsz}
|
||||
{}
|
||||
|
||||
Optimizer& to_max() { m_alg.to_min = false; return *this; }
|
||||
Optimizer& to_min() { m_alg.to_min = true; return *this; }
|
||||
|
||||
template<class Func, size_t N>
|
||||
Result<N> optimize(Func&& func,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds)
|
||||
{
|
||||
return m_alg.optimize(std::forward<Func>(func), initvals, bounds);
|
||||
}
|
||||
|
||||
Optimizer &set_criteria(const StopCriteria &cr)
|
||||
{
|
||||
m_alg.stc = cr; return *this;
|
||||
}
|
||||
|
||||
const StopCriteria &get_criteria() const { return m_alg.stc; }
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::opt
|
||||
|
||||
#endif // BRUTEFORCEOPTIMIZER_HPP
|
|
@ -12,134 +12,11 @@
|
|||
#endif
|
||||
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <cassert>
|
||||
|
||||
#include <libslic3r/Optimize/Optimizer.hpp>
|
||||
|
||||
namespace Slic3r { namespace opt {
|
||||
|
||||
// A type to hold the complete result of the optimization.
|
||||
template<size_t N> struct Result {
|
||||
int resultcode;
|
||||
std::array<double, N> optimum;
|
||||
double score;
|
||||
};
|
||||
|
||||
// An interval of possible input values for optimization
|
||||
class Bound {
|
||||
double m_min, m_max;
|
||||
|
||||
public:
|
||||
Bound(double min = std::numeric_limits<double>::min(),
|
||||
double max = std::numeric_limits<double>::max())
|
||||
: m_min(min), m_max(max)
|
||||
{}
|
||||
|
||||
double min() const noexcept { return m_min; }
|
||||
double max() const noexcept { return m_max; }
|
||||
};
|
||||
|
||||
// Helper types for optimization function input and bounds
|
||||
template<size_t N> using Input = std::array<double, N>;
|
||||
template<size_t N> using Bounds = std::array<Bound, N>;
|
||||
|
||||
// A type for specifying the stop criteria. Setter methods can be concatenated
|
||||
class StopCriteria {
|
||||
|
||||
// If the absolute value difference between two scores.
|
||||
double m_abs_score_diff = std::nan("");
|
||||
|
||||
// If the relative value difference between two scores.
|
||||
double m_rel_score_diff = std::nan("");
|
||||
|
||||
// Stop if this value or better is found.
|
||||
double m_stop_score = std::nan("");
|
||||
|
||||
// A predicate that if evaluates to true, the optimization should terminate
|
||||
// and the best result found prior to termination should be returned.
|
||||
std::function<bool()> m_stop_condition = [] { return false; };
|
||||
|
||||
// The max allowed number of iterations.
|
||||
unsigned m_max_iterations = 0;
|
||||
|
||||
public:
|
||||
|
||||
StopCriteria & abs_score_diff(double val)
|
||||
{
|
||||
m_abs_score_diff = val; return *this;
|
||||
}
|
||||
|
||||
double abs_score_diff() const { return m_abs_score_diff; }
|
||||
|
||||
StopCriteria & rel_score_diff(double val)
|
||||
{
|
||||
m_rel_score_diff = val; return *this;
|
||||
}
|
||||
|
||||
double rel_score_diff() const { return m_rel_score_diff; }
|
||||
|
||||
StopCriteria & stop_score(double val)
|
||||
{
|
||||
m_stop_score = val; return *this;
|
||||
}
|
||||
|
||||
double stop_score() const { return m_stop_score; }
|
||||
|
||||
StopCriteria & max_iterations(double val)
|
||||
{
|
||||
m_max_iterations = val; return *this;
|
||||
}
|
||||
|
||||
double max_iterations() const { return m_max_iterations; }
|
||||
|
||||
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
|
||||
{
|
||||
m_stop_condition = cond; return *this;
|
||||
}
|
||||
|
||||
bool stop_condition() { return m_stop_condition(); }
|
||||
};
|
||||
|
||||
// Helper class to use optimization methods involving gradient.
|
||||
template<size_t N> struct ScoreGradient {
|
||||
double score;
|
||||
std::optional<std::array<double, N>> gradient;
|
||||
|
||||
ScoreGradient(double s, const std::array<double, N> &grad)
|
||||
: score{s}, gradient{grad}
|
||||
{}
|
||||
};
|
||||
|
||||
// Helper to be used in static_assert.
|
||||
template<class T> struct always_false { enum { value = false }; };
|
||||
|
||||
// Basic interface to optimizer object
|
||||
template<class Method, class Enable = void> class Optimizer {
|
||||
public:
|
||||
|
||||
Optimizer(const StopCriteria &)
|
||||
{
|
||||
static_assert (always_false<Method>::value,
|
||||
"Optimizer unimplemented for given method!");
|
||||
}
|
||||
|
||||
Optimizer<Method> &to_min() { return *this; }
|
||||
Optimizer<Method> &to_max() { return *this; }
|
||||
Optimizer<Method> &set_criteria(const StopCriteria &) { return *this; }
|
||||
StopCriteria get_criteria() const { return {}; };
|
||||
|
||||
template<class Func, size_t N>
|
||||
Result<N> optimize(Func&& func,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds) { return {}; }
|
||||
|
||||
// optional for randomized methods:
|
||||
void seed(long /*s*/) {}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Helper types for NLopt algorithm selection in template contexts
|
||||
|
@ -166,19 +43,6 @@ struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
|
|||
template<class M, class T = void>
|
||||
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
|
||||
|
||||
// Helper to convert C style array to std::array. The copy should be optimized
|
||||
// away with modern compilers.
|
||||
template<size_t N, class T> auto to_arr(const T *a)
|
||||
{
|
||||
std::array<T, N> r;
|
||||
std::copy(a, a + N, std::begin(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
template<size_t N, class T> auto to_arr(const T (&a) [N])
|
||||
{
|
||||
return to_arr<N>(static_cast<const T *>(a));
|
||||
}
|
||||
|
||||
enum class OptDir { MIN, MAX }; // Where to optimize
|
||||
|
||||
|
@ -357,23 +221,12 @@ public:
|
|||
void seed(long s) { m_opt.seed(s); }
|
||||
};
|
||||
|
||||
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
|
||||
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
|
||||
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
|
||||
{
|
||||
return ScoreGradient<N>(s, detail::to_arr(grad));
|
||||
}
|
||||
|
||||
// Predefinded NLopt algorithms that are used in the codebase
|
||||
// Predefinded NLopt algorithms
|
||||
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
||||
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
||||
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
||||
|
||||
// TODO: define others if needed...
|
||||
|
||||
// Helper defs for pre-crafted global and local optimizers that work well.
|
||||
using DefaultGlobalOptimizer = Optimizer<AlgNLoptGenetic>;
|
||||
using DefaultLocalOptimizer = Optimizer<AlgNLoptSubplex>;
|
||||
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
|
||||
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>;
|
||||
|
||||
}} // namespace Slic3r::opt
|
||||
|
182
src/libslic3r/Optimize/Optimizer.hpp
Normal file
182
src/libslic3r/Optimize/Optimizer.hpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
#ifndef OPTIMIZER_HPP
|
||||
#define OPTIMIZER_HPP
|
||||
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <cassert>
|
||||
|
||||
namespace Slic3r { namespace opt {
|
||||
|
||||
// A type to hold the complete result of the optimization.
|
||||
template<size_t N> struct Result {
|
||||
int resultcode; // Method dependent
|
||||
std::array<double, N> optimum;
|
||||
double score;
|
||||
};
|
||||
|
||||
// An interval of possible input values for optimization
|
||||
class Bound {
|
||||
double m_min, m_max;
|
||||
|
||||
public:
|
||||
Bound(double min = std::numeric_limits<double>::min(),
|
||||
double max = std::numeric_limits<double>::max())
|
||||
: m_min(min), m_max(max)
|
||||
{}
|
||||
|
||||
double min() const noexcept { return m_min; }
|
||||
double max() const noexcept { return m_max; }
|
||||
};
|
||||
|
||||
// Helper types for optimization function input and bounds
|
||||
template<size_t N> using Input = std::array<double, N>;
|
||||
template<size_t N> using Bounds = std::array<Bound, N>;
|
||||
|
||||
// A type for specifying the stop criteria. Setter methods can be concatenated
|
||||
class StopCriteria {
|
||||
|
||||
// If the absolute value difference between two scores.
|
||||
double m_abs_score_diff = std::nan("");
|
||||
|
||||
// If the relative value difference between two scores.
|
||||
double m_rel_score_diff = std::nan("");
|
||||
|
||||
// Stop if this value or better is found.
|
||||
double m_stop_score = std::nan("");
|
||||
|
||||
// A predicate that if evaluates to true, the optimization should terminate
|
||||
// and the best result found prior to termination should be returned.
|
||||
std::function<bool()> m_stop_condition = [] { return false; };
|
||||
|
||||
// The max allowed number of iterations.
|
||||
unsigned m_max_iterations = 0;
|
||||
|
||||
public:
|
||||
|
||||
StopCriteria & abs_score_diff(double val)
|
||||
{
|
||||
m_abs_score_diff = val; return *this;
|
||||
}
|
||||
|
||||
double abs_score_diff() const { return m_abs_score_diff; }
|
||||
|
||||
StopCriteria & rel_score_diff(double val)
|
||||
{
|
||||
m_rel_score_diff = val; return *this;
|
||||
}
|
||||
|
||||
double rel_score_diff() const { return m_rel_score_diff; }
|
||||
|
||||
StopCriteria & stop_score(double val)
|
||||
{
|
||||
m_stop_score = val; return *this;
|
||||
}
|
||||
|
||||
double stop_score() const { return m_stop_score; }
|
||||
|
||||
StopCriteria & max_iterations(double val)
|
||||
{
|
||||
m_max_iterations = val; return *this;
|
||||
}
|
||||
|
||||
double max_iterations() const { return m_max_iterations; }
|
||||
|
||||
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
|
||||
{
|
||||
m_stop_condition = cond; return *this;
|
||||
}
|
||||
|
||||
bool stop_condition() { return m_stop_condition(); }
|
||||
};
|
||||
|
||||
// Helper class to use optimization methods involving gradient.
|
||||
template<size_t N> struct ScoreGradient {
|
||||
double score;
|
||||
std::optional<std::array<double, N>> gradient;
|
||||
|
||||
ScoreGradient(double s, const std::array<double, N> &grad)
|
||||
: score{s}, gradient{grad}
|
||||
{}
|
||||
};
|
||||
|
||||
// Helper to be used in static_assert.
|
||||
template<class T> struct always_false { enum { value = false }; };
|
||||
|
||||
// Basic interface to optimizer object
|
||||
template<class Method, class Enable = void> class Optimizer {
|
||||
public:
|
||||
|
||||
Optimizer(const StopCriteria &)
|
||||
{
|
||||
static_assert (always_false<Method>::value,
|
||||
"Optimizer unimplemented for given method!");
|
||||
}
|
||||
|
||||
// Switch optimization towards function minimum
|
||||
Optimizer &to_min() { return *this; }
|
||||
|
||||
// Switch optimization towards function maximum
|
||||
Optimizer &to_max() { return *this; }
|
||||
|
||||
// Set criteria for successive optimizations
|
||||
Optimizer &set_criteria(const StopCriteria &) { return *this; }
|
||||
|
||||
// Get current criteria
|
||||
StopCriteria get_criteria() const { return {}; };
|
||||
|
||||
// Find function minimum or maximum for Func which has has signature:
|
||||
// double(const Input<N> &input) and input with dimension N
|
||||
//
|
||||
// Initial starting point can be given as the second parameter.
|
||||
//
|
||||
// For each dimension an interval (Bound) has to be given marking the bounds
|
||||
// for that dimension.
|
||||
//
|
||||
// initvals have to be within the specified bounds, otherwise its undefined
|
||||
// behavior.
|
||||
//
|
||||
// Func can return a score of type double or optionally a ScoreGradient
|
||||
// class to indicate the function gradient for a optimization methods that
|
||||
// make use of the gradient.
|
||||
template<class Func, size_t N>
|
||||
Result<N> optimize(Func&& /*func*/,
|
||||
const Input<N> &/*initvals*/,
|
||||
const Bounds<N>& /*bounds*/) { return {}; }
|
||||
|
||||
// optional for randomized methods:
|
||||
void seed(long /*s*/) {}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Helper to convert C style array to std::array. The copy should be optimized
|
||||
// away with modern compilers.
|
||||
template<size_t N, class T> auto to_arr(const T *a)
|
||||
{
|
||||
std::array<T, N> r;
|
||||
std::copy(a, a + N, std::begin(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
template<size_t N, class T> auto to_arr(const T (&a) [N])
|
||||
{
|
||||
return to_arr<N>(static_cast<const T *>(a));
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Helper functions to create bounds, initial value
|
||||
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
|
||||
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
|
||||
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
|
||||
{
|
||||
return ScoreGradient<N>(s, detail::to_arr(grad));
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::opt
|
||||
|
||||
#endif // OPTIMIZER_HPP
|
|
@ -1,4 +1,5 @@
|
|||
#include "PlaceholderParser.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
@ -1303,7 +1304,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co
|
|||
if (!context.error_message.empty()) {
|
||||
if (context.error_message.back() != '\n' && context.error_message.back() != '\r')
|
||||
context.error_message += '\n';
|
||||
throw std::runtime_error(context.error_message);
|
||||
throw Slic3r::RuntimeError(context.error_message);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
@ -1319,7 +1320,7 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu
|
|||
}
|
||||
|
||||
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
|
||||
// Throws std::runtime_error on syntax or runtime error.
|
||||
// Throws Slic3r::RuntimeError on syntax or runtime error.
|
||||
bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override)
|
||||
{
|
||||
client::MyContext context;
|
||||
|
|
|
@ -40,11 +40,11 @@ public:
|
|||
const DynamicConfig* external_config() const { return m_external_config; }
|
||||
|
||||
// Fill in the template using a macro processing language.
|
||||
// Throws std::runtime_error on syntax or runtime error.
|
||||
// Throws Slic3r::RuntimeError on syntax or runtime error.
|
||||
std::string process(const std::string &templ, unsigned int current_extruder_id = 0, const DynamicConfig *config_override = nullptr) const;
|
||||
|
||||
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
|
||||
// Throws std::runtime_error on syntax or runtime error.
|
||||
// Throws Slic3r::RuntimeError on syntax or runtime error.
|
||||
static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr);
|
||||
|
||||
// Update timestamp, year, month, day, hour, minute, second variables at the provided config.
|
||||
|
|
|
@ -44,16 +44,6 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t)
|
|||
return ret_points;
|
||||
}
|
||||
|
||||
void Point::rotate(double angle)
|
||||
{
|
||||
double cur_x = (double)(*this)(0);
|
||||
double cur_y = (double)(*this)(1);
|
||||
double s = ::sin(angle);
|
||||
double c = ::cos(angle);
|
||||
(*this)(0) = (coord_t)round(c * cur_x - s * cur_y);
|
||||
(*this)(1) = (coord_t)round(c * cur_y + s * cur_x);
|
||||
}
|
||||
|
||||
void Point::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
double cur_x = (double)(*this)(0);
|
||||
|
|
|
@ -105,6 +105,7 @@ public:
|
|||
template<typename OtherDerived>
|
||||
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
|
||||
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
|
||||
static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
|
||||
|
||||
// This method allows you to assign Eigen expressions to MyVectorType
|
||||
template<typename OtherDerived>
|
||||
|
@ -121,7 +122,14 @@ public:
|
|||
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); }
|
||||
|
||||
void rotate(double angle);
|
||||
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);
|
||||
}
|
||||
|
||||
void rotate(double angle, const Point ¢er);
|
||||
Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; }
|
||||
Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
|
@ -16,7 +17,7 @@ Polyline Polygon::split_at_vertex(const Point &point) const
|
|||
for (const Point &pt : this->points)
|
||||
if (pt == point)
|
||||
return this->split_at_index(int(&pt - &this->points.front()));
|
||||
throw std::invalid_argument("Point not found");
|
||||
throw Slic3r::InvalidArgument("Point not found");
|
||||
return Polyline();
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,14 @@ inline double total_length(const Polygons &polylines) {
|
|||
return total;
|
||||
}
|
||||
|
||||
inline double area(const Polygons &polys)
|
||||
{
|
||||
double s = 0.;
|
||||
for (auto &p : polys) s += p.area();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Remove sticks (tentacles with zero area) from the polygon.
|
||||
extern bool remove_sticks(Polygon &poly);
|
||||
extern bool remove_sticks(Polygons &polys);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "BoundingBox.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "Line.hpp"
|
||||
|
@ -19,7 +20,7 @@ Polyline::operator Polylines() const
|
|||
Polyline::operator Line() const
|
||||
{
|
||||
if (this->points.size() > 2)
|
||||
throw std::invalid_argument("Can't convert polyline with more than two points to a line");
|
||||
throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line");
|
||||
return Line(this->points.front(), this->points.back());
|
||||
}
|
||||
|
||||
|
@ -207,7 +208,7 @@ BoundingBox get_extents(const Polylines &polylines)
|
|||
const Point& leftmost_point(const Polylines &polylines)
|
||||
{
|
||||
if (polylines.empty())
|
||||
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
|
||||
throw Slic3r::InvalidArgument("leftmost_point() called on empty PolylineCollection");
|
||||
Polylines::const_iterator it = polylines.begin();
|
||||
const Point *p = &it->leftmost_point();
|
||||
for (++ it; it != polylines.end(); ++it) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <cassert>
|
||||
|
||||
#include "Exception.hpp"
|
||||
#include "Preset.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
|
||||
|
@ -107,7 +108,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
|
|||
const std::string id = path.stem().string();
|
||||
|
||||
if (! boost::filesystem::exists(path)) {
|
||||
throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str());
|
||||
throw Slic3r::RuntimeError((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str());
|
||||
}
|
||||
|
||||
VendorProfile res(id);
|
||||
|
@ -117,7 +118,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
|
|||
{
|
||||
auto res = tree.find(key);
|
||||
if (res == tree.not_found()) {
|
||||
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str());
|
||||
throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str());
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
@ -129,7 +130,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
|
|||
auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data();
|
||||
auto config_version = Semver::parse(config_version_str);
|
||||
if (! config_version) {
|
||||
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str());
|
||||
throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str());
|
||||
} else {
|
||||
res.config_version = std::move(*config_version);
|
||||
}
|
||||
|
@ -672,9 +673,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
|
|||
preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
|
||||
preset.loaded = true;
|
||||
} catch (const std::ifstream::failure &err) {
|
||||
throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
|
||||
throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
|
||||
} catch (const std::runtime_error &err) {
|
||||
throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
|
||||
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
|
||||
}
|
||||
presets_loaded.emplace_back(preset);
|
||||
} catch (const std::runtime_error &err) {
|
||||
|
@ -686,7 +687,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
|
|||
std::sort(m_presets.begin() + m_num_default_presets, m_presets.end());
|
||||
this->select_preset(first_visible_idx());
|
||||
if (! errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
}
|
||||
|
||||
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
|
||||
|
@ -1365,9 +1366,10 @@ const std::vector<std::string>& PhysicalPrinter::printer_options()
|
|||
"print_host",
|
||||
"printhost_apikey",
|
||||
"printhost_cafile",
|
||||
"authorization_type",
|
||||
"login",
|
||||
"password"
|
||||
"printhost_authorization_type",
|
||||
// HTTP digest authentization (RFC 2617)
|
||||
"printhost_user",
|
||||
"printhost_password"
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
|
@ -1412,11 +1414,11 @@ const std::set<std::string>& PhysicalPrinter::get_preset_names() const
|
|||
|
||||
bool PhysicalPrinter::has_empty_config() const
|
||||
{
|
||||
return config.opt_string("print_host" ).empty() &&
|
||||
config.opt_string("printhost_apikey").empty() &&
|
||||
config.opt_string("printhost_cafile").empty() &&
|
||||
config.opt_string("login" ).empty() &&
|
||||
config.opt_string("password" ).empty();
|
||||
return config.opt_string("print_host" ).empty() &&
|
||||
config.opt_string("printhost_apikey" ).empty() &&
|
||||
config.opt_string("printhost_cafile" ).empty() &&
|
||||
config.opt_string("printhost_user" ).empty() &&
|
||||
config.opt_string("printhost_password").empty();
|
||||
}
|
||||
|
||||
void PhysicalPrinter::update_preset_names_in_config()
|
||||
|
@ -1441,7 +1443,7 @@ void PhysicalPrinter::save(const std::string& file_name_from, const std::string&
|
|||
|
||||
void PhysicalPrinter::update_from_preset(const Preset& preset)
|
||||
{
|
||||
config.apply_only(preset.config, printer_options(), false);
|
||||
config.apply_only(preset.config, printer_options(), true);
|
||||
// add preset names to the options list
|
||||
auto ret = preset_names.emplace(preset.name);
|
||||
update_preset_names_in_config();
|
||||
|
@ -1476,8 +1478,8 @@ bool PhysicalPrinter::delete_preset(const std::string& preset_name)
|
|||
return preset_names.erase(preset_name) > 0;
|
||||
}
|
||||
|
||||
PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) :
|
||||
name(name)
|
||||
PhysicalPrinter::PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset) :
|
||||
name(name), config(default_config)
|
||||
{
|
||||
update_from_preset(preset);
|
||||
}
|
||||
|
@ -1514,6 +1516,13 @@ std::string PhysicalPrinter::get_preset_name(std::string name)
|
|||
|
||||
PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::string>& keys)
|
||||
{
|
||||
// Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options().
|
||||
for (const std::string &key : keys) {
|
||||
const ConfigOptionDef *opt = print_config_def.get(key);
|
||||
assert(opt);
|
||||
assert(opt->default_value);
|
||||
m_default_config.set_key_value(key, opt->default_value->clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Load all printers found in dir_path.
|
||||
|
@ -1539,7 +1548,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
|
|||
continue;
|
||||
}
|
||||
try {
|
||||
PhysicalPrinter printer(name);
|
||||
PhysicalPrinter printer(name, this->default_config());
|
||||
printer.file = dir_entry.path().string();
|
||||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
|
@ -1549,10 +1558,10 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
|
|||
printer.loaded = true;
|
||||
}
|
||||
catch (const std::ifstream::failure& err) {
|
||||
throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what());
|
||||
throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what());
|
||||
}
|
||||
catch (const std::runtime_error& err) {
|
||||
throw std::runtime_error(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what());
|
||||
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what());
|
||||
}
|
||||
printers_loaded.emplace_back(printer);
|
||||
}
|
||||
|
@ -1564,7 +1573,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
|
|||
m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end()));
|
||||
std::sort(m_printers.begin(), m_printers.end());
|
||||
if (!errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
}
|
||||
|
||||
// if there is saved user presets, contains information about "Print Host upload",
|
||||
|
@ -1590,7 +1599,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti
|
|||
new_printer_name = (boost::format("Printer %1%") % ++cnt).str();
|
||||
|
||||
// create new printer from this preset
|
||||
PhysicalPrinter printer(new_printer_name, preset);
|
||||
PhysicalPrinter printer(new_printer_name, this->default_config(), preset);
|
||||
printer.loaded = true;
|
||||
save_printer(printer);
|
||||
}
|
||||
|
|
|
@ -460,8 +460,7 @@ private:
|
|||
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
|
||||
std::deque<Preset>::iterator find_preset_internal(const std::string &name)
|
||||
{
|
||||
Preset key(m_type, name);
|
||||
auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
|
||||
auto it = Slic3r::lower_bound_by_predicate(m_presets.begin() + m_num_default_presets, m_presets.end(), [&name](const auto& l) { return l.name < name; });
|
||||
if (it == m_presets.end() || it->name != name) {
|
||||
// Preset has not been not found in the sorted list of non-default presets. Try the defaults.
|
||||
for (size_t i = 0; i < m_num_default_presets; ++ i)
|
||||
|
@ -539,9 +538,8 @@ namespace PresetUtils {
|
|||
class PhysicalPrinter
|
||||
{
|
||||
public:
|
||||
PhysicalPrinter() {}
|
||||
PhysicalPrinter(const std::string& name) : name(name){}
|
||||
PhysicalPrinter(const std::string& name, const Preset& preset);
|
||||
PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config) : name(name), config(default_config) {}
|
||||
PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset);
|
||||
void set_name(const std::string &name);
|
||||
|
||||
// Name of the Physical Printer, usually derived form the file name.
|
||||
|
@ -698,6 +696,8 @@ public:
|
|||
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
|
||||
std::string path_from_name(const std::string& new_name) const;
|
||||
|
||||
const DynamicPrintConfig& default_config() const { return m_default_config; }
|
||||
|
||||
private:
|
||||
PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other);
|
||||
|
||||
|
@ -707,9 +707,7 @@ private:
|
|||
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
|
||||
std::deque<PhysicalPrinter>::iterator find_printer_internal(const std::string& name)
|
||||
{
|
||||
PhysicalPrinter printer(name);
|
||||
auto it = std::lower_bound(m_printers.begin(), m_printers.end(), printer);
|
||||
return it;
|
||||
return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto& l) { return l.name < name; });
|
||||
}
|
||||
std::deque<PhysicalPrinter>::const_iterator find_printer_internal(const std::string& name) const
|
||||
{
|
||||
|
@ -723,6 +721,9 @@ private:
|
|||
// so that the addresses of the presets don't change during resizing of the container.
|
||||
std::deque<PhysicalPrinter> m_printers;
|
||||
|
||||
// Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options().
|
||||
DynamicPrintConfig m_default_config;
|
||||
|
||||
// Selected printer.
|
||||
size_t m_idx_selected = size_t(-1);
|
||||
// The name of the preset which is currently select for this printer
|
||||
|
|
|
@ -157,7 +157,7 @@ void PresetBundle::setup_directories()
|
|||
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());
|
||||
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create its data directory at ") + subdir.string());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,7 +207,7 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
|
|||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
if (! errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
this->load_selections(config, preferred_model_id);
|
||||
}
|
||||
|
@ -327,6 +327,32 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p
|
|||
return presets.get_preset_name_by_alias(alias);
|
||||
}
|
||||
|
||||
void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type,
|
||||
const std::vector<std::string>& unselected_options)
|
||||
{
|
||||
PresetCollection& presets = type == Preset::TYPE_PRINT ? prints :
|
||||
type == Preset::TYPE_SLA_PRINT ? sla_prints :
|
||||
type == Preset::TYPE_FILAMENT ? filaments :
|
||||
type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers;
|
||||
|
||||
// if we want to save just some from selected options
|
||||
if (!unselected_options.empty()) {
|
||||
// revert unselected options to the old values
|
||||
presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options);
|
||||
}
|
||||
|
||||
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
|
||||
presets.save_current_preset(new_name);
|
||||
// Mark the print & filament enabled if they are compatible with the currently selected preset.
|
||||
// If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible.
|
||||
update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
if (type == Preset::TYPE_FILAMENT) {
|
||||
// synchronize the first filament presets.
|
||||
set_filament_preset(0, filaments.get_selected_preset_name());
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::load_installed_filaments(AppConfig &config)
|
||||
{
|
||||
if (! config.has_section(AppConfig::SECTION_FILAMENTS)) {
|
||||
|
@ -653,21 +679,21 @@ void PresetBundle::load_config_file(const std::string &path)
|
|||
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 Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
|
||||
throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
|
||||
} catch (const boost::property_tree::file_parser_error &err) {
|
||||
throw std::runtime_error((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
|
||||
throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
|
||||
% err.filename() % err.message() % err.line()).str());
|
||||
} catch (const std::runtime_error &err) {
|
||||
throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
|
||||
throw Slic3r::RuntimeError(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);
|
||||
throw Slic3r::RuntimeError(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.");
|
||||
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
|
||||
case CONFIG_FILE_TYPE_CONFIG:
|
||||
{
|
||||
// Initialize a config from full defaults.
|
||||
|
|
|
@ -130,6 +130,10 @@ public:
|
|||
|
||||
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const;
|
||||
|
||||
// Save current preset of a required type under a new name. If the name is different from the old one,
|
||||
// Unselected option would be reverted to the beginning values
|
||||
void save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector<std::string>& unselected_options);
|
||||
|
||||
static const char *PRUSA_BUNDLE;
|
||||
private:
|
||||
std::string load_system_presets();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "clipper/clipper_z.hpp"
|
||||
|
||||
#include "Exception.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
@ -1507,7 +1508,7 @@ BoundingBox Print::total_bounding_box() const
|
|||
double Print::skirt_first_layer_height() const
|
||||
{
|
||||
if (m_objects.empty())
|
||||
throw std::invalid_argument("skirt_first_layer_height() can't be called without PrintObjects");
|
||||
throw Slic3r::InvalidArgument("skirt_first_layer_height() can't be called without PrintObjects");
|
||||
return m_objects.front()->config().get_abs_value("first_layer_height");
|
||||
}
|
||||
|
||||
|
@ -1583,7 +1584,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
|
|||
// Slicing process, running at a background thread.
|
||||
void Print::process()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Staring the slicing process." << log_memory_info();
|
||||
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->make_perimeters();
|
||||
this->set_status(70, L("Infilling layers"));
|
||||
|
@ -1603,7 +1604,7 @@ void Print::process()
|
|||
// Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches.
|
||||
m_tool_ordering = ToolOrdering(*this, -1, false);
|
||||
if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1))
|
||||
throw std::runtime_error("The print is empty. The model is not printable with current print settings.");
|
||||
throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings.");
|
||||
}
|
||||
this->set_done(psWipeTower);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,12 @@ enum class SlicingMode : uint32_t;
|
|||
class Layer;
|
||||
class SupportLayer;
|
||||
|
||||
namespace FillAdaptive {
|
||||
struct Octree;
|
||||
struct OctreeDeleter;
|
||||
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
|
||||
};
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
enum PrintStep {
|
||||
psSkirt,
|
||||
|
@ -235,6 +241,7 @@ private:
|
|||
void discover_horizontal_shells();
|
||||
void combine_infill();
|
||||
void _generate_support_material();
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> prepare_adaptive_infill_data();
|
||||
|
||||
// XYZ in scaled coordinates
|
||||
Vec3crd m_size;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Exception.hpp"
|
||||
#include "PrintBase.hpp"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
@ -68,7 +69,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str
|
|||
filename = boost::filesystem::change_extension(filename, default_ext);
|
||||
return filename.string();
|
||||
} catch (std::runtime_error &err) {
|
||||
throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what());
|
||||
throw Slic3r::RuntimeError(L("Failed processing of the output_filename_format template.") + "\n" + err.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -133,13 +133,13 @@ void PrintConfigDef::init_common_params()
|
|||
|
||||
// Options used by physical printers
|
||||
|
||||
def = this->add("login", coString);
|
||||
def->label = L("Login");
|
||||
def = this->add("printhost_user", coString);
|
||||
def->label = L("User");
|
||||
// def->tooltip = L("");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("password", coString);
|
||||
def = this->add("printhost_password", coString);
|
||||
def->label = L("Password");
|
||||
// def->tooltip = L("");
|
||||
def->mode = comAdvanced;
|
||||
|
@ -151,7 +151,7 @@ void PrintConfigDef::init_common_params()
|
|||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("authorization_type", coEnum);
|
||||
def = this->add("printhost_authorization_type", coEnum);
|
||||
def->label = L("Authorization Type");
|
||||
// def->tooltip = L("");
|
||||
def->enum_keys_map = &ConfigOptionEnum<AuthorizationType>::get_enum_values();
|
||||
|
@ -881,6 +881,8 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_values.push_back("hilbertcurve");
|
||||
def->enum_values.push_back("archimedeanchords");
|
||||
def->enum_values.push_back("octagramspiral");
|
||||
def->enum_values.push_back("adaptivecubic");
|
||||
def->enum_values.push_back("supportcubic");
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
def->enum_labels.push_back(L("Grid"));
|
||||
def->enum_labels.push_back(L("Triangles"));
|
||||
|
@ -894,6 +896,8 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_labels.push_back(L("Hilbert Curve"));
|
||||
def->enum_labels.push_back(L("Archimedean Chords"));
|
||||
def->enum_labels.push_back(L("Octagram Spiral"));
|
||||
def->enum_labels.push_back(L("Adaptive Cubic"));
|
||||
def->enum_labels.push_back(L("Support Cubic"));
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipStars));
|
||||
|
||||
def = this->add("first_layer_acceleration", coFloat);
|
||||
|
|
|
@ -39,7 +39,7 @@ enum AuthorizationType {
|
|||
|
||||
enum InfillPattern : int {
|
||||
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount,
|
||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount,
|
||||
};
|
||||
|
||||
enum class IroningType {
|
||||
|
@ -139,6 +139,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::g
|
|||
keys_map["hilbertcurve"] = ipHilbertCurve;
|
||||
keys_map["archimedeanchords"] = ipArchimedeanChords;
|
||||
keys_map["octagramspiral"] = ipOctagramSpiral;
|
||||
keys_map["adaptivecubic"] = ipAdaptiveCubic;
|
||||
keys_map["supportcubic"] = ipSupportCubic;
|
||||
}
|
||||
return keys_map;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Exception.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
@ -9,6 +10,9 @@
|
|||
#include "Surface.hpp"
|
||||
#include "Slicing.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "AABBTreeIndirect.hpp"
|
||||
#include "Fill/FillAdaptive.hpp"
|
||||
#include "Format/STL.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
@ -135,7 +139,7 @@ void PrintObject::slice()
|
|||
}
|
||||
});
|
||||
if (m_layers.empty())
|
||||
throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
|
||||
throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
|
||||
this->set_done(posSlice);
|
||||
}
|
||||
|
||||
|
@ -369,13 +373,15 @@ void PrintObject::infill()
|
|||
this->prepare_infill();
|
||||
|
||||
if (this->set_started(posInfill)) {
|
||||
auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this](const tbb::blocked_range<size_t>& range) {
|
||||
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
m_print->throw_if_canceled();
|
||||
m_layers[layer_idx]->make_fills();
|
||||
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -421,13 +427,31 @@ void PrintObject::generate_support_material()
|
|||
// therefore they cannot be printed without supports.
|
||||
for (const Layer *layer : m_layers)
|
||||
if (layer->empty())
|
||||
throw std::runtime_error("Levitating objects cannot be printed without supports.");
|
||||
throw Slic3r::SlicingError("Levitating objects cannot be printed without supports.");
|
||||
#endif
|
||||
}
|
||||
this->set_done(posSupportMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data()
|
||||
{
|
||||
using namespace FillAdaptive;
|
||||
|
||||
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
|
||||
if (adaptive_line_spacing == 0. && support_line_spacing == 0.)
|
||||
return std::make_pair(OctreePtr(), OctreePtr());
|
||||
|
||||
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
|
||||
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
|
||||
Transform3d m = m_trafo;
|
||||
m.translate(Vec3d(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0));
|
||||
its_transform(mesh, transform_to_octree().toRotationMatrix() * m, true);
|
||||
return std::make_pair(
|
||||
adaptive_line_spacing ? build_octree(mesh, adaptive_line_spacing, false) : OctreePtr(),
|
||||
support_line_spacing ? build_octree(mesh, support_line_spacing, true) : OctreePtr());
|
||||
}
|
||||
|
||||
void PrintObject::clear_layers()
|
||||
{
|
||||
for (Layer *l : m_layers)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Exception.hpp"
|
||||
#include "Print.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -13,7 +14,7 @@ unsigned int PrintRegion::extruder(FlowRole role) const
|
|||
else if (role == frSolidInfill || role == frTopSolidInfill)
|
||||
extruder = m_config.solid_infill_extruder;
|
||||
else
|
||||
throw std::invalid_argument("Unknown role");
|
||||
throw Slic3r::InvalidArgument("Unknown role");
|
||||
return extruder;
|
||||
}
|
||||
|
||||
|
@ -40,7 +41,7 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir
|
|||
} else if (role == frTopSolidInfill) {
|
||||
config_width = m_config.top_infill_extrusion_width;
|
||||
} else {
|
||||
throw std::invalid_argument("Unknown role");
|
||||
throw Slic3r::InvalidArgument("Unknown role");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/mutex.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_reduce.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include <libslic3r/libslic3r.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -21,28 +25,56 @@ template<> struct _ccr<true>
|
|||
using SpinningMutex = tbb::spin_mutex;
|
||||
using BlockingMutex = tbb::mutex;
|
||||
|
||||
template<class Fn, class It>
|
||||
static IteratorOnly<It, void> loop_(const tbb::blocked_range<It> &range, Fn &&fn)
|
||||
{
|
||||
for (auto &el : range) fn(el);
|
||||
}
|
||||
|
||||
template<class Fn, class I>
|
||||
static IntegerOnly<I, void> loop_(const tbb::blocked_range<I> &range, Fn &&fn)
|
||||
{
|
||||
for (I i = range.begin(); i < range.end(); ++i) fn(i);
|
||||
}
|
||||
|
||||
template<class It, class Fn>
|
||||
static IteratorOnly<It, void> for_each(It from,
|
||||
It to,
|
||||
Fn && fn,
|
||||
size_t granularity = 1)
|
||||
static void for_each(It from, It to, Fn &&fn, size_t granularity = 1)
|
||||
{
|
||||
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
|
||||
[&fn, from](const auto &range) {
|
||||
for (auto &el : range) fn(el);
|
||||
loop_(range, std::forward<Fn>(fn));
|
||||
});
|
||||
}
|
||||
|
||||
template<class I, class Fn>
|
||||
static IntegerOnly<I, void> for_each(I from,
|
||||
I to,
|
||||
Fn && fn,
|
||||
size_t granularity = 1)
|
||||
template<class I, class MergeFn, class T, class AccessFn>
|
||||
static T reduce(I from,
|
||||
I to,
|
||||
const T &init,
|
||||
MergeFn &&mergefn,
|
||||
AccessFn &&access,
|
||||
size_t granularity = 1
|
||||
)
|
||||
{
|
||||
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
|
||||
[&fn](const auto &range) {
|
||||
for (I i = range.begin(); i < range.end(); ++i) fn(i);
|
||||
});
|
||||
return tbb::parallel_reduce(
|
||||
tbb::blocked_range{from, to, granularity}, init,
|
||||
[&](const auto &range, T subinit) {
|
||||
T acc = subinit;
|
||||
loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); });
|
||||
return acc;
|
||||
},
|
||||
std::forward<MergeFn>(mergefn));
|
||||
}
|
||||
|
||||
template<class I, class MergeFn, class T>
|
||||
static IteratorOnly<I, T> reduce(I from,
|
||||
I to,
|
||||
const T & init,
|
||||
MergeFn &&mergefn,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return reduce(
|
||||
from, to, init, std::forward<MergeFn>(mergefn),
|
||||
[](typename I::value_type &i) { return i; }, granularity);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -55,23 +87,52 @@ public:
|
|||
using SpinningMutex = _Mtx;
|
||||
using BlockingMutex = _Mtx;
|
||||
|
||||
template<class It, class Fn>
|
||||
static IteratorOnly<It, void> for_each(It from,
|
||||
It to,
|
||||
Fn &&fn,
|
||||
size_t /* ignore granularity */ = 1)
|
||||
template<class Fn, class It>
|
||||
static IteratorOnly<It, void> loop_(It from, It to, Fn &&fn)
|
||||
{
|
||||
for (auto it = from; it != to; ++it) fn(*it);
|
||||
}
|
||||
|
||||
template<class I, class Fn>
|
||||
static IntegerOnly<I, void> for_each(I from,
|
||||
I to,
|
||||
Fn &&fn,
|
||||
size_t /* ignore granularity */ = 1)
|
||||
template<class Fn, class I>
|
||||
static IntegerOnly<I, void> loop_(I from, I to, Fn &&fn)
|
||||
{
|
||||
for (I i = from; i < to; ++i) fn(i);
|
||||
}
|
||||
|
||||
template<class It, class Fn>
|
||||
static void for_each(It from,
|
||||
It to,
|
||||
Fn &&fn,
|
||||
size_t /* ignore granularity */ = 1)
|
||||
{
|
||||
loop_(from, to, std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
template<class I, class MergeFn, class T, class AccessFn>
|
||||
static T reduce(I from,
|
||||
I to,
|
||||
const T & init,
|
||||
MergeFn &&mergefn,
|
||||
AccessFn &&access,
|
||||
size_t /*granularity*/ = 1
|
||||
)
|
||||
{
|
||||
T acc = init;
|
||||
loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); });
|
||||
return acc;
|
||||
}
|
||||
|
||||
template<class I, class MergeFn, class T>
|
||||
static IteratorOnly<I, T> reduce(I from,
|
||||
I to,
|
||||
const T &init,
|
||||
MergeFn &&mergefn,
|
||||
size_t /*granularity*/ = 1
|
||||
)
|
||||
{
|
||||
return reduce(from, to, init, std::forward<MergeFn>(mergefn),
|
||||
[](typename I::value_type &i) { return i; });
|
||||
}
|
||||
};
|
||||
|
||||
using ccr = _ccr<USE_FULL_CONCURRENCY>;
|
||||
|
|
|
@ -1,35 +1,259 @@
|
|||
#include <limits>
|
||||
#include <exception>
|
||||
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include <libslic3r/SLA/Rotfinder.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
|
||||
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
|
||||
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include "Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
#include <thread>
|
||||
|
||||
std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
||||
float accuracy,
|
||||
std::function<void(unsigned)> statuscb,
|
||||
std::function<bool()> stopcond)
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
inline bool is_on_floor(const SLAPrintObject &mo)
|
||||
{
|
||||
using libnest2d::opt::Method;
|
||||
using libnest2d::opt::bound;
|
||||
using libnest2d::opt::Optimizer;
|
||||
using libnest2d::opt::TOptimizer;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
auto opt_elevation = mo.config().support_object_elevation.getFloat();
|
||||
auto opt_padaround = mo.config().pad_around_object.getBool();
|
||||
|
||||
static const unsigned MAX_TRIES = 100000;
|
||||
return opt_elevation < EPSILON || opt_padaround;
|
||||
}
|
||||
|
||||
// Find transformed mesh ground level without copy and with parallel reduce.
|
||||
double find_ground_level(const TriangleMesh &mesh,
|
||||
const Transform3d & tr,
|
||||
size_t threads)
|
||||
{
|
||||
size_t vsize = mesh.its.vertices.size();
|
||||
|
||||
auto minfn = [](double a, double b) { return std::min(a, b); };
|
||||
|
||||
auto accessfn = [&mesh, &tr] (size_t vi) {
|
||||
return (tr * mesh.its.vertices[vi].template cast<double>()).z();
|
||||
};
|
||||
|
||||
double zmin = std::numeric_limits<double>::max();
|
||||
size_t granularity = vsize / threads;
|
||||
return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity);
|
||||
}
|
||||
|
||||
// Get the vertices of a triangle directly in an array of 3 points
|
||||
std::array<Vec3d, 3> get_triangle_vertices(const TriangleMesh &mesh,
|
||||
size_t faceidx)
|
||||
{
|
||||
const auto &face = mesh.its.indices[faceidx];
|
||||
return {Vec3d{mesh.its.vertices[face(0)].cast<double>()},
|
||||
Vec3d{mesh.its.vertices[face(1)].cast<double>()},
|
||||
Vec3d{mesh.its.vertices[face(2)].cast<double>()}};
|
||||
}
|
||||
|
||||
std::array<Vec3d, 3> get_transformed_triangle(const TriangleMesh &mesh,
|
||||
const Transform3d & tr,
|
||||
size_t faceidx)
|
||||
{
|
||||
const auto &tri = get_triangle_vertices(mesh, faceidx);
|
||||
return {tr * tri[0], tr * tri[1], tr * tri[2]};
|
||||
}
|
||||
|
||||
// Get area and normal of a triangle
|
||||
struct Facestats {
|
||||
Vec3d normal;
|
||||
double area;
|
||||
|
||||
explicit Facestats(const std::array<Vec3d, 3> &triangle)
|
||||
{
|
||||
Vec3d U = triangle[1] - triangle[0];
|
||||
Vec3d V = triangle[2] - triangle[0];
|
||||
Vec3d C = U.cross(V);
|
||||
normal = C.normalized();
|
||||
area = 0.5 * C.norm();
|
||||
}
|
||||
};
|
||||
|
||||
inline const Vec3d DOWN = {0., 0., -1.};
|
||||
constexpr double POINTS_PER_UNIT_AREA = 1.;
|
||||
|
||||
// The score function for a particular face
|
||||
inline double get_score(const Facestats &fc)
|
||||
{
|
||||
// Simply get the angle (acos of dot product) between the face normal and
|
||||
// the DOWN vector.
|
||||
double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI;
|
||||
|
||||
// Only consider faces that have have slopes below 90 deg:
|
||||
phi = phi * (phi > 0.5);
|
||||
|
||||
// Make the huge slopes more significant than the smaller slopes
|
||||
phi = phi * phi * phi;
|
||||
|
||||
// Multiply with the area of the current face
|
||||
return fc.area * POINTS_PER_UNIT_AREA * phi;
|
||||
}
|
||||
|
||||
template<class AccessFn>
|
||||
double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads)
|
||||
{
|
||||
double initv = 0.;
|
||||
auto mergefn = std::plus<double>{};
|
||||
size_t grainsize = facecount / Nthreads;
|
||||
size_t from = 0, to = facecount;
|
||||
|
||||
return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize);
|
||||
}
|
||||
|
||||
// Try to guess the number of support points needed to support a mesh
|
||||
double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr)
|
||||
{
|
||||
if (mesh.its.vertices.empty()) return std::nan("");
|
||||
|
||||
auto accessfn = [&mesh, &tr](size_t fi) {
|
||||
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
|
||||
return get_score(fc);
|
||||
};
|
||||
|
||||
size_t facecount = mesh.its.indices.size();
|
||||
size_t Nthreads = std::thread::hardware_concurrency();
|
||||
return sum_score(accessfn, facecount, Nthreads) / facecount;
|
||||
}
|
||||
|
||||
double get_model_supportedness_onfloor(const TriangleMesh &mesh,
|
||||
const Transform3d & tr)
|
||||
{
|
||||
if (mesh.its.vertices.empty()) return std::nan("");
|
||||
|
||||
size_t Nthreads = std::thread::hardware_concurrency();
|
||||
|
||||
double zmin = find_ground_level(mesh, tr, Nthreads);
|
||||
double zlvl = zmin + 0.1; // Set up a slight tolerance from z level
|
||||
|
||||
auto accessfn = [&mesh, &tr, zlvl](size_t fi) {
|
||||
std::array<Vec3d, 3> tri = get_transformed_triangle(mesh, tr, fi);
|
||||
Facestats fc{tri};
|
||||
|
||||
if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl)
|
||||
return -fc.area * POINTS_PER_UNIT_AREA;
|
||||
|
||||
return get_score(fc);
|
||||
};
|
||||
|
||||
size_t facecount = mesh.its.indices.size();
|
||||
return sum_score(accessfn, facecount, Nthreads) / facecount;
|
||||
}
|
||||
|
||||
using XYRotation = std::array<double, 2>;
|
||||
|
||||
// prepare the rotation transformation
|
||||
Transform3d to_transform3d(const XYRotation &rot)
|
||||
{
|
||||
Transform3d rt = Transform3d::Identity();
|
||||
rt.rotate(Eigen::AngleAxisd(rot[1], Vec3d::UnitY()));
|
||||
rt.rotate(Eigen::AngleAxisd(rot[0], Vec3d::UnitX()));
|
||||
return rt;
|
||||
}
|
||||
|
||||
XYRotation from_transform3d(const Transform3d &tr)
|
||||
{
|
||||
Vec3d rot3d = Geometry::Transformation {tr}.get_rotation();
|
||||
return {rot3d.x(), rot3d.y()};
|
||||
}
|
||||
|
||||
// Find the best score from a set of function inputs. Evaluate for every point.
|
||||
template<size_t N, class Fn, class It, class StopCond>
|
||||
std::array<double, N> find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn)
|
||||
{
|
||||
std::array<double, N> ret;
|
||||
|
||||
double score = std::numeric_limits<double>::max();
|
||||
|
||||
size_t Nthreads = std::thread::hardware_concurrency();
|
||||
size_t dist = std::distance(from, to);
|
||||
std::vector<double> scores(dist, score);
|
||||
|
||||
ccr_par::for_each(size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) {
|
||||
if (stopfn()) return;
|
||||
|
||||
scores[i] = fn(*(from + i));
|
||||
}, dist / Nthreads);
|
||||
|
||||
auto it = std::min_element(scores.begin(), scores.end());
|
||||
|
||||
if (it != scores.end()) ret = *(from + std::distance(scores.begin(), it));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// collect the rotations for each face of the convex hull
|
||||
std::vector<XYRotation> get_chull_rotations(const TriangleMesh &mesh, size_t max_count)
|
||||
{
|
||||
TriangleMesh chull = mesh.convex_hull_3d();
|
||||
chull.require_shared_vertices();
|
||||
double chull2d_area = chull.convex_hull().area();
|
||||
double area_threshold = chull2d_area / (scaled<double>(1e3) * scaled(1.));
|
||||
|
||||
size_t facecount = chull.its.indices.size();
|
||||
|
||||
struct RotArea { XYRotation rot; double area; };
|
||||
|
||||
auto inputs = reserve_vector<RotArea>(facecount);
|
||||
|
||||
auto rotcmp = [](const RotArea &r1, const RotArea &r2) {
|
||||
double xdiff = r1.rot[X] - r2.rot[X], ydiff = r1.rot[Y] - r2.rot[Y];
|
||||
return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.;
|
||||
};
|
||||
|
||||
auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) {
|
||||
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
|
||||
return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON;
|
||||
};
|
||||
|
||||
for (size_t fi = 0; fi < facecount; ++fi) {
|
||||
Facestats fc{get_triangle_vertices(chull, fi)};
|
||||
|
||||
if (fc.area > area_threshold) {
|
||||
auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN);
|
||||
XYRotation rot = from_transform3d(Transform3d::Identity() * q);
|
||||
RotArea ra = {rot, fc.area};
|
||||
|
||||
auto it = std::lower_bound(inputs.begin(), inputs.end(), ra, rotcmp);
|
||||
|
||||
if (it == inputs.end() || !eqcmp(it->rot, rot))
|
||||
inputs.insert(it, ra);
|
||||
}
|
||||
}
|
||||
|
||||
inputs.shrink_to_fit();
|
||||
if (!max_count) max_count = inputs.size();
|
||||
std::sort(inputs.begin(), inputs.end(),
|
||||
[](const RotArea &ra, const RotArea &rb) {
|
||||
return ra.area > rb.area;
|
||||
});
|
||||
|
||||
auto ret = reserve_vector<XYRotation>(std::min(max_count, inputs.size()));
|
||||
for (const RotArea &ra : inputs) ret.emplace_back(ra.rot);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vec2d find_best_rotation(const SLAPrintObject & po,
|
||||
float accuracy,
|
||||
std::function<void(unsigned)> statuscb,
|
||||
std::function<bool()> stopcond)
|
||||
{
|
||||
static const unsigned MAX_TRIES = 1000;
|
||||
|
||||
// return value
|
||||
std::array<double, 3> rot;
|
||||
XYRotation rot;
|
||||
|
||||
// We will use only one instance of this converted mesh to examine different
|
||||
// rotations
|
||||
const TriangleMesh& mesh = modelobj.raw_mesh();
|
||||
TriangleMesh mesh = po.model_object()->raw_mesh();
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
// For current iteration number
|
||||
// To keep track of the number of iterations
|
||||
unsigned status = 0;
|
||||
|
||||
// The maximum number of iterations
|
||||
|
@ -38,77 +262,61 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
|||
// call status callback with zero, because we are at the start
|
||||
statuscb(status);
|
||||
|
||||
// So this is the object function which is called by the solver many times
|
||||
// It has to yield a single value representing the current score. We will
|
||||
// call the status callback in each iteration but the actual value may be
|
||||
// the same for subsequent iterations (status goes from 0 to 100 but
|
||||
// iterations can be many more)
|
||||
auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries]
|
||||
(double rx, double ry, double rz)
|
||||
{
|
||||
const TriangleMesh& m = mesh;
|
||||
|
||||
// prepare the rotation transformation
|
||||
Transform3d rt = Transform3d::Identity();
|
||||
|
||||
rt.rotate(Eigen::AngleAxisd(rz, Vec3d::UnitZ()));
|
||||
rt.rotate(Eigen::AngleAxisd(ry, Vec3d::UnitY()));
|
||||
rt.rotate(Eigen::AngleAxisd(rx, Vec3d::UnitX()));
|
||||
|
||||
double score = 0;
|
||||
|
||||
// For all triangles we calculate the normal and sum up the dot product
|
||||
// (a scalar indicating how much are two vectors aligned) with each axis
|
||||
// this will result in a value that is greater if a normal is aligned
|
||||
// with all axes. If the normal is aligned than the triangle itself is
|
||||
// orthogonal to the axes and that is good for print quality.
|
||||
|
||||
// TODO: some applications optimize for minimum z-axis cross section
|
||||
// area. The current function is only an example of how to optimize.
|
||||
|
||||
// Later we can add more criteria like the number of overhangs, etc...
|
||||
for(size_t i = 0; i < m.stl.facet_start.size(); i++) {
|
||||
Vec3d n = m.stl.facet_start[i].normal.cast<double>();
|
||||
|
||||
// rotate the normal with the current rotation given by the solver
|
||||
n = rt * n;
|
||||
|
||||
// We should score against the alignment with the reference planes
|
||||
score += std::abs(n.dot(Vec3d::UnitX()));
|
||||
score += std::abs(n.dot(Vec3d::UnitY()));
|
||||
score += std::abs(n.dot(Vec3d::UnitZ()));
|
||||
}
|
||||
|
||||
auto statusfn = [&statuscb, &status, &max_tries] {
|
||||
// report status
|
||||
if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) );
|
||||
|
||||
return score;
|
||||
statuscb(unsigned(++status * 100.0/max_tries) );
|
||||
};
|
||||
|
||||
// Firing up the genetic optimizer. For now it uses the nlopt library.
|
||||
StopCriteria stc;
|
||||
stc.max_iterations = max_tries;
|
||||
stc.relative_score_difference = 1e-3;
|
||||
stc.stop_condition = stopcond; // stop when stopcond returns true
|
||||
TOptimizer<Method::G_GENETIC> solver(stc);
|
||||
// Different search methods have to be used depending on the model elevation
|
||||
if (is_on_floor(po)) {
|
||||
|
||||
// We are searching rotations around the three axes x, y, z. Thus the
|
||||
// problem becomes a 3 dimensional optimization task.
|
||||
// We can specify the bounds for a dimension in the following way:
|
||||
auto b = bound(-PI/2, PI/2);
|
||||
std::vector<XYRotation> inputs = get_chull_rotations(mesh, max_tries);
|
||||
max_tries = inputs.size();
|
||||
|
||||
// Now we start the optimization process with initial angles (0, 0, 0)
|
||||
auto result = solver.optimize_max(objfunc,
|
||||
libnest2d::opt::initvals(0.0, 0.0, 0.0),
|
||||
b, b, b);
|
||||
// If the model can be placed on the bed directly, we only need to
|
||||
// check the 3D convex hull face rotations.
|
||||
|
||||
// Save the result and fck off
|
||||
rot[0] = std::get<0>(result.optimum);
|
||||
rot[1] = std::get<1>(result.optimum);
|
||||
rot[2] = std::get<2>(result.optimum);
|
||||
auto objfn = [&mesh, &statusfn](const XYRotation &rot) {
|
||||
statusfn();
|
||||
Transform3d tr = to_transform3d(rot);
|
||||
return get_model_supportedness_onfloor(mesh, tr);
|
||||
};
|
||||
|
||||
return rot;
|
||||
rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond);
|
||||
} else {
|
||||
// Preparing the optimizer.
|
||||
size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls
|
||||
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{}
|
||||
.max_iterations(max_tries)
|
||||
.stop_condition(stopcond),
|
||||
gridsize);
|
||||
|
||||
// We are searching rotations around only two axes x, y. Thus the
|
||||
// problem becomes a 2 dimensional optimization task.
|
||||
// We can specify the bounds for a dimension in the following way:
|
||||
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
|
||||
|
||||
auto result = solver.to_min().optimize(
|
||||
[&mesh, &statusfn] (const XYRotation &rot)
|
||||
{
|
||||
statusfn();
|
||||
return get_model_supportedness(mesh, to_transform3d(rot));
|
||||
}, opt::initvals({0., 0.}), bounds);
|
||||
|
||||
// Save the result and fck off
|
||||
rot = result.optimum;
|
||||
}
|
||||
|
||||
return {rot[0], rot[1]};
|
||||
}
|
||||
|
||||
double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr)
|
||||
{
|
||||
TriangleMesh mesh = po.model_object()->raw_mesh();
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
return is_on_floor(po) ? get_model_supportedness_onfloor(mesh, tr) :
|
||||
get_model_supportedness(mesh, tr);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
#include <functional>
|
||||
#include <array>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelObject;
|
||||
class SLAPrintObject;
|
||||
|
||||
namespace sla {
|
||||
|
||||
|
@ -25,14 +27,17 @@ namespace sla {
|
|||
*
|
||||
* @return Returns the rotations around each axis (x, y, z)
|
||||
*/
|
||||
std::array<double, 3> find_best_rotation(
|
||||
const ModelObject& modelobj,
|
||||
Vec2d find_best_rotation(
|
||||
const SLAPrintObject& modelobj,
|
||||
float accuracy = 1.0f,
|
||||
std::function<void(unsigned)> statuscb = [] (unsigned) {},
|
||||
std::function<bool()> stopcond = [] () { return false; }
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
double get_model_supportedness(const SLAPrintObject &mesh,
|
||||
const Transform3d & tr);
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // SLAROTFINDER_HPP
|
||||
|
|
|
@ -163,10 +163,10 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
|||
SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1];
|
||||
//FIXME WTF?
|
||||
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
|
||||
const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||
const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle)));
|
||||
const float safe_angle = 35.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||
const float between_layers_offset = scaled<float>(layer_height * std::tan(safe_angle));
|
||||
const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||
const float slope_offset = float(scale_(layer_height / std::tan(slope_angle)));
|
||||
const float slope_offset = scaled<float>(layer_height * std::tan(slope_angle));
|
||||
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
|
||||
for (SupportPointGenerator::Structure &top : layer_above.islands) {
|
||||
for (SupportPointGenerator::Structure &bottom : layer_below.islands) {
|
||||
|
@ -181,6 +181,25 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
|||
Polygons bottom_polygons = top.polygons_below();
|
||||
top.overhangs = diff_ex(top_polygons, bottom_polygons);
|
||||
if (! top.overhangs.empty()) {
|
||||
|
||||
// Produce 2 bands around the island, a safe band for dangling overhangs
|
||||
// and an unsafe band for sloped overhangs.
|
||||
// These masks include the original island
|
||||
auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
|
||||
auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare);
|
||||
|
||||
// Absolutely hopeless overhangs are those outside the unsafe band
|
||||
top.overhangs = diff_ex(top_polygons, overh_mask);
|
||||
|
||||
// Now cut out the supported core from the safe band
|
||||
// and cut the safe band from the unsafe band to get distinct
|
||||
// zones.
|
||||
overh_mask = diff(overh_mask, dangl_mask);
|
||||
dangl_mask = diff(dangl_mask, bottom_polygons);
|
||||
|
||||
top.dangling_areas = intersection_ex(top_polygons, dangl_mask);
|
||||
top.overhangs_slopes = intersection_ex(top_polygons, overh_mask);
|
||||
|
||||
top.overhangs_area = 0.f;
|
||||
std::vector<std::pair<ExPolygon*, float>> expolys_with_areas;
|
||||
for (ExPolygon &ex : top.overhangs) {
|
||||
|
@ -196,8 +215,6 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
|||
overhangs_sorted.emplace_back(std::move(*p.first));
|
||||
top.overhangs = std::move(overhangs_sorted);
|
||||
top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR);
|
||||
top.overhangs_slopes = diff_ex(top_polygons, offset(bottom_polygons, slope_offset));
|
||||
top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,21 +273,9 @@ void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const
|
|||
// Now iterate over all polygons and append new points if needed.
|
||||
for (Structure &s : layer_top->islands) {
|
||||
// Penalization resulting from large diff from the last layer:
|
||||
// s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area);
|
||||
s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area);
|
||||
|
||||
//float force_deficit = s.support_force_deficit(m_config.tear_pressure());
|
||||
if (s.islands_below.empty()) { // completely new island - needs support no doubt
|
||||
uniformly_cover({ *s.polygon }, s, point_grid, true);
|
||||
} else if (! s.dangling_areas.empty()) {
|
||||
// Let's see if there's anything that overlaps enough to need supports:
|
||||
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
|
||||
//FIXME is it an island point or not? Vojtech thinks it is.
|
||||
uniformly_cover(s.dangling_areas, s, point_grid);
|
||||
} else if (! s.overhangs_slopes.empty()) {
|
||||
//FIXME add the support force deficit as a parameter, only cover until the defficiency is covered.
|
||||
uniformly_cover(s.overhangs_slopes, s, point_grid);
|
||||
}
|
||||
add_support_points(s, point_grid);
|
||||
}
|
||||
|
||||
m_throw_on_cancel();
|
||||
|
@ -288,6 +293,42 @@ void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const
|
|||
}
|
||||
}
|
||||
|
||||
void SupportPointGenerator::add_support_points(SupportPointGenerator::Structure &s, SupportPointGenerator::PointGrid3D &grid3d)
|
||||
{
|
||||
// Select each type of surface (overrhang, dangling, slope), derive the support
|
||||
// force deficit for it and call uniformly conver with the right params
|
||||
|
||||
float tp = m_config.tear_pressure();
|
||||
float current = s.supports_force_total();
|
||||
static constexpr float SLOPE_DAMPING = .0015f;
|
||||
static constexpr float DANGL_DAMPING = .09f;
|
||||
|
||||
if (s.islands_below.empty()) {
|
||||
// completely new island - needs support no doubt
|
||||
// deficit is full, there is nothing below that would hold this island
|
||||
uniformly_cover({ *s.polygon }, s, s.area * tp, grid3d, IslandCoverageFlags(icfIsNew | icfBoundaryOnly) );
|
||||
return;
|
||||
}
|
||||
|
||||
auto areafn = [](double sum, auto &p) { return sum + p.area() * SCALING_FACTOR * SCALING_FACTOR; };
|
||||
if (! s.dangling_areas.empty()) {
|
||||
// Let's see if there's anything that overlaps enough to need supports:
|
||||
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
|
||||
|
||||
double a = std::accumulate(s.dangling_areas.begin(), s.dangling_areas.end(), 0., areafn);
|
||||
uniformly_cover(s.dangling_areas, s, a * tp - current * DANGL_DAMPING * std::sqrt(1. - a / s.area), grid3d);
|
||||
}
|
||||
|
||||
if (! s.overhangs_slopes.empty()) {
|
||||
double a = std::accumulate(s.overhangs_slopes.begin(), s.overhangs_slopes.end(), 0., areafn);
|
||||
uniformly_cover(s.overhangs_slopes, s, a * tp - current * SLOPE_DAMPING * std::sqrt(1. - a / s.area), grid3d);
|
||||
}
|
||||
|
||||
if (! s.overhangs.empty()) {
|
||||
uniformly_cover(s.overhangs, s, s.overhangs_area * tp, grid3d);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng)
|
||||
{
|
||||
// Triangulate the polygon with holes into triplets of 3D points.
|
||||
|
@ -297,16 +338,16 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
|
|||
if (! triangles.empty())
|
||||
{
|
||||
// Calculate area of each triangle.
|
||||
std::vector<float> areas;
|
||||
areas.reserve(triangles.size() / 3);
|
||||
auto areas = reserve_vector<float>(triangles.size() / 3);
|
||||
double aback = 0.;
|
||||
for (size_t i = 0; i < triangles.size(); ) {
|
||||
const Vec2f &a = triangles[i ++];
|
||||
const Vec2f v1 = triangles[i ++] - a;
|
||||
const Vec2f v2 = triangles[i ++] - a;
|
||||
areas.emplace_back(0.5f * std::abs(cross2(v1, v2)));
|
||||
if (i != 3)
|
||||
// Prefix sum of the areas.
|
||||
areas.back() += areas[areas.size() - 2];
|
||||
|
||||
// Prefix sum of the areas.
|
||||
areas.emplace_back(aback + 0.5f * std::abs(cross2(v1, v2)));
|
||||
aback = areas.back();
|
||||
}
|
||||
|
||||
size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2));
|
||||
|
@ -316,7 +357,7 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
|
|||
double r = random_triangle(rng);
|
||||
size_t idx_triangle = std::min<size_t>(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3;
|
||||
// Select a random point on the triangle.
|
||||
double u = float(sqrt(random_float(rng)));
|
||||
double u = float(std::sqrt(random_float(rng)));
|
||||
double v = float(random_float(rng));
|
||||
const Vec2f &a = triangles[idx_triangle ++];
|
||||
const Vec2f &b = triangles[idx_triangle++];
|
||||
|
@ -328,16 +369,37 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
|
|||
return out;
|
||||
}
|
||||
|
||||
|
||||
std::vector<Vec2f> sample_expolygon(const ExPolygons &expolys, float samples_per_mm2, std::mt19937 &rng)
|
||||
{
|
||||
std::vector<Vec2f> out;
|
||||
for (const ExPolygon &expoly : expolys)
|
||||
append(out, sample_expolygon(expoly, samples_per_mm2, rng));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void sample_expolygon_boundary(const ExPolygon & expoly,
|
||||
float samples_per_mm,
|
||||
std::vector<Vec2f> &out,
|
||||
std::mt19937 & rng)
|
||||
{
|
||||
double point_stepping_scaled = scale_(1.f) / samples_per_mm;
|
||||
for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) {
|
||||
const Polygon &contour = (i_contour == 0) ? expoly.contour :
|
||||
expoly.holes[i_contour - 1];
|
||||
|
||||
const Points pts = contour.equally_spaced_points(point_stepping_scaled);
|
||||
for (size_t i = 0; i < pts.size(); ++ i)
|
||||
out.emplace_back(unscale<float>(pts[i].x()),
|
||||
unscale<float>(pts[i].y()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
|
||||
{
|
||||
std::vector<Vec2f> out = sample_expolygon(expoly, samples_per_mm2, rng);
|
||||
double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary;
|
||||
for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) {
|
||||
const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1];
|
||||
const Points pts = contour.equally_spaced_points(point_stepping_scaled);
|
||||
for (size_t i = 0; i < pts.size(); ++ i)
|
||||
out.emplace_back(unscale<float>(pts[i].x()), unscale<float>(pts[i].y()));
|
||||
}
|
||||
sample_expolygon_boundary(expoly, samples_per_mm_boundary, out, rng);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -359,17 +421,17 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
|
|||
}
|
||||
|
||||
// Assign the raw samples to grid cells, sort the grid cells lexicographically.
|
||||
struct RawSample {
|
||||
struct RawSample
|
||||
{
|
||||
Vec2f coord;
|
||||
Vec2i cell_id;
|
||||
RawSample(const Vec2f &crd = {}, const Vec2i &id = {}): coord{crd}, cell_id{id} {}
|
||||
};
|
||||
std::vector<RawSample> raw_samples_sorted;
|
||||
RawSample sample;
|
||||
for (const Vec2f &pt : raw_samples) {
|
||||
sample.coord = pt;
|
||||
sample.cell_id = ((pt - corner_min) / radius).cast<int>();
|
||||
raw_samples_sorted.emplace_back(sample);
|
||||
}
|
||||
|
||||
auto raw_samples_sorted = reserve_vector<RawSample>(raw_samples.size());
|
||||
for (const Vec2f &pt : raw_samples)
|
||||
raw_samples_sorted.emplace_back(pt, ((pt - corner_min) / radius).cast<int>());
|
||||
|
||||
std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs)
|
||||
{ return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); });
|
||||
|
||||
|
@ -464,11 +526,22 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
|
|||
return out;
|
||||
}
|
||||
|
||||
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
|
||||
|
||||
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags)
|
||||
{
|
||||
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
||||
|
||||
const float support_force_deficit = structure.support_force_deficit(m_config.tear_pressure());
|
||||
float support_force_deficit = deficit;
|
||||
auto bb = get_extents(islands);
|
||||
|
||||
if (flags & icfIsNew) {
|
||||
Vec2d bbdim = unscaled(Vec2crd{bb.max - bb.min});
|
||||
if (bbdim.x() > bbdim.y()) std::swap(bbdim.x(), bbdim.y());
|
||||
double aspectr = bbdim.y() / bbdim.x();
|
||||
|
||||
support_force_deficit *= (1 + aspectr / 2.);
|
||||
}
|
||||
|
||||
if (support_force_deficit < 0)
|
||||
return;
|
||||
|
||||
|
@ -485,13 +558,18 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
|
|||
float min_spacing = poisson_radius;
|
||||
|
||||
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
||||
|
||||
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, m_rng);
|
||||
|
||||
std::vector<Vec2f> raw_samples =
|
||||
flags & icfBoundaryOnly ?
|
||||
sample_expolygon_with_boundary(islands, samples_per_mm2,
|
||||
5.f / poisson_radius, m_rng) :
|
||||
sample_expolygon(islands, samples_per_mm2, m_rng);
|
||||
|
||||
std::vector<Vec2f> poisson_samples;
|
||||
for (size_t iter = 0; iter < 4; ++ iter) {
|
||||
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius,
|
||||
[&structure, &grid3d, min_spacing](const Vec2f &pos) {
|
||||
return grid3d.collides_with(pos, &structure, min_spacing);
|
||||
return grid3d.collides_with(pos, structure.layer->print_z, min_spacing);
|
||||
});
|
||||
if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON)
|
||||
break;
|
||||
|
@ -521,12 +599,13 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
|
|||
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
|
||||
}
|
||||
for (const Vec2f &pt : poisson_samples) {
|
||||
m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, m_config.head_diameter/2.f, is_new_island);
|
||||
m_output.emplace_back(float(pt(0)), float(pt(1)), structure.zlevel, m_config.head_diameter/2.f, flags & icfIsNew);
|
||||
structure.supports_force_this_layer += m_config.support_force();
|
||||
grid3d.insert(pt, &structure);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl)
|
||||
{
|
||||
// get iterator to the reorganized vector end
|
||||
|
|
|
@ -22,8 +22,9 @@ public:
|
|||
float density_relative {1.f};
|
||||
float minimal_distance {1.f};
|
||||
float head_diameter {0.4f};
|
||||
///////////////
|
||||
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
|
||||
|
||||
// Originally calibrated to 7.7f, reduced density by Tamas to 70% which is 11.1 (7.7 / 0.7) to adjust for new algorithm changes in tm_suppt_gen_improve
|
||||
inline float support_force() const { return 11.1f / density_relative; } // a force one point can support (arbitrary force unit)
|
||||
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
||||
};
|
||||
|
||||
|
@ -38,8 +39,8 @@ public:
|
|||
struct MyLayer;
|
||||
|
||||
struct Structure {
|
||||
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) :
|
||||
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
|
||||
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) :
|
||||
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), zlevel(h)
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
|
@ -49,7 +50,7 @@ public:
|
|||
const BoundingBox bbox;
|
||||
const Vec2f centroid = Vec2f::Zero();
|
||||
const float area = 0.f;
|
||||
float height = 0;
|
||||
float zlevel = 0;
|
||||
// How well is this ExPolygon held to the print base?
|
||||
// Positive number, the higher the better.
|
||||
float supports_force_this_layer = 0.f;
|
||||
|
@ -159,8 +160,8 @@ public:
|
|||
grid.emplace(cell_id(pt.position), pt);
|
||||
}
|
||||
|
||||
bool collides_with(const Vec2f &pos, Structure *island, float radius) {
|
||||
Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z));
|
||||
bool collides_with(const Vec2f &pos, float print_z, float radius) {
|
||||
Vec3f pos3d(pos.x(), pos.y(), print_z);
|
||||
Vec3i cell = cell_id(pos3d);
|
||||
std::pair<Grid::const_iterator, Grid::const_iterator> it_pair = grid.equal_range(cell);
|
||||
if (collides_with(pos3d, radius, it_pair.first, it_pair.second))
|
||||
|
@ -198,7 +199,16 @@ private:
|
|||
SupportPointGenerator::Config m_config;
|
||||
|
||||
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
|
||||
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
|
||||
|
||||
public:
|
||||
enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfBoundaryOnly = 0x2 };
|
||||
|
||||
private:
|
||||
|
||||
void uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags = icfNone);
|
||||
|
||||
void add_support_points(Structure& structure, PointGrid3D &grid3d);
|
||||
|
||||
void project_onto_mesh(std::vector<SupportPoint>& points) const;
|
||||
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
|
@ -215,6 +225,9 @@ private:
|
|||
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl);
|
||||
|
||||
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng);
|
||||
void sample_expolygon_boundary(const ExPolygon &expoly, float samples_per_mm, std::vector<Vec2f> &out, std::mt19937 &rng);
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SUPPORTPOINTGENERATOR_HPP
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/Optimizer.hpp>
|
||||
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include <libslic3r/Exception.hpp>
|
||||
#include <libslic3r/SLAPrintSteps.hpp>
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
|
||||
|
@ -187,7 +188,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
|||
}
|
||||
|
||||
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal))
|
||||
throw std::runtime_error(L("Too much overlapping holes."));
|
||||
throw Slic3r::SlicingError(L("Too many overlapping holes."));
|
||||
|
||||
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
|
||||
|
||||
|
@ -195,7 +196,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
|||
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
|
||||
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
|
||||
} catch (const std::runtime_error &) {
|
||||
throw std::runtime_error(L(
|
||||
throw Slic3r::SlicingError(L(
|
||||
"Drilling holes into the mesh failed. "
|
||||
"This is usually caused by broken model. Try to fix it first."));
|
||||
}
|
||||
|
@ -241,7 +242,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
|||
|
||||
if(slindex_it == po.m_slice_index.end())
|
||||
//TRN To be shown at the status bar on SLA slicing error.
|
||||
throw std::runtime_error(
|
||||
throw Slic3r::RuntimeError(
|
||||
L("Slicing had to be stopped due to an internal error: "
|
||||
"Inconsistent slice index."));
|
||||
|
||||
|
@ -445,7 +446,7 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
|
|||
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
|
||||
|
||||
if (!validate_pad(pad_mesh, pcfg))
|
||||
throw std::runtime_error(
|
||||
throw Slic3r::SlicingError(
|
||||
L("No pad can be generated for this model with the "
|
||||
"current configuration"));
|
||||
|
||||
|
@ -613,7 +614,7 @@ void SLAPrint::Steps::initialize_printer_input()
|
|||
|
||||
for(const SliceRecord& slicerecord : o->get_slice_index()) {
|
||||
if (!slicerecord.is_valid())
|
||||
throw std::runtime_error(
|
||||
throw Slic3r::SlicingError(
|
||||
L("There are unprintable objects. Try to "
|
||||
"adjust support settings to make the "
|
||||
"objects printable."));
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
#include "semver/semver.h"
|
||||
|
||||
#include "Exception.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
|
@ -38,7 +40,7 @@ public:
|
|||
{
|
||||
auto parsed = parse(str);
|
||||
if (! parsed) {
|
||||
throw std::runtime_error(std::string("Could not parse version string: ") + str);
|
||||
throw Slic3r::RuntimeError(std::string("Could not parse version string: ") + str);
|
||||
}
|
||||
ver = parsed->ver;
|
||||
parsed->ver = semver_zero();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Exception.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
|
@ -70,7 +71,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3i> &fac
|
|||
stl_get_size(&stl);
|
||||
}
|
||||
|
||||
TriangleMesh::TriangleMesh(const indexed_triangle_set &M)
|
||||
TriangleMesh::TriangleMesh(const indexed_triangle_set &M) : repaired(false)
|
||||
{
|
||||
stl.stats.type = inmemory;
|
||||
|
||||
|
@ -420,7 +421,7 @@ std::deque<uint32_t> TriangleMesh::find_unvisited_neighbors(std::vector<unsigned
|
|||
{
|
||||
// Make sure we're not operating on a broken mesh.
|
||||
if (!this->repaired)
|
||||
throw std::runtime_error("find_unvisited_neighbors() requires repair()");
|
||||
throw Slic3r::RuntimeError("find_unvisited_neighbors() requires repair()");
|
||||
|
||||
// If the visited list is empty, populate it with false for every facet.
|
||||
if (facet_visited.empty())
|
||||
|
@ -683,7 +684,7 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac
|
|||
{
|
||||
mesh = _mesh;
|
||||
if (! mesh->has_shared_vertices())
|
||||
throw std::invalid_argument("TriangleMeshSlicer was passed a mesh without shared vertices.");
|
||||
throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices.");
|
||||
|
||||
throw_on_cancel();
|
||||
facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1);
|
||||
|
|
|
@ -348,11 +348,11 @@ inline std::string get_time_dhm(float time_in_secs)
|
|||
|
||||
char buffer[64];
|
||||
if (days > 0)
|
||||
::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs);
|
||||
::sprintf(buffer, "%dd %dh %dm", days, hours, minutes);
|
||||
else if (hours > 0)
|
||||
::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs);
|
||||
::sprintf(buffer, "%dh %dm", hours, minutes);
|
||||
else if (minutes > 0)
|
||||
::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs);
|
||||
::sprintf(buffer, "%dm", minutes);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <exception>
|
||||
|
||||
#include "Exception.hpp"
|
||||
#include "Zipper.hpp"
|
||||
#include "miniz_extension.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
@ -29,7 +30,7 @@ public:
|
|||
|
||||
SLIC3R_NORETURN void blow_up() const
|
||||
{
|
||||
throw std::runtime_error(formatted_errorstr());
|
||||
throw Slic3r::RuntimeError(formatted_errorstr());
|
||||
}
|
||||
|
||||
bool is_alive()
|
||||
|
|
|
@ -6,4 +6,7 @@
|
|||
#define SLIC3R_VERSION "@SLIC3R_VERSION@"
|
||||
#define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@"
|
||||
|
||||
#define GCODEVIEWER_APP_NAME "@GCODEVIEWER_APP_NAME@"
|
||||
#define GCODEVIEWER_BUILD_ID "@GCODEVIEWER_BUILD_ID@"
|
||||
|
||||
#endif /* __SLIC3R_VERSION_H */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue