Merge branch 'master' into et_tm_sla_volumes_6-SPE-1285

This commit is contained in:
tamasmeszaros 2023-01-18 16:40:12 +01:00
commit 76d0e11699
72 changed files with 9347 additions and 4714 deletions

View File

@ -0,0 +1,2 @@
min_slic3r_version = 2.6.0-alpha0
1.0.0 Initial

File diff suppressed because it is too large Load Diff

View File

@ -67,9 +67,9 @@ public:
double m_t = infty(); double m_t = infty();
int m_face_id = -1; int m_face_id = -1;
const AABBMesh *m_mesh = nullptr; const AABBMesh *m_mesh = nullptr;
Vec3d m_dir; Vec3d m_dir = Vec3d::Zero();
Vec3d m_source; Vec3d m_source = Vec3d::Zero();
Vec3d m_normal; Vec3d m_normal = Vec3d::Zero();
friend class AABBMesh; friend class AABBMesh;
// A valid object of this class can only be obtained from // A valid object of this class can only be obtained from

View File

@ -36,6 +36,10 @@ static const std::string MODEL_PREFIX = "model:";
// are phased out, then we will revert to the original name. // are phased out, then we will revert to the original name.
//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; //static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2";
// Url to index archive zip that contains latest indicies
static const std::string INDEX_ARCHIVE_URL= "https://files.prusa3d.com/wp-content/uploads/repository/vendor_indices.zip";
// Url to folder with vendor profile files. Used when downloading new profiles that are not in resources folder.
static const std::string PROFILE_FOLDER_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/";
const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
@ -68,6 +72,8 @@ void AppConfig::set_defaults()
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. // If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
if (get("no_defaults").empty()) if (get("no_defaults").empty())
set("no_defaults", "1"); set("no_defaults", "1");
if (get("no_templates").empty())
set("no_templates", "0");
if (get("show_incompatible_presets").empty()) if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0"); set("show_incompatible_presets", "0");
@ -665,6 +671,26 @@ std::string AppConfig::version_check_url() const
return from_settings.empty() ? VERSION_CHECK_URL : from_settings; return from_settings.empty() ? VERSION_CHECK_URL : from_settings;
} }
std::string AppConfig::index_archive_url() const
{
#if 0
// this code is for debug & testing purposes only - changed url wont get trough inner checks anyway.
auto from_settings = get("index_archive_url");
return from_settings.empty() ? INDEX_ARCHIVE_URL : from_settings;
#endif
return INDEX_ARCHIVE_URL;
}
std::string AppConfig::profile_folder_url() const
{
#if 0
// this code is for debug & testing purposes only - changed url wont get trough inner checks anyway.
auto from_settings = get("profile_folder_url");
return from_settings.empty() ? PROFILE_FOLDER_URL : from_settings;
#endif
return PROFILE_FOLDER_URL;
}
bool AppConfig::exists() bool AppConfig::exists()
{ {
return boost::filesystem::exists(config_path()); return boost::filesystem::exists(config_path());

View File

@ -139,6 +139,11 @@ public:
// Get the Slic3r version check url. // Get the Slic3r version check url.
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
std::string version_check_url() const; std::string version_check_url() const;
// Get the Slic3r url to vendor index archive zip.
std::string index_archive_url() const;
// Get the Slic3r url to folder with vendor profile files.
std::string profile_folder_url() const;
// Returns the original Slic3r version found in the ini file before it was overwritten // Returns the original Slic3r version found in the ini file before it was overwritten
// by the current version // by the current version

View File

@ -5,7 +5,7 @@
#include <optional> #include <optional>
#include <algorithm> #include <algorithm>
#include "libslic3r/SLA/SupportTreeUtils.hpp" #include "libslic3r/TriangleMesh.hpp"
namespace Slic3r { namespace branchingtree { namespace Slic3r { namespace branchingtree {
@ -76,18 +76,20 @@ void build_tree(PointCloud &nodes, Builder &builder)
switch (type) { switch (type) {
case BED: { case BED: {
closest_node.weight = w; closest_node.weight = w;
if (closest_it->dst_branching > nodes.properties().max_branch_length()) { double max_br_len = nodes.properties().max_branch_length();
auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; if (closest_it->dst_branching > max_br_len) {
Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; std::optional<Vec3f> avo = builder.suggest_avoidance(node, max_br_len);
new_node.id = int(nodes.next_junction_id()); if (!avo)
new_node.weight = nodes.get(node_id).weight + hl_br_len; break;
new_node.left = node.id;
Node new_node {*avo, node.Rmin};
new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).norm();
new_node.left = node.id;
if ((routed = builder.add_bridge(node, new_node))) { if ((routed = builder.add_bridge(node, new_node))) {
size_t new_idx = nodes.insert_junction(new_node); size_t new_idx = nodes.insert_junction(new_node);
ptsqueue.push(new_idx); ptsqueue.push(new_idx);
} }
} } else if ((routed = builder.add_ground_bridge(node, closest_node))) {
else if ((routed = builder.add_ground_bridge(node, closest_node))) {
closest_node.left = closest_node.right = node_id; closest_node.left = closest_node.right = node_id;
nodes.get(closest_node_id) = closest_node; nodes.get(closest_node_id) = closest_node;
nodes.mark_unreachable(closest_node_id); nodes.mark_unreachable(closest_node_id);

View File

@ -5,7 +5,6 @@
#include <admesh/stl.h> #include <admesh/stl.h>
#include "libslic3r/ExPolygon.hpp" #include "libslic3r/ExPolygon.hpp"
#include "libslic3r/BoundingBox.hpp"
namespace Slic3r { namespace branchingtree { namespace Slic3r { namespace branchingtree {
@ -21,6 +20,7 @@ class Properties
ExPolygons m_bed_shape; ExPolygons m_bed_shape;
public: public:
// Maximum slope for bridges of the tree // Maximum slope for bridges of the tree
Properties &max_slope(double val) noexcept Properties &max_slope(double val) noexcept
{ {
@ -77,6 +77,11 @@ struct Node
{} {}
}; };
inline bool is_occupied(const Node &n)
{
return n.left != Node::ID_NONE && n.right != Node::ID_NONE;
}
// An output interface for the branching tree generator function. Consider each // An output interface for the branching tree generator function. Consider each
// method as a callback and implement the actions that need to be done. // method as a callback and implement the actions that need to be done.
class Builder class Builder
@ -100,6 +105,12 @@ public:
// Add an anchor bridge to the model body // Add an anchor bridge to the model body
virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0; virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0;
virtual std::optional<Vec3f> suggest_avoidance(const Node &from,
float max_bridge_len) const
{
return {};
}
// Report nodes that can not be routed to an endpoint (model or ground) // Report nodes that can not be routed to an endpoint (model or ground)
virtual void report_unroutable(const Node &j) = 0; virtual void report_unroutable(const Node &j) = 0;

View File

@ -1,82 +1,15 @@
#include "PointCloud.hpp" #include "PointCloud.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Tesselate.hpp" #include "libslic3r/Tesselate.hpp"
#include "libslic3r/SLA/SupportTreeUtils.hpp"
#include <igl/random_points_on_mesh.h> #include <igl/random_points_on_mesh.h>
namespace Slic3r { namespace branchingtree { namespace Slic3r { namespace branchingtree {
std::optional<Vec3f> find_merge_pt(const Vec3f &A, std::optional<Vec3f> find_merge_pt(const Vec3f &A, const Vec3f &B, float max_slope)
const Vec3f &B,
float critical_angle)
{ {
// The idea is that A and B both have their support cones. But searching return sla::find_merge_pt(A, B, max_slope);
// for the intersection of these support cones is difficult and its enough
// to reduce this problem to 2D and search for the intersection of two
// rays that merge somewhere between A and B. The 2D plane is a vertical
// slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and
// the 2D X axis is determined by the XY direction of the AB vector.
//
// Z^
// | A *
// | . . B *
// | . . . .
// | . . . .
// | . x .
// -------------------> XY
// Determine the transformation matrix for the 2D projection:
Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f};
Vec3f dir = diff.normalized(); // TODO: avoid normalization
Eigen::Matrix<float, 2, 3> tr2D;
tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()};
tr2D.row(1) = Vec3f{0.f, 0.f, 1.f};
// Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can
// omit 'a', pretend that its the origin and use BA as the vector b.
Vec2f b = tr2D * (B - A);
// Get the square sine of the ray emanating from 'a' towards 'b'. This ray might
// exceed the allowed angle but that is corrected subsequently.
// The sign of the original sine is also needed, hence b.y is multiplied by
// abs(b.y)
float b_sqn = b.squaredNorm();
float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f;
// sine2 from 'b' to 'a' is the opposite of sine2 from a to b
float sin2sig_b = -sin2sig_a;
// Derive the allowed angles from the given critical angle.
// critical_angle is measured from the horizontal X axis.
// The rays need to go downwards which corresponds to negative angles
float sincrit = std::sin(critical_angle); // sine of the critical angle
float sin2crit = -sincrit * sincrit; // signed sine squared
sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays
sin2sig_b = std::min(sin2sig_b, sin2crit); //
float sin2_a = std::abs(sin2sig_a); // Get cosine squared values
float sin2_b = std::abs(sin2sig_b);
float cos2_a = 1.f - sin2_a;
float cos2_b = 1.f - sin2_b;
// Derive the new direction vectors. This is by square rooting the sin2
// and cos2 values and restoring the original signs
Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)};
Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)};
// Determine where two rays ([0, 0], Da), (b, Db) intersect.
// Based on
// https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect
// One ray is emanating from (0, 0) so the formula is simplified
double t1 = (Db.y() * b.x() - b.y() * Db.x()) /
(Da.x() * Db.y() - Da.y() * Db.x());
Vec2f mp = t1 * Da;
Vec3f Mp = A + tr2D.transpose() * mp;
return t1 >= 0.f ? Mp : Vec3f{};
} }
void to_eigen_mesh(const indexed_triangle_set &its, void to_eigen_mesh(const indexed_triangle_set &its,
@ -141,8 +74,6 @@ std::vector<Node> sample_mesh(const indexed_triangle_set &its, double radius)
std::vector<Node> sample_bed(const ExPolygons &bed, float z, double radius) std::vector<Node> sample_bed(const ExPolygons &bed, float z, double radius)
{ {
std::vector<Vec3f> ret;
auto triangles = triangulate_expolygons_3d(bed, z); auto triangles = triangulate_expolygons_3d(bed, z);
indexed_triangle_set its; indexed_triangle_set its;
its.vertices.reserve(triangles.size()); its.vertices.reserve(triangles.size());
@ -198,7 +129,10 @@ PointCloud::PointCloud(std::vector<Node> meshpts,
for (size_t i = 0; i < m_leafs.size(); ++i) { for (size_t i = 0; i < m_leafs.size(); ++i) {
Node &n = m_leafs[i]; Node &n = m_leafs[i];
n.id = int(LEAFS_BEGIN + i); n.id = int(LEAFS_BEGIN + i);
n.left = Node::ID_NONE;
n.right = Node::ID_NONE;
m_ktree.insert({n.pos, n.id}); m_ktree.insert({n.pos, n.id});
} }
} }

View File

@ -5,7 +5,7 @@
#include "BranchingTree.hpp" #include "BranchingTree.hpp"
#include "libslic3r/Execution/Execution.hpp" //#include "libslic3r/Execution/Execution.hpp"
#include "libslic3r/MutablePriorityQueue.hpp" #include "libslic3r/MutablePriorityQueue.hpp"
#include "libslic3r/BoostAdapter.hpp" #include "libslic3r/BoostAdapter.hpp"
@ -78,14 +78,6 @@ private:
rtree<PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */> rtree<PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>
m_ktree; m_ktree;
bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const
{
Vec3d D = (pt - supp).cast<double>();
double dot_sq = -D.z() * std::abs(-D.z());
return dot_sq < D.squaredNorm() * cos2bridge_slope;
}
template<class PC> template<class PC>
static auto *get_node(PC &&pc, size_t id) static auto *get_node(PC &&pc, size_t id)
{ {
@ -104,6 +96,14 @@ private:
public: public:
bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const
{
Vec3d D = (pt - supp).cast<double>();
double dot_sq = -D.z() * std::abs(-D.z());
return dot_sq < D.squaredNorm() * cos2bridge_slope;
}
static constexpr auto Unqueued = size_t(-1); static constexpr auto Unqueued = size_t(-1);
struct ZCompareFn struct ZCompareFn
@ -255,18 +255,42 @@ public:
} }
}; };
template<class Fn> constexpr bool IsTraverseFn = std::is_invocable_v<Fn, Node&>;
struct TraverseReturnT
{
bool to_left : 1; // if true, continue traversing to the left
bool to_right : 1; // if true, continue traversing to the right
};
template<class PC, class Fn> void traverse(PC &&pc, size_t root, Fn &&fn) template<class PC, class Fn> void traverse(PC &&pc, size_t root, Fn &&fn)
{ {
if (auto nodeptr = pc.find(root); nodeptr != nullptr) { if (auto nodeptr = pc.find(root); nodeptr != nullptr) {
auto &nroot = *nodeptr; auto &nroot = *nodeptr;
fn(nroot); TraverseReturnT ret{true, true};
if (nroot.left >= 0) traverse(pc, nroot.left, fn);
if (nroot.right >= 0) traverse(pc, nroot.right, fn); if constexpr (std::is_same_v<std::invoke_result_t<Fn, decltype(nroot)>, void>) {
// Our fn has no return value
fn(nroot);
} else {
// Fn returns instructions about how to continue traversing
ret = fn(nroot);
}
if (ret.to_left && nroot.left >= 0)
traverse(pc, nroot.left, fn);
if (ret.to_right && nroot.right >= 0)
traverse(pc, nroot.right, fn);
} }
} }
void build_tree(PointCloud &pcloud, Builder &builder); void build_tree(PointCloud &pcloud, Builder &builder);
inline void build_tree(PointCloud &&pc, Builder &builder)
{
build_tree(pc, builder);
}
}} // namespace Slic3r::branchingtree }} // namespace Slic3r::branchingtree
#endif // POINTCLOUD_HPP #endif // POINTCLOUD_HPP

View File

@ -82,12 +82,11 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
if (floating_polylines.empty()) { if (floating_polylines.empty()) {
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges // consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
//use 3mm resolution (should be quite fast, and rough estimation should not cause any problems here) auto [pc1, pc2] = compute_principal_components(overhang_area);
auto [pc1, pc2] = compute_principal_components(overhang_area, 3.0); if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
if (pc2 == Vec2d::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
return {Vec2d{1.0,0.0}, 0.0}; return {Vec2d{1.0,0.0}, 0.0};
} else { } else {
return {pc2.normalized(), 0.0}; return {pc2.normalized().cast<double>(), 0.0};
} }
} }

View File

@ -331,6 +331,7 @@ set(SLIC3R_SOURCES
SLA/SupportTreeMesher.hpp SLA/SupportTreeMesher.hpp
SLA/SupportTreeMesher.cpp SLA/SupportTreeMesher.cpp
SLA/SupportTreeUtils.hpp SLA/SupportTreeUtils.hpp
SLA/SupportTreeUtilsLegacy.hpp
SLA/SupportTreeBuilder.cpp SLA/SupportTreeBuilder.cpp
SLA/SupportTree.hpp SLA/SupportTree.hpp
SLA/SupportTree.cpp SLA/SupportTree.cpp

View File

@ -445,8 +445,11 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
f->angle = surface_fill.params.angle; f->angle = surface_fill.params.angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
if (surface_fill.params.pattern == ipLightning) if (surface_fill.params.pattern == ipLightning) {
dynamic_cast<FillLightning::Filler*>(f.get())->generator = lightning_generator; auto *lf = dynamic_cast<FillLightning::Filler*>(f.get());
lf->generator = lightning_generator;
lf->num_raft_layers = this->object()->slicing_parameters().raft_layers();
}
if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) { if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) {
FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get()); FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get());

View File

@ -13,7 +13,7 @@ void Filler::_fill_surface_single(
ExPolygon expolygon, ExPolygon expolygon,
Polylines &polylines_out) Polylines &polylines_out)
{ {
const Layer &layer = generator->getTreesForLayer(this->layer_id); const Layer &layer = generator->getTreesForLayer(this->layer_id - this->num_raft_layers);
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap)); Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap));
if (params.dont_connect() || fill_lines.size() <= 1) { if (params.dont_connect() || fill_lines.size() <= 1) {

View File

@ -22,6 +22,7 @@ public:
~Filler() override = default; ~Filler() override = default;
Generator *generator { nullptr }; Generator *generator { nullptr };
size_t num_raft_layers { 0 };
protected: protected:
Fill* clone() const override { return new Filler(*this); } Fill* clone() const override { return new Filler(*this); }

View File

@ -577,12 +577,22 @@ void Layer::sort_perimeters_into_islands(
} }
if (! sample_set) { if (! sample_set) {
// If there is no infill, take a sample of some inner perimeter. // If there is no infill, take a sample of some inner perimeter.
for (uint32_t iperimeter : extrusions.first) for (uint32_t iperimeter : extrusions.first) {
if (const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter]; ! ee.role().is_external()) { const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter];
sample = ee.first_point(); if (ee.is_collection()) {
for (const ExtrusionEntity *ee2 : dynamic_cast<const ExtrusionEntityCollection&>(ee).entities)
if (! ee2->role().is_external()) {
sample = ee2->first_point();
sample_set = true;
goto loop_end;
}
} else if (! ee.role().is_external()) {
sample = ee.first_point();
sample_set = true; sample_set = true;
break; break;
} }
}
loop_end:
if (! sample_set) { if (! sample_set) {
if (! extrusions.second.empty()) { if (! extrusions.second.empty()) {
// If there is no inner perimeter, take a sample of some gap fill extrusion. // If there is no inner perimeter, take a sample of some gap fill extrusion.
@ -752,7 +762,9 @@ void Layer::sort_perimeters_into_islands(
const PrintRegionConfig &region_config = this_layer_region.region().config(); const PrintRegionConfig &region_config = this_layer_region.region().config();
const auto bbox_eps = scaled<coord_t>( const auto bbox_eps = scaled<coord_t>(
EPSILON + print_config.gcode_resolution.value + EPSILON + print_config.gcode_resolution.value +
(region_config.fuzzy_skin.value == FuzzySkinType::None ? 0. : region_config.fuzzy_skin_thickness.value)); (region_config.fuzzy_skin.value == FuzzySkinType::None ? 0. : region_config.fuzzy_skin_thickness.value
//FIXME it looks as if Arachne could extend open lines by fuzzy_skin_point_dist, which does not seem right.
+ region_config.fuzzy_skin_point_dist.value));
auto point_inside_surface_dist2 = auto point_inside_surface_dist2 =
[&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps] [&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps]
(const size_t lslice_idx, const Point &point) { (const size_t lslice_idx, const Point &point) {

View File

@ -13,7 +13,9 @@ template<size_t N>
long num_iter(const std::array<size_t, N> &idx, size_t gridsz) long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
{ {
long ret = 0; long ret = 0;
for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i); for (size_t i = 0; i < N; ++i)
ret += idx[i] * std::pow(gridsz, i);
return ret; return ret;
} }

View File

@ -13,7 +13,7 @@
#include <utility> #include <utility>
#include <libslic3r/Optimize/Optimizer.hpp> #include "Optimizer.hpp"
namespace Slic3r { namespace opt { namespace Slic3r { namespace opt {
@ -40,69 +40,163 @@ struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
static const constexpr bool value = true; static const constexpr bool value = true;
}; };
// NLopt can wrap any of its algorithms to use the augmented lagrangian method
// for deriving an object function from all equality and inequality constraints
// This way one can use algorithms that do not support these constraints natively
template<class Alg> struct NLoptAUGLAG {};
template<nlopt_algorithm a1, nlopt_algorithm a2>
struct IsNLoptAlg<NLoptAUGLAG<NLoptAlgComb<a1, a2>>> {
static const constexpr bool value = true;
};
template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAUGLAG<NLoptAlg<a>>> {
static const constexpr bool value = true;
};
template<class M, class T = void> template<class M, class T = void>
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>; using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
template<class M> struct GetNLoptAlg_ {
static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS;
static constexpr nlopt_algorithm Global = NLOPT_NUM_ALGORITHMS;
static constexpr bool IsAUGLAG = false;
};
template<nlopt_algorithm a> struct GetNLoptAlg_<NLoptAlg<a>> {
static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS;
static constexpr nlopt_algorithm Global = a;
static constexpr bool IsAUGLAG = false;
};
template<nlopt_algorithm g, nlopt_algorithm l>
struct GetNLoptAlg_<NLoptAlgComb<g, l>> {
static constexpr nlopt_algorithm Local = l;
static constexpr nlopt_algorithm Global = g;
static constexpr bool IsAUGLAG = false;
};
template<class M> constexpr nlopt_algorithm GetNLoptAlg_Global = GetNLoptAlg_<remove_cvref_t<M>>::Global;
template<class M> constexpr nlopt_algorithm GetNLoptAlg_Local = GetNLoptAlg_<remove_cvref_t<M>>::Local;
template<class M> constexpr bool IsAUGLAG = GetNLoptAlg_<remove_cvref_t<M>>::IsAUGLAG;
template<class M> struct GetNLoptAlg_<NLoptAUGLAG<M>> {
static constexpr nlopt_algorithm Local = GetNLoptAlg_Local<M>;
static constexpr nlopt_algorithm Global = GetNLoptAlg_Global<M>;
static constexpr bool IsAUGLAG = true;
};
enum class OptDir { MIN, MAX }; // Where to optimize enum class OptDir { MIN, MAX }; // Where to optimize
struct NLopt { // Helper RAII class for nlopt_opt struct NLoptRAII { // Helper RAII class for nlopt_opt
nlopt_opt ptr = nullptr; nlopt_opt ptr = nullptr;
template<class...A> explicit NLopt(A&&...a) template<class...A> explicit NLoptRAII(A&&...a)
{ {
ptr = nlopt_create(std::forward<A>(a)...); ptr = nlopt_create(std::forward<A>(a)...);
} }
NLopt(const NLopt&) = delete; NLoptRAII(const NLoptRAII&) = delete;
NLopt(NLopt&&) = delete; NLoptRAII(NLoptRAII&&) = delete;
NLopt& operator=(const NLopt&) = delete; NLoptRAII& operator=(const NLoptRAII&) = delete;
NLopt& operator=(NLopt&&) = delete; NLoptRAII& operator=(NLoptRAII&&) = delete;
~NLopt() { nlopt_destroy(ptr); } ~NLoptRAII() { nlopt_destroy(ptr); }
}; };
template<class Method> class NLoptOpt {}; // Map a generic function to each argument following the mapping function
template<class Fn, class...Args>
Fn for_each_argument(Fn &&fn, Args&&...args)
{
// see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/
(fn(std::forward<Args>(args)),...);
// Optimizers based on NLopt. return fn;
template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> { }
protected:
// Call fn on each element of the input tuple tup.
template<class Fn, class Tup>
Fn for_each_in_tuple(Fn fn, Tup &&tup)
{
auto mpfn = [&fn](auto&...pack) {
for_each_argument(fn, pack...);
};
std::apply(mpfn, tup);
return fn;
}
// Wrap each element of the tuple tup into a wrapper class W and return
// a new tuple with each element being of type W<T_i> where T_i is the type of
// i-th element of tup.
template<template<class> class W, class...Args>
auto wrap_tup(const std::tuple<Args...> &tup)
{
return std::tuple<W<Args>...>(tup);
}
template<class M, class = NLoptOnly<M>>
class NLoptOpt {
StopCriteria m_stopcr; StopCriteria m_stopcr;
OptDir m_dir; StopCriteria m_loc_stopcr;
OptDir m_dir = OptDir::MIN;
template<class Fn> using TOptData = static constexpr double ConstraintEps = 1e-6;
std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
template<class Fn> struct OptData {
Fn fn;
NLoptOpt *self = nullptr;
nlopt_opt opt_raw = nullptr;
OptData(const Fn &f): fn{f} {}
OptData(const Fn &f, NLoptOpt *s, nlopt_opt nlopt_raw)
: fn{f}, self{s}, opt_raw{nlopt_raw} {}
};
template<class Fn, size_t N> template<class Fn, size_t N>
static double optfunc(unsigned n, const double *params, static double optfunc(unsigned n, const double *params,
double *gradient, double *gradient, void *data)
void *data)
{ {
assert(n >= N); assert(n == N);
auto tdata = static_cast<TOptData<Fn>*>(data); auto tdata = static_cast<OptData<Fn>*>(data);
if (std::get<1>(*tdata)->m_stopcr.stop_condition()) if (tdata->self->m_stopcr.stop_condition())
nlopt_force_stop(std::get<2>(*tdata)); nlopt_force_stop(tdata->opt_raw);
auto fnptr = std::get<0>(*tdata);
auto funval = to_arr<N>(params); auto funval = to_arr<N>(params);
double scoreval = 0.; double scoreval = 0.;
using RetT = decltype((*fnptr)(funval)); using RetT = decltype(tdata->fn(funval));
if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) { if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) {
ScoreGradient<N> score = (*fnptr)(funval); ScoreGradient<N> score = tdata->fn(funval);
for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i];
scoreval = score.score; scoreval = score.score;
} else { } else {
scoreval = (*fnptr)(funval); scoreval = tdata->fn(funval);
} }
return scoreval; return scoreval;
} }
template<class Fn, size_t N>
static double constrain_func(unsigned n, const double *params,
double *gradient, void *data)
{
assert(n == N);
auto tdata = static_cast<OptData<Fn>*>(data);
auto funval = to_arr<N>(params);
return tdata->fn(funval);
}
template<size_t N> template<size_t N>
void set_up(NLopt &nl, const Bounds<N>& bounds) static void set_up(NLoptRAII &nl,
const Bounds<N> &bounds,
const StopCriteria &stopcr)
{ {
std::array<double, N> lb, ub; std::array<double, N> lb, ub;
@ -114,23 +208,45 @@ protected:
nlopt_set_lower_bounds(nl.ptr, lb.data()); nlopt_set_lower_bounds(nl.ptr, lb.data());
nlopt_set_upper_bounds(nl.ptr, ub.data()); nlopt_set_upper_bounds(nl.ptr, ub.data());
double abs_diff = m_stopcr.abs_score_diff(); double abs_diff = stopcr.abs_score_diff();
double rel_diff = m_stopcr.rel_score_diff(); double rel_diff = stopcr.rel_score_diff();
double stopval = m_stopcr.stop_score(); double stopval = stopcr.stop_score();
if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff); if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff);
if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff);
if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval); if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval);
if(m_stopcr.max_iterations() > 0) if(stopcr.max_iterations() > 0)
nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations()); nlopt_set_maxeval(nl.ptr, stopcr.max_iterations());
} }
template<class Fn, size_t N> template<class Fn, size_t N, class...EqFns, class...IneqFns>
Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals) Result<N> optimize(NLoptRAII &nl, Fn &&fn, const Input<N> &initvals,
const std::tuple<EqFns...> &equalities,
const std::tuple<IneqFns...> &inequalities)
{ {
Result<N> r; Result<N> r;
TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr); OptData<Fn> data {fn, this, nl.ptr};
auto do_for_each_eq = [this, &nl](auto &arg) {
arg.self = this;
arg.opt_raw = nl.ptr;
using F = decltype(arg.fn);
nlopt_add_equality_constraint (nl.ptr, constrain_func<F, N>, &arg, ConstraintEps);
};
auto do_for_each_ineq = [this, &nl](auto &arg) {
arg.self = this;
arg.opt_raw = nl.ptr;
using F = decltype(arg.fn);
nlopt_add_inequality_constraint (nl.ptr, constrain_func<F, N>, &arg, ConstraintEps);
};
auto eq_data = wrap_tup<OptData>(equalities);
for_each_in_tuple(do_for_each_eq, eq_data);
auto ineq_data = wrap_tup<OptData>(inequalities);
for_each_in_tuple(do_for_each_ineq, ineq_data);
switch(m_dir) { switch(m_dir) {
case OptDir::MIN: case OptDir::MIN:
@ -147,51 +263,81 @@ protected:
public: public:
template<class Func, size_t N> template<class Fn, size_t N, class...EqFns, class...IneqFns>
Result<N> optimize(Func&& func, Result<N> optimize(Fn&& f,
const Input<N> &initvals, const Input<N> &initvals,
const Bounds<N>& bounds) const Bounds<N>& bounds,
const std::tuple<EqFns...> &equalities,
const std::tuple<IneqFns...> &inequalities)
{ {
NLopt nl{alg, N}; if constexpr (IsAUGLAG<M>) {
set_up(nl, bounds); NLoptRAII nl_wrap{NLOPT_AUGLAG, N};
set_up(nl_wrap, bounds, get_criteria());
return optimize(nl, std::forward<Func>(func), initvals); NLoptRAII nl_glob{GetNLoptAlg_Global<M>, N};
set_up(nl_glob, bounds, get_criteria());
nlopt_set_local_optimizer(nl_wrap.ptr, nl_glob.ptr);
if constexpr (GetNLoptAlg_Local<M> < NLOPT_NUM_ALGORITHMS) {
NLoptRAII nl_loc{GetNLoptAlg_Local<M>, N};
set_up(nl_loc, bounds, m_loc_stopcr);
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
return optimize(nl_wrap, std::forward<Fn>(f), initvals,
equalities, inequalities);
} else {
return optimize(nl_wrap, std::forward<Fn>(f), initvals,
equalities, inequalities);
}
} else {
NLoptRAII nl_glob{GetNLoptAlg_Global<M>, N};
set_up(nl_glob, bounds, get_criteria());
if constexpr (GetNLoptAlg_Local<M> < NLOPT_NUM_ALGORITHMS) {
NLoptRAII nl_loc{GetNLoptAlg_Local<M>, N};
set_up(nl_loc, bounds, m_loc_stopcr);
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
return optimize(nl_glob, std::forward<Fn>(f), initvals,
equalities, inequalities);
} else {
return optimize(nl_glob, std::forward<Fn>(f), initvals,
equalities, inequalities);
}
}
assert(false);
return {};
} }
explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {} explicit NLoptOpt(const StopCriteria &stopcr_glob = {})
: m_stopcr(stopcr_glob)
{}
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
const StopCriteria &get_criteria() const noexcept { return m_stopcr; } const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
void set_dir(OptDir dir) noexcept { m_dir = dir; }
void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; }
const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; }
void set_dir(OptDir dir) noexcept { m_dir = dir; }
void seed(long s) { nlopt_srand(s); } void seed(long s) { nlopt_srand(s); }
}; };
template<nlopt_algorithm glob, nlopt_algorithm loc> template<class Alg> struct AlgFeatures_ {
class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>> static constexpr bool SupportsInequalities = false;
{ static constexpr bool SupportsEqualities = false;
using Base = NLoptOpt<NLoptAlg<glob>>;
public:
template<class Fn, size_t N>
Result<N> optimize(Fn&& f,
const Input<N> &initvals,
const Bounds<N>& bounds)
{
NLopt nl_glob{glob, N}, nl_loc{loc, N};
Base::set_up(nl_glob, bounds);
Base::set_up(nl_loc, bounds);
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
return Base::optimize(nl_glob, std::forward<Fn>(f), initvals);
}
explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {}
}; };
} // namespace detail; } // namespace detail;
template<class Alg> constexpr bool SupportsEqualities =
detail::AlgFeatures_<remove_cvref_t<Alg>>::SupportsEqualities;
template<class Alg> constexpr bool SupportsInequalities =
detail::AlgFeatures_<remove_cvref_t<Alg>>::SupportsInequalities;
// Optimizers based on NLopt. // Optimizers based on NLopt.
template<class M> class Optimizer<M, detail::NLoptOnly<M>> { template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
detail::NLoptOpt<M> m_opt; detail::NLoptOpt<M> m_opt;
@ -201,12 +347,24 @@ public:
Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; } Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; }
Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; } Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; }
template<class Func, size_t N> template<class Func, size_t N, class...EqFns, class...IneqFns>
Result<N> optimize(Func&& func, Result<N> optimize(Func&& func,
const Input<N> &initvals, const Input<N> &initvals,
const Bounds<N>& bounds) const Bounds<N>& bounds,
const std::tuple<EqFns...> &eq_constraints = {},
const std::tuple<IneqFns...> &ineq_constraint = {})
{ {
return m_opt.optimize(std::forward<Func>(func), initvals, bounds); static_assert(std::tuple_size_v<std::tuple<EqFns...>> == 0
|| SupportsEqualities<M>,
"Equality constraints are not supported.");
static_assert(std::tuple_size_v<std::tuple<IneqFns...>> == 0
|| SupportsInequalities<M>,
"Inequality constraints are not supported.");
return m_opt.optimize(std::forward<Func>(func), initvals, bounds,
eq_constraints,
ineq_constraint);
} }
explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {}
@ -219,14 +377,56 @@ public:
const StopCriteria &get_criteria() const { return m_opt.get_criteria(); } const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
void seed(long s) { m_opt.seed(s); } void seed(long s) { m_opt.seed(s); }
void set_loc_criteria(const StopCriteria &cr) { m_opt.set_loc_criteria(cr); }
const StopCriteria &get_loc_criteria() const noexcept { return m_opt.get_loc_criteria(); }
}; };
// Predefinded NLopt algorithms // Predefinded NLopt algorithms
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>; using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>; using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>; using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>; using AlgNLoptCobyla = detail::NLoptAlg<NLOPT_LN_COBYLA>;
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>; using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
using AlgNLoptORIG_DIRECT = detail::NLoptAlg<NLOPT_GN_ORIG_DIRECT>;
using AlgNLoptISRES = detail::NLoptAlg<NLOPT_GN_ISRES>;
using AlgNLoptAGS = detail::NLoptAlg<NLOPT_GN_AGS>;
using AlgNLoptMLSL_Subplx = detail::NLoptAlgComb<NLOPT_GN_MLSL_LDS, NLOPT_LN_SBPLX>;
using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb<NLOPT_GN_MLSL, NLOPT_LN_COBYLA>;
using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb<NLOPT_GN_ESCH, NLOPT_LN_SBPLX>;
// To craft auglag algorithms (constraint support through object function transformation)
using detail::NLoptAUGLAG;
namespace detail {
template<> struct AlgFeatures_<AlgNLoptCobyla> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = true;
};
template<> struct AlgFeatures_<AlgNLoptISRES> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = false;
};
template<> struct AlgFeatures_<AlgNLoptORIG_DIRECT> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = false;
};
template<> struct AlgFeatures_<AlgNLoptAGS> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = true;
};
template<class M> struct AlgFeatures_<NLoptAUGLAG<M>> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = true;
};
} // namespace detail
}} // namespace Slic3r::opt }} // namespace Slic3r::opt

View File

@ -12,6 +12,15 @@
namespace Slic3r { namespace opt { namespace Slic3r { namespace opt {
template<class T, class O = T>
using FloatingOnly = std::enable_if_t<std::is_floating_point<T>::value, O>;
template<class T, class = FloatingOnly<T>>
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
constexpr float NaNf = NaN<float>;
constexpr double NaNd = NaN<double>;
// A type to hold the complete result of the optimization. // A type to hold the complete result of the optimization.
template<size_t N> struct Result { template<size_t N> struct Result {
int resultcode; // Method dependent int resultcode; // Method dependent
@ -79,7 +88,7 @@ public:
double stop_score() const { return m_stop_score; } double stop_score() const { return m_stop_score; }
StopCriteria & max_iterations(double val) StopCriteria & max_iterations(unsigned val)
{ {
m_max_iterations = val; return *this; m_max_iterations = val; return *this;
} }
@ -137,16 +146,25 @@ public:
// For each dimension an interval (Bound) has to be given marking the bounds // For each dimension an interval (Bound) has to be given marking the bounds
// for that dimension. // for that dimension.
// //
// Optionally, some constraints can be given in the form of double(Input<N>)
// functors. The parameters eq_constraints and ineq_constraints can be used
// to add equality and inequality (<= 0) constraints to the optimization.
// Note that it is up the the particular method if it accepts these
// constraints.
//
// initvals have to be within the specified bounds, otherwise its undefined // initvals have to be within the specified bounds, otherwise its undefined
// behavior. // behavior.
// //
// Func can return a score of type double or optionally a ScoreGradient // Func can return a score of type double or optionally a ScoreGradient
// class to indicate the function gradient for a optimization methods that // class to indicate the function gradient for a optimization methods that
// make use of the gradient. // make use of the gradient.
template<class Func, size_t N> template<class Func, size_t N, class...EqConstraints, class...IneqConstraints>
Result<N> optimize(Func&& /*func*/, Result<N> optimize(Func&& /*func*/,
const Input<N> &/*initvals*/, const Input<N> &/*initvals*/,
const Bounds<N>& /*bounds*/) { return {}; } const Bounds<N>& /*bounds*/,
const std::tuple<EqConstraints...> &eq_constraints = {},
const std::tuple<IneqConstraints...> &ineq_constraint = {}
) { return {}; }
// optional for randomized methods: // optional for randomized methods:
void seed(long /*s*/) {} void seed(long /*s*/) {}

View File

@ -146,6 +146,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
res.changelog_url = changelog_url->second.data(); res.changelog_url = changelog_url->second.data();
} }
const auto templates_profile = vendor_section.find("templates_profile");
if (templates_profile != vendor_section.not_found()) {
res.templates_profile = templates_profile->second.data() == "1";
}
if (! load_all) { if (! load_all) {
return res; return res;
} }
@ -200,6 +205,10 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
} }
model.bed_model = section.second.get<std::string>("bed_model", ""); model.bed_model = section.second.get<std::string>("bed_model", "");
model.bed_texture = section.second.get<std::string>("bed_texture", ""); model.bed_texture = section.second.get<std::string>("bed_texture", "");
model.thumbnail = section.second.get<std::string>("thumbnail", "");
if (model.thumbnail.empty())
model.thumbnail = model.id + "_thumbnail.png";
if (! model.id.empty() && ! model.variants.empty()) if (! model.id.empty() && ! model.variants.empty())
res.models.push_back(std::move(model)); res.models.push_back(std::move(model));
} }
@ -336,7 +345,8 @@ std::string Preset::label() const
bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer) bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer)
{ {
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) // templates_profile vendor profiles should be decided as same vendor profiles
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor && !preset.vendor->templates_profile)
// The current profile has a vendor assigned and it is different from the active print's vendor. // The current profile has a vendor assigned and it is different from the active print's vendor.
return false; return false;
auto &condition = preset.preset.compatible_prints_condition(); auto &condition = preset.preset.compatible_prints_condition();
@ -358,7 +368,8 @@ bool is_compatible_with_print(const PresetWithVendorProfile &preset, const Prese
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config) bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config)
{ {
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) // templates_profile vendor profiles should be decided as same vendor profiles
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor && !preset.vendor->templates_profile)
// The current profile has a vendor assigned and it is different from the active print's vendor. // The current profile has a vendor assigned and it is different from the active print's vendor.
return false; return false;
auto &condition = preset.preset.compatible_printers_condition(); auto &condition = preset.preset.compatible_printers_condition();
@ -495,12 +506,14 @@ static std::vector<std::string> s_Preset_sla_print_options {
"faded_layers", "faded_layers",
"supports_enable", "supports_enable",
"support_tree_type", "support_tree_type",
"support_head_front_diameter", "support_head_front_diameter",
"support_head_penetration", "support_head_penetration",
"support_head_width", "support_head_width",
"support_pillar_diameter", "support_pillar_diameter",
"support_small_pillar_diameter_percent", "support_small_pillar_diameter_percent",
"support_max_bridges_on_pillar", "support_max_bridges_on_pillar",
"support_max_weight_on_model",
"support_pillar_connection_mode", "support_pillar_connection_mode",
"support_buildplate_only", "support_buildplate_only",
"support_enforcers_only", "support_enforcers_only",
@ -512,6 +525,25 @@ static std::vector<std::string> s_Preset_sla_print_options {
"support_max_bridge_length", "support_max_bridge_length",
"support_max_pillar_link_distance", "support_max_pillar_link_distance",
"support_object_elevation", "support_object_elevation",
"branchingsupport_head_front_diameter",
"branchingsupport_head_penetration",
"branchingsupport_head_width",
"branchingsupport_pillar_diameter",
"branchingsupport_small_pillar_diameter_percent",
"branchingsupport_max_bridges_on_pillar",
"branchingsupport_max_weight_on_model",
"branchingsupport_pillar_connection_mode",
"branchingsupport_buildplate_only",
"branchingsupport_pillar_widening_factor",
"branchingsupport_base_diameter",
"branchingsupport_base_height",
"branchingsupport_base_safety_distance",
"branchingsupport_critical_angle",
"branchingsupport_max_bridge_length",
"branchingsupport_max_pillar_link_distance",
"branchingsupport_object_elevation",
"support_points_density_relative", "support_points_density_relative",
"support_points_minimal_distance", "support_points_minimal_distance",
"slice_closing_radius", "slice_closing_radius",
@ -1165,6 +1197,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
if (opt) if (opt)
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size())); config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
bool some_compatible = false; bool some_compatible = false;
std::vector<size_t> indices_of_template_presets;
for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) { for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) {
bool selected = idx_preset == m_idx_selected; bool selected = idx_preset == m_idx_selected;
Preset &preset_selected = m_presets[idx_preset]; Preset &preset_selected = m_presets[idx_preset];
@ -1181,7 +1214,29 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
m_idx_selected = size_t(-1); m_idx_selected = size_t(-1);
if (selected) if (selected)
preset_selected.is_compatible = preset_edited.is_compatible; preset_selected.is_compatible = preset_edited.is_compatible;
if (preset_edited.vendor && preset_edited.vendor->templates_profile) {
indices_of_template_presets.push_back(idx_preset);
}
} }
// filter out template profiles where profile with same alias and compability exists
if (!indices_of_template_presets.empty()) {
for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++idx_preset) {
if (m_presets[idx_preset].vendor && !m_presets[idx_preset].vendor->templates_profile && m_presets[idx_preset].is_compatible) {
std::string preset_alias = m_presets[idx_preset].alias;
for (size_t idx_of_template_in_presets : indices_of_template_presets) {
if (m_presets[idx_of_template_in_presets].alias == preset_alias) {
// unselect selected template filament if there is non-template alias compatible
if (idx_of_template_in_presets == m_idx_selected && (unselect_if_incompatible == PresetSelectCompatibleType::Always || unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible)) {
m_idx_selected = size_t(-1);
}
m_presets[idx_of_template_in_presets].is_compatible = false;
break;
}
}
}
}
}
// Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile. // Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile.
if (m_idx_selected >= m_num_default_presets && m_default_suppressed) if (m_idx_selected >= m_num_default_presets && m_default_suppressed)
for (size_t i = 0; i < m_num_default_presets; ++ i) for (size_t i = 0; i < m_num_default_presets; ++ i)
@ -2079,6 +2134,25 @@ namespace PresetUtils {
} }
return out; return out;
} }
bool vendor_profile_has_all_resources(const VendorProfile& vp)
{
namespace fs = boost::filesystem;
std::string vendor_folder = Slic3r::data_dir() + "/vendor/" + vp.id + "/";
std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/";
std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/";
for (const VendorProfile::PrinterModel& model : vp.models) {
for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail } ) {
if (! res.empty()
&& !fs::exists(fs::path(vendor_folder + res))
&& !fs::exists(fs::path(rsrc_folder + res))
&& !fs::exists(fs::path(cache_folder + res)))
return false;
}
}
return true;
}
} // namespace PresetUtils } // namespace PresetUtils
} // namespace Slic3r } // namespace Slic3r

View File

@ -34,6 +34,7 @@ public:
Semver config_version; Semver config_version;
std::string config_update_url; std::string config_update_url;
std::string changelog_url; std::string changelog_url;
bool templates_profile { false };
struct PrinterVariant { struct PrinterVariant {
PrinterVariant() {} PrinterVariant() {}
@ -52,6 +53,7 @@ public:
// Vendor & Printer Model specific print bed model & texture. // Vendor & Printer Model specific print bed model & texture.
std::string bed_model; std::string bed_model;
std::string bed_texture; std::string bed_texture;
std::string thumbnail;
PrinterVariant* variant(const std::string &name) { PrinterVariant* variant(const std::string &name) {
for (auto &v : this->variants) for (auto &v : this->variants)
@ -619,6 +621,7 @@ namespace PresetUtils {
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
std::string system_printer_bed_model(const Preset& preset); std::string system_printer_bed_model(const Preset& preset);
std::string system_printer_bed_texture(const Preset& preset); std::string system_printer_bed_texture(const Preset& preset);
bool vendor_profile_has_all_resources(const VendorProfile& vp);
} // namespace PresetUtils } // namespace PresetUtils

View File

@ -159,6 +159,7 @@ void PresetBundle::setup_directories()
data_dir, data_dir,
data_dir / "vendor", data_dir / "vendor",
data_dir / "cache", data_dir / "cache",
data_dir / "cache" / "vendor",
data_dir / "shapes", data_dir / "shapes",
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
// Store the print/filament/printer presets into a "presets" directory. // Store the print/filament/printer presets into a "presets" directory.
@ -1307,17 +1308,26 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
try { try {
pt::read_ini(ifs, tree); pt::read_ini(ifs, tree);
} catch (const boost::property_tree::ini_parser::ini_parser_error &err) { } catch (const boost::property_tree::ini_parser::ini_parser_error &err) {
throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); // This throw was uncatched. While other similar problems later are just returning empty pair.
//throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str());
BOOST_LOG_TRIVIAL(error) << format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str();
return std::make_pair(PresetsConfigSubstitutions{}, 0);
} }
} }
const VendorProfile *vendor_profile = nullptr; const VendorProfile *vendor_profile = nullptr;
if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) { if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) {
auto vp = VendorProfile::from_ini(tree, path); VendorProfile vp;
if (vp.models.size() == 0) { try {
vp = VendorProfile::from_ini(tree, path);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Failed to open profile file.") % path;
return std::make_pair(PresetsConfigSubstitutions{}, 0);
}
if (vp.models.size() == 0 && !vp.templates_profile) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path;
return std::make_pair(PresetsConfigSubstitutions{}, 0); return std::make_pair(PresetsConfigSubstitutions{}, 0);
} else if (vp.num_variants() == 0) { } else if (vp.num_variants() == 0 && !vp.templates_profile) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path; BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path;
return std::make_pair(PresetsConfigSubstitutions{}, 0); return std::make_pair(PresetsConfigSubstitutions{}, 0);
} }
@ -1359,6 +1369,9 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
} else if (boost::starts_with(section.first, "filament:")) { } else if (boost::starts_with(section.first, "filament:")) {
presets = &this->filaments; presets = &this->filaments;
preset_name = section.first.substr(9); preset_name = section.first.substr(9);
if (vendor_profile && vendor_profile->templates_profile) {
preset_name += " @Template";
}
} else if (boost::starts_with(section.first, "sla_print:")) { } else if (boost::starts_with(section.first, "sla_print:")) {
presets = &this->sla_prints; presets = &this->sla_prints;
preset_name = section.first.substr(10); preset_name = section.first.substr(10);

View File

@ -3,53 +3,97 @@
namespace Slic3r { namespace Slic3r {
// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first
std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, const double unscaled_resolution)
// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance
// none of the values is divided/normalized by area.
// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous)
// f(x,y) = x^2 for second moment of area
// and f(x,y) = x*y for second moment of area covariance
std::tuple<float, Vec2f, Vec2f, float> compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c)
{ {
// USING UNSCALED VALUES // based on the following guide:
const Vec2d pixel_size = Vec2d(unscaled_resolution, unscaled_resolution); // Denote the vertices of S by a, b, c. Then the map
const auto bb = get_extents(polys); // g:(u,v)↦a+u(ba)+v(ca) ,
const Vec2i pixel_count = unscaled(bb.size()).cwiseQuotient(pixel_size).cast<int>() + Vec2i::Ones(); // which in coordinates appears as
// g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1a1)+v(c1a1)=a2+u(b2a2)+v(c2a2) ,(1)
// obviously maps S bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain
// ∫Sf(x,y)d(x,y)=∫Sf(x(u,v),y(u,v))Jg(u,v) d(u,v) .
// In the case at hand the Jacobian determinant is a constant: From (1) we obtain
// Jg(u,v)=det[xuyuxvyv]=(b1a1)(c2a2)(c1a1)(b2a2) .
// Therefore we can write
// ∫Sf(x,y)d(x,y)=Jg∫10∫1u0f~(u,v) dv du ,
// where f~ denotes the pullback of f to S:
// f~(u,v):=f(x(u,v),y(u,v)) .
// Don't forget taking the absolute value of Jg!
std::vector<Linef> lines{}; float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y()));
for (Line l : to_lines(polys)) { lines.emplace_back(unscaled(l.a), unscaled(l.b)); }
AABBTreeIndirect::Tree<2, double> tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
auto is_inside = [&](const Vec2d &point) {
size_t nearest_line_index_out = 0;
Vec2d nearest_point_out = Vec2d::Zero();
auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, point, nearest_line_index_out, nearest_point_out);
if (distance < 0) return false;
const Linef &line = lines[nearest_line_index_out];
Vec2d v1 = line.b - line.a;
Vec2d v2 = point - line.a;
if ((v1.x() * v2.y()) - (v1.y() * v2.x()) > 0.0) { return true; }
return false;
};
double pixel_area = pixel_size.x() * pixel_size.y(); // coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x)
Vec2d centroid_accumulator = Vec2d::Zero(); // coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y)
Vec2d second_moment_of_area_accumulator = Vec2d::Zero(); // second moment of area for x: f(x, y) = x^2;
double second_moment_of_area_covariance_accumulator = 0.0; // f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form)
double area = 0.0;
for (int x = 0; x < pixel_count.x(); x++) { // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du
for (int y = 0; y < pixel_count.y(); y++) { // integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2)
Vec2d position = unscaled(bb.min) + pixel_size.cwiseProduct(Vec2d{x, y});
if (is_inside(position)) { Vec2f second_moment_of_area_xy = jacobian_determinant_abs *
area += pixel_area; (a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) +
centroid_accumulator += pixel_area * position; a.cwiseProduct(b + c)) /
second_moment_of_area_accumulator += pixel_area * position.cwiseProduct(position); 12.0f;
second_moment_of_area_covariance_accumulator += pixel_area * position.x() * position.y(); // second moment of area covariance : f(x, y) = x*y;
} // f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form)
//(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2))
// == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2))
// intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv =
// 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2
// b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u -
// 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du =
// 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2)
// result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du
float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) *
(a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() +
b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y());
float area = jacobian_determinant_abs * 0.5f;
Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f;
return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance};
};
// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first
std::tuple<Vec2f, Vec2f> compute_principal_components(const Polygons &polys)
{
Vec2f centroid_accumulator = Vec2f::Zero();
Vec2f second_moment_of_area_accumulator = Vec2f::Zero();
float second_moment_of_area_covariance_accumulator = 0.0f;
float area = 0.0f;
for (const Polygon &poly : polys) {
Vec2f p0 = unscaled(poly.first_point()).cast<float>();
for (size_t i = 2; i < poly.points.size(); i++) {
Vec2f p1 = unscaled(poly.points[i - 1]).cast<float>();
Vec2f p2 = unscaled(poly.points[i]).cast<float>();
float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f;
auto [triangle_area, first_moment_of_area, second_moment_area,
second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2);
area += sign * triangle_area;
centroid_accumulator += sign * first_moment_of_area;
second_moment_of_area_accumulator += sign * second_moment_area;
second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance;
} }
} }
if (area <= 0.0) { if (area <= 0.0) {
return {Vec2d::Zero(), Vec2d::Zero()}; return {Vec2f::Zero(), Vec2f::Zero()};
} }
Vec2d centroid = centroid_accumulator / area; Vec2f centroid = centroid_accumulator / area;
Vec2d variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid); Vec2f variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid);
double covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); double covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y();
#if 0 #if 0
std::cout << "area : " << area << std::endl; std::cout << "area : " << area << std::endl;
@ -58,7 +102,7 @@ std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, con
std::cout << "covariance : " << covariance << std::endl; std::cout << "covariance : " << covariance << std::endl;
#endif #endif
if (abs(covariance) < EPSILON) { if (abs(covariance) < EPSILON) {
std::tuple<Vec2d, Vec2d> result{Vec2d{variance.x(), 0.0}, Vec2d{0.0, variance.y()}}; std::tuple<Vec2f, Vec2f> result{Vec2f{variance.x(), 0.0}, Vec2f{0.0, variance.y()}};
if (variance.y() > variance.x()) { if (variance.y() > variance.x()) {
return {std::get<1>(result), std::get<0>(result)}; return {std::get<1>(result), std::get<0>(result)};
} else } else
@ -72,12 +116,12 @@ std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, con
// Eigenvalues are solutions to det(C - lI) = 0, where l is the eigenvalue and I unit matrix // Eigenvalues are solutions to det(C - lI) = 0, where l is the eigenvalue and I unit matrix
// Eigenvector for eigenvalue l is any vector v such that Cv = lv // Eigenvector for eigenvalue l is any vector v such that Cv = lv
double eigenvalue_a = 0.5 * (variance.x() + variance.y() + float eigenvalue_a = 0.5f * (variance.x() + variance.y() +
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance)); sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance));
double eigenvalue_b = 0.5 * (variance.x() + variance.y() - float eigenvalue_b = 0.5f * (variance.x() + variance.y() -
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance)); sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance));
Vec2d eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0}; Vec2f eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0f};
Vec2d eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0}; Vec2f eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0f};
#if 0 #if 0
std::cout << "eigenvalue_a: " << eigenvalue_a << std::endl; std::cout << "eigenvalue_a: " << eigenvalue_a << std::endl;

View File

@ -9,8 +9,15 @@
namespace Slic3r { namespace Slic3r {
// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance
// none of the values is divided/normalized by area.
// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous)
// f(x,y) = x^2 for second moment of area
// and f(x,y) = x*y for second moment of area covariance
std::tuple<float, Vec2f, Vec2f, float> compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c);
// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first // returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first
std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, const double unscaled_resolution); std::tuple<Vec2f, Vec2f> compute_principal_components(const Polygons &polys);
} }

View File

@ -1201,7 +1201,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
} }
// Invalidate just the supports step. // Invalidate just the supports step.
for (const PrintObjectStatus &print_object_status : print_objects_range) { for (const PrintObjectStatus &print_object_status : print_objects_range) {
update_apply_status(print_object_status.print_object->invalidate_step(posSupportSpotsSearch));
update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial)); update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial));
} }
if (supports_differ) { if (supports_differ) {

View File

@ -181,7 +181,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAMaterialSpeed);
static inline const t_config_enum_values s_keys_map_SLASupportTreeType = { static inline const t_config_enum_values s_keys_map_SLASupportTreeType = {
{"default", int(sla::SupportTreeType::Default)}, {"default", int(sla::SupportTreeType::Default)},
{"branching", int(sla::SupportTreeType::Branching)} {"branching", int(sla::SupportTreeType::Branching)},
//TODO: {"organic", int(sla::SupportTreeType::Organic)}
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLASupportTreeType); CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLASupportTreeType);
@ -534,7 +535,7 @@ void PrintConfigDef::init_fff_params()
def->category = L("Speed"); def->category = L("Speed");
def->tooltip = L("This setting enables dynamic speed control on overhangs."); def->tooltip = L("This setting enables dynamic speed control on overhangs.");
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("overhang_overlap_levels", coPercents); def = this->add("overhang_overlap_levels", coPercents);
def->full_label = L("Overhang overlap levels"); def->full_label = L("Overhang overlap levels");
@ -841,7 +842,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("extra_perimeters_on_overhangs", coBool); def = this->add("extra_perimeters_on_overhangs", coBool);
def->label = L("Extra perimeters on overhangs (Experimental)"); def->label = L("Extra perimeters on overhangs (Experimental)");
def->category = L("Layers and Perimeters"); def->category = L("Layers and Perimeters");
def->tooltip = L("Create additional perimeter paths over steep overhangs and areas where bridges cannot be anchored."); def->tooltip = L("Detect overhang areas where bridges cannot be anchored, and fill them with "
"extra perimeter paths. These paths are anchored to the nearby non-overhang area when possible.");
def->mode = comExpert; def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
@ -3271,6 +3273,219 @@ void PrintConfigDef::init_extruder_option_keys()
assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end()));
} }
void PrintConfigDef::init_sla_support_params(const std::string &prefix)
{
ConfigOptionDef* def;
constexpr const char * pretext_unavailable = L("Unavailable for this method.\n");
std::string pretext;
def = this->add(prefix + "support_head_front_diameter", coFloat);
def->label = L("Pinhead front diameter");
def->category = L("Supports");
def->tooltip = L("Diameter of the pointing side of the head");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.4));
def = this->add(prefix + "support_head_penetration", coFloat);
def->label = L("Head penetration");
def->category = L("Supports");
def->tooltip = L("How much the pinhead has to penetrate the model surface");
def->sidetext = L("mm");
def->mode = comAdvanced;
def->min = 0;
def->set_default_value(new ConfigOptionFloat(0.2));
def = this->add(prefix + "support_head_width", coFloat);
def->label = L("Pinhead width");
def->category = L("Supports");
def->tooltip = L("Width from the back sphere center to the front sphere center");
def->sidetext = L("mm");
def->min = 0;
def->max = 20;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add(prefix + "support_pillar_diameter", coFloat);
def->label = L("Pillar diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the support pillars");
def->sidetext = L("mm");
def->min = 0;
def->max = 15;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add(prefix + "support_small_pillar_diameter_percent", coPercent);
def->label = L("Small pillar diameter percent");
def->category = L("Supports");
def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
"which are used in problematic areas where a normal pilla cannot fit.");
def->sidetext = L("%");
def->min = 1;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionPercent(50));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_bridges_on_pillar", coInt);
def->label = L("Max bridges on a pillar");
def->tooltip = pretext + L(
"Maximum number of bridges that can be placed on a pillar. Bridges "
"hold support point pinheads and connect to pillars as small branches.");
def->min = 0;
def->max = 50;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInt(prefix == "branching" ? 2 : 3));
pretext = "";
if (prefix.empty())
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_weight_on_model", coFloat);
def->label = L("Max weight on model");
def->category = L("Supports");
def->tooltip = pretext + L(
"Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all "
"branches emanating from the endpoint.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(10.));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_pillar_connection_mode", coEnum);
def->label = L("Pillar connection mode");
def->tooltip = pretext + L("Controls the bridge type between two neighboring pillars."
" Can be zig-zag, cross (double zig-zag) or dynamic which"
" will automatically switch between the first two depending"
" on the distance of the two pillars.");
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
def->enum_values = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
def->enum_labels = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
def->enum_labels[0] = L("Zig-Zag");
def->enum_labels[1] = L("Cross");
def->enum_labels[2] = L("Dynamic");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic));
def = this->add(prefix + "support_buildplate_only", coBool);
def->label = L("Support on build plate only");
def->category = L("Supports");
def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print.");
def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false));
def = this->add(prefix + "support_pillar_widening_factor", coFloat);
def->label = L("Pillar widening factor");
def->category = L("Supports");
pretext = "";
if (prefix.empty())
pretext = pretext_unavailable;
def->tooltip = pretext +
L("Merging bridges or pillars into another pillars can "
"increase the radius. Zero means no increase, one means "
"full increase. The exact amount of increase is unspecified and can "
"change in the future.");
def->min = 0;
def->max = 1;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.5));
def = this->add(prefix + "support_base_diameter", coFloat);
def->label = L("Support base diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the pillar base");
def->sidetext = L("mm");
def->min = 0;
def->max = 30;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(4.0));
def = this->add(prefix + "support_base_height", coFloat);
def->label = L("Support base height");
def->category = L("Supports");
def->tooltip = L("The height of the pillar base cone");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add(prefix + "support_base_safety_distance", coFloat);
def->label = L("Support base safety distance");
def->category = L("Supports");
def->tooltip = L(
"The minimum distance of the pillar base from the model in mm. "
"Makes sense in zero elevation mode where a gap according "
"to this parameter is inserted between the model and the pad.");
def->sidetext = L("mm");
def->min = 0;
def->max = 10;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(1));
def = this->add(prefix + "support_critical_angle", coFloat);
def->label = L("Critical angle");
def->category = L("Supports");
def->tooltip = L("The default angle for connecting support sticks and junctions.");
def->sidetext = L("°");
def->min = 0;
def->max = 90;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(45));
def = this->add(prefix + "support_max_bridge_length", coFloat);
def->label = L("Max bridge length");
def->category = L("Supports");
def->tooltip = L("The max length of a bridge");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
double default_val = 15.0;
if (prefix == "branching")
default_val = 5.0;
def->set_default_value(new ConfigOptionFloat(default_val));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_pillar_link_distance", coFloat);
def->label = L("Max pillar linking distance");
def->category = L("Supports");
def->tooltip = pretext + L("The max distance of two pillars to get linked with each other."
" A zero value will prohibit pillar cascading.");
def->sidetext = L("mm");
def->min = 0; // 0 means no linking
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(10.0));
def = this->add(prefix + "support_object_elevation", coFloat);
def->label = L("Object elevation");
def->category = L("Supports");
def->tooltip = L("How much the supports should lift up the supported object. "
"If \"Pad around object\" is enabled, this value is ignored.");
def->sidetext = L("mm");
def->min = 0;
def->max = 150; // This is the max height of print on SL1
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(5.0));
}
void PrintConfigDef::init_sla_params() void PrintConfigDef::init_sla_params()
{ {
ConfigOptionDef* def; ConfigOptionDef* def;
@ -3613,90 +3828,12 @@ void PrintConfigDef::init_sla_params()
def->enum_labels = ConfigOptionEnum<sla::SupportTreeType>::get_enum_names(); def->enum_labels = ConfigOptionEnum<sla::SupportTreeType>::get_enum_names();
def->enum_labels[0] = L("Default"); def->enum_labels[0] = L("Default");
def->enum_labels[1] = L("Branching"); def->enum_labels[1] = L("Branching");
def->mode = comAdvanced; // TODO: def->enum_labels[2] = L("Organic");
def->mode = comSimple;
def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default));
def = this->add("support_head_front_diameter", coFloat); init_sla_support_params("");
def->label = L("Pinhead front diameter"); init_sla_support_params("branching");
def->category = L("Supports");
def->tooltip = L("Diameter of the pointing side of the head");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.4));
def = this->add("support_head_penetration", coFloat);
def->label = L("Head penetration");
def->category = L("Supports");
def->tooltip = L("How much the pinhead has to penetrate the model surface");
def->sidetext = L("mm");
def->mode = comAdvanced;
def->min = 0;
def->set_default_value(new ConfigOptionFloat(0.2));
def = this->add("support_head_width", coFloat);
def->label = L("Pinhead width");
def->category = L("Supports");
def->tooltip = L("Width from the back sphere center to the front sphere center");
def->sidetext = L("mm");
def->min = 0;
def->max = 20;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_pillar_diameter", coFloat);
def->label = L("Pillar diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the support pillars");
def->sidetext = L("mm");
def->min = 0;
def->max = 15;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_small_pillar_diameter_percent", coPercent);
def->label = L("Small pillar diameter percent");
def->category = L("Supports");
def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
"which are used in problematic areas where a normal pilla cannot fit.");
def->sidetext = L("%");
def->min = 1;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionPercent(50));
def = this->add("support_max_bridges_on_pillar", coInt);
def->label = L("Max bridges on a pillar");
def->tooltip = L(
"Maximum number of bridges that can be placed on a pillar. Bridges "
"hold support point pinheads and connect to pillars as small branches.");
def->min = 0;
def->max = 50;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInt(3));
def = this->add("support_pillar_connection_mode", coEnum);
def->label = L("Pillar connection mode");
def->tooltip = L("Controls the bridge type between two neighboring pillars."
" Can be zig-zag, cross (double zig-zag) or dynamic which"
" will automatically switch between the first two depending"
" on the distance of the two pillars.");
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
def->enum_values = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
def->enum_labels = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
def->enum_labels[0] = L("Zig-Zag");
def->enum_labels[1] = L("Cross");
def->enum_labels[2] = L("Dynamic");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic));
def = this->add("support_buildplate_only", coBool);
def->label = L("Support on build plate only");
def->category = L("Supports");
def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print.");
def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("support_enforcers_only", coBool); def = this->add("support_enforcers_only", coBool);
def->label = L("Support only in enforced regions"); def->label = L("Support only in enforced regions");
@ -3705,93 +3842,6 @@ void PrintConfigDef::init_sla_params()
def->mode = comSimple; def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("support_pillar_widening_factor", coFloat);
def->label = L("Pillar widening factor");
def->category = L("Supports");
def->tooltip = L(
"Merging bridges or pillars into another pillars can "
"increase the radius. Zero means no increase, one means "
"full increase. The exact amount of increase is unspecified and can "
"change in the future. What is garanteed is that thickness will not "
"exceed \"support_base_diameter\"");
def->min = 0;
def->max = 1;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.15));
def = this->add("support_base_diameter", coFloat);
def->label = L("Support base diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the pillar base");
def->sidetext = L("mm");
def->min = 0;
def->max = 30;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(4.0));
def = this->add("support_base_height", coFloat);
def->label = L("Support base height");
def->category = L("Supports");
def->tooltip = L("The height of the pillar base cone");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_base_safety_distance", coFloat);
def->label = L("Support base safety distance");
def->category = L("Supports");
def->tooltip = L(
"The minimum distance of the pillar base from the model in mm. "
"Makes sense in zero elevation mode where a gap according "
"to this parameter is inserted between the model and the pad.");
def->sidetext = L("mm");
def->min = 0;
def->max = 10;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(1));
def = this->add("support_critical_angle", coFloat);
def->label = L("Critical angle");
def->category = L("Supports");
def->tooltip = L("The default angle for connecting support sticks and junctions.");
def->sidetext = L("°");
def->min = 0;
def->max = 90;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(45));
def = this->add("support_max_bridge_length", coFloat);
def->label = L("Max bridge length");
def->category = L("Supports");
def->tooltip = L("The max length of a bridge");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(15.0));
def = this->add("support_max_pillar_link_distance", coFloat);
def->label = L("Max pillar linking distance");
def->category = L("Supports");
def->tooltip = L("The max distance of two pillars to get linked with each other."
" A zero value will prohibit pillar cascading.");
def->sidetext = L("mm");
def->min = 0; // 0 means no linking
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(10.0));
def = this->add("support_object_elevation", coFloat);
def->label = L("Object elevation");
def->category = L("Supports");
def->tooltip = L("How much the supports should lift up the supported object. "
"If \"Pad around object\" is enabled, this value is ignored.");
def->sidetext = L("mm");
def->min = 0;
def->max = 150; // This is the max height of print on SL1
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(5.0));
def = this->add("support_points_density_relative", coInt); def = this->add("support_points_density_relative", coInt);
def->label = L("Support points density"); def->label = L("Support points density");
def->category = L("Supports"); def->category = L("Supports");
@ -4759,6 +4809,23 @@ Points get_bed_shape(const PrintConfig &cfg)
Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.bed_shape.values); } Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.bed_shape.values); }
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config)
{
const auto *suptreetype = config.option<ConfigOptionEnum<sla::SupportTreeType>>("support_tree_type");
std::string slatree = "";
if (suptreetype) {
auto ttype = static_cast<sla::SupportTreeType>(suptreetype->getInt());
switch (ttype) {
case sla::SupportTreeType::Branching: slatree = "branching"; break;
case sla::SupportTreeType::Organic: slatree = "organic"; break;
default:
;
}
}
return slatree;
}
} // namespace Slic3r } // namespace Slic3r
#include <cereal/types/polymorphic.hpp> #include <cereal/types/polymorphic.hpp>

View File

@ -185,6 +185,7 @@ private:
void init_fff_params(); void init_fff_params();
void init_extruder_option_keys(); void init_extruder_option_keys();
void init_sla_params(); void init_sla_params();
void init_sla_support_params(const std::string &method_prefix);
std::vector<std::string> m_extruder_option_keys; std::vector<std::string> m_extruder_option_keys;
std::vector<std::string> m_extruder_retract_keys; std::vector<std::string> m_extruder_retract_keys;
@ -861,6 +862,8 @@ PRINT_CONFIG_CLASS_DEFINE(
// Generate only ground facing supports // Generate only ground facing supports
((ConfigOptionBool, support_buildplate_only)) ((ConfigOptionBool, support_buildplate_only))
((ConfigOptionFloat, support_max_weight_on_model))
// Generate only ground facing supports // Generate only ground facing supports
((ConfigOptionBool, support_enforcers_only)) ((ConfigOptionBool, support_enforcers_only))
@ -892,6 +895,62 @@ PRINT_CONFIG_CLASS_DEFINE(
// and the model object's bounding box bottom. Units in mm. // and the model object's bounding box bottom. Units in mm.
((ConfigOptionFloat, support_object_elevation))/*= 5.0*/ ((ConfigOptionFloat, support_object_elevation))/*= 5.0*/
// Branching tree
// Diameter in mm of the pointing side of the head.
((ConfigOptionFloat, branchingsupport_head_front_diameter))/*= 0.2*/
// How much the pinhead has to penetrate the model surface
((ConfigOptionFloat, branchingsupport_head_penetration))/*= 0.2*/
// Width in mm from the back sphere center to the front sphere center.
((ConfigOptionFloat, branchingsupport_head_width))/*= 1.0*/
// Radius in mm of the support pillars.
((ConfigOptionFloat, branchingsupport_pillar_diameter))/*= 0.8*/
// The percentage of smaller pillars compared to the normal pillar diameter
// which are used in problematic areas where a normal pilla cannot fit.
((ConfigOptionPercent, branchingsupport_small_pillar_diameter_percent))
// How much bridge (supporting another pinhead) can be placed on a pillar.
((ConfigOptionInt, branchingsupport_max_bridges_on_pillar))
// How the pillars are bridged together
((ConfigOptionEnum<SLAPillarConnectionMode>, branchingsupport_pillar_connection_mode))
// Generate only ground facing supports
((ConfigOptionBool, branchingsupport_buildplate_only))
((ConfigOptionFloat, branchingsupport_max_weight_on_model))
((ConfigOptionFloat, branchingsupport_pillar_widening_factor))
// Radius in mm of the pillar base.
((ConfigOptionFloat, branchingsupport_base_diameter))/*= 2.0*/
// The height of the pillar base cone in mm.
((ConfigOptionFloat, branchingsupport_base_height))/*= 1.0*/
// The minimum distance of the pillar base from the model in mm.
((ConfigOptionFloat, branchingsupport_base_safety_distance)) /*= 1.0*/
// The default angle for connecting support sticks and junctions.
((ConfigOptionFloat, branchingsupport_critical_angle))/*= 45*/
// The max length of a bridge in mm
((ConfigOptionFloat, branchingsupport_max_bridge_length))/*= 15.0*/
// The max distance of two pillars to get cross linked.
((ConfigOptionFloat, branchingsupport_max_pillar_link_distance))
// The elevation in Z direction upwards. This is the space between the pad
// and the model object's bounding box bottom. Units in mm.
((ConfigOptionFloat, branchingsupport_object_elevation))/*= 5.0*/
/////// Following options influence automatic support points placement: /////// Following options influence automatic support points placement:
((ConfigOptionInt, support_points_density_relative)) ((ConfigOptionInt, support_points_density_relative))
((ConfigOptionFloat, support_points_minimal_distance)) ((ConfigOptionFloat, support_points_minimal_distance))
@ -1111,6 +1170,8 @@ Points get_bed_shape(const DynamicPrintConfig &cfg);
Points get_bed_shape(const PrintConfig &cfg); Points get_bed_shape(const PrintConfig &cfg);
Points get_bed_shape(const SLAPrinterConfig &cfg); Points get_bed_shape(const SLAPrinterConfig &cfg);
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config);
// ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. // ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp.
// Each change of ModelConfig is tracked by assigning a new timestamp from a global counter. // Each change of ModelConfig is tracked by assigning a new timestamp from a global counter.
// The counter is used for faster synchronization of the background slicing thread // The counter is used for faster synchronization of the background slicing thread

View File

@ -789,10 +789,10 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
// propagate to dependent steps // propagate to dependent steps
if (step == posPerimeters) { if (step == posPerimeters) {
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posEstimateCurledExtrusions }); invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions });
invalidated |= m_print->invalidate_steps({ psSkirtBrim }); invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posPrepareInfill) { } else if (step == posPrepareInfill) {
invalidated |= this->invalidate_steps({ posInfill, posIroning }); invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch });
} else if (step == posInfill) { } else if (step == posInfill) {
invalidated |= this->invalidate_steps({ posIroning, posSupportSpotsSearch }); invalidated |= this->invalidate_steps({ posIroning, posSupportSpotsSearch });
invalidated |= m_print->invalidate_steps({ psSkirtBrim }); invalidated |= m_print->invalidate_steps({ psSkirtBrim });

View File

@ -13,20 +13,28 @@
namespace Slic3r { namespace sla { namespace Slic3r { namespace sla {
inline constexpr const auto &beam_ex_policy = ex_tbb;
class BranchingTreeBuilder: public branchingtree::Builder { class BranchingTreeBuilder: public branchingtree::Builder {
SupportTreeBuilder &m_builder; SupportTreeBuilder &m_builder;
const SupportableMesh &m_sm; const SupportableMesh &m_sm;
const branchingtree::PointCloud &m_cloud; const branchingtree::PointCloud &m_cloud;
std::vector<branchingtree::Node> m_pillars; // to put an index over them
// cache succesfull ground connections
mutable std::map<int, GroundConnection> m_gnd_connections;
mutable execution::SpinningMutex<ExecutionTBB> m_gnd_connections_mtx;
// Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable
// widening behaviour // widening behaviour
static constexpr double WIDENING_SCALE = 0.02; static constexpr double WIDENING_SCALE = 0.05;
double get_radius(const branchingtree::Node &j) double get_radius(const branchingtree::Node &j) const
{ {
double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight; double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight;
return std::min(m_sm.cfg.base_radius_mm, double(j.Rmin) + w); return double(j.Rmin) + w;
} }
std::vector<size_t> m_unroutable_pinheads; std::vector<size_t> m_unroutable_pinheads;
@ -78,6 +86,44 @@ class BranchingTreeBuilder: public branchingtree::Builder {
}); });
} }
void discard_subtree_rescure(size_t root)
{
// Discard all the support points connecting to this branch.
// As a last resort, try to route child nodes to ground and stop
// traversing if any child branch succeeds.
traverse(m_cloud, root, [this](const branchingtree::Node &node) {
branchingtree::TraverseReturnT ret{true, true};
int suppid_parent = m_cloud.get_leaf_id(node.id);
int suppid_left = branchingtree::Node::ID_NONE;
int suppid_right = branchingtree::Node::ID_NONE;
double glvl = ground_level(m_sm);
branchingtree::Node dst = node;
dst.pos.z() = glvl;
dst.weight += node.pos.z() - glvl;
if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst))
ret.to_left = false;
else
suppid_left = m_cloud.get_leaf_id(node.left);
if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), dst))
ret.to_right = false;
else
suppid_right = m_cloud.get_leaf_id(node.right);
if (suppid_parent >= 0)
m_unroutable_pinheads.emplace_back(suppid_parent);
if (suppid_left >= 0)
m_unroutable_pinheads.emplace_back(suppid_left);
if (suppid_right >= 0)
m_unroutable_pinheads.emplace_back(suppid_right);
return ret;
});
}
public: public:
BranchingTreeBuilder(SupportTreeBuilder &builder, BranchingTreeBuilder(SupportTreeBuilder &builder,
const SupportableMesh &sm, const SupportableMesh &sm,
@ -98,13 +144,24 @@ public:
bool add_mesh_bridge(const branchingtree::Node &from, bool add_mesh_bridge(const branchingtree::Node &from,
const branchingtree::Node &to) override; const branchingtree::Node &to) override;
std::optional<Vec3f> suggest_avoidance(const branchingtree::Node &from,
float max_bridge_len) const override;
void report_unroutable(const branchingtree::Node &j) override void report_unroutable(const branchingtree::Node &j) override
{ {
BOOST_LOG_TRIVIAL(error) << "Cannot route junction at " << j.pos.x() double glvl = ground_level(m_sm);
<< " " << j.pos.y() << " " << j.pos.z(); branchingtree::Node dst = j;
dst.pos.z() = glvl;
dst.weight += j.pos.z() - glvl;
if (add_ground_bridge(j, dst))
return;
BOOST_LOG_TRIVIAL(warning) << "Cannot route junction at " << j.pos.x()
<< " " << j.pos.y() << " " << j.pos.z();
// Discard all the support points connecting to this branch. // Discard all the support points connecting to this branch.
discard_subtree(j.id); discard_subtree_rescure(j.id);
// discard_subtree(j.id);
} }
const std::vector<size_t>& unroutable_pinheads() const const std::vector<size_t>& unroutable_pinheads() const
@ -113,15 +170,28 @@ public:
} }
bool is_valid() const override { return !m_builder.ctl().stopcondition(); } bool is_valid() const override { return !m_builder.ctl().stopcondition(); }
const std::vector<branchingtree::Node> & pillars() const { return m_pillars; }
const GroundConnection *ground_conn(size_t pillar) const
{
const GroundConnection *ret = nullptr;
auto it = m_gnd_connections.find(m_pillars[pillar].id);
if (it != m_gnd_connections.end())
ret = &it->second;
return ret;
}
}; };
bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from,
const branchingtree::Node &to) const branchingtree::Node &to)
{ {
Vec3d fromd = from.pos.cast<double>(), tod = to.pos.cast<double>(); Vec3d fromd = from.pos.cast<double>(), tod = to.pos.cast<double>();
double fromR = get_radius(from), toR = get_radius(to); double fromR = get_radius(from), toR = get_radius(to);
Beam beam{Ball{fromd, fromR}, Ball{tod, toR}}; Beam beam{Ball{fromd, fromR}, Ball{tod, toR}};
auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam, auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam,
m_sm.cfg.safety_distance_mm); m_sm.cfg.safety_distance_mm);
bool ret = hit.distance() > (tod - fromd).norm(); bool ret = hit.distance() > (tod - fromd).norm();
@ -144,8 +214,8 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node,
Beam beam2{Ball{from2d, closestR}, Ball{tod, mergeR}}; Beam beam2{Ball{from2d, closestR}, Ball{tod, mergeR}};
auto sd = m_sm.cfg.safety_distance_mm ; auto sd = m_sm.cfg.safety_distance_mm ;
auto hit1 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam1, sd); auto hit1 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam1, sd);
auto hit2 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam2, sd); auto hit2 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam2, sd);
bool ret = hit1.distance() > (tod - from1d).norm() && bool ret = hit1.distance() > (tod - from1d).norm() &&
hit2.distance() > (tod - from2d).norm(); hit2.distance() > (tod - from2d).norm();
@ -156,12 +226,31 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node,
bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
const branchingtree::Node &to) const branchingtree::Node &to)
{ {
bool ret = search_ground_route(ex_tbb, m_builder, m_sm, bool ret = false;
sla::Junction{from.pos.cast<double>(),
get_radius(from)},
get_radius(to)).first;
if (ret) { namespace bgi = boost::geometry::index;
auto it = m_gnd_connections.find(from.id);
const GroundConnection *connptr = nullptr;
if (it == m_gnd_connections.end()) {
sla::Junction j{from.pos.cast<double>(), get_radius(from)};
Vec3d init_dir = (to.pos - from.pos).cast<double>().normalized();
auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j,
get_radius(to), init_dir);
// Remember that this node was tested if can go to ground, don't
// test it with any other destination ground point because
// it is unlikely that search_ground_route would find a better solution
connptr = &(m_gnd_connections[from.id] = conn);
} else {
connptr = &(it->second);
}
if (connptr && *connptr) {
m_pillars.emplace_back(from);
ret = true;
build_subtree(from.id); build_subtree(from.id);
} }
@ -171,17 +260,20 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
const branchingtree::Node &to) const branchingtree::Node &to)
{ {
if (from.weight > m_sm.cfg.max_weight_on_model_support)
return false;
sla::Junction fromj = {from.pos.cast<double>(), get_radius(from)}; sla::Junction fromj = {from.pos.cast<double>(), get_radius(from)};
auto anchor = m_sm.cfg.ground_facing_only ? auto anchor = m_sm.cfg.ground_facing_only ?
std::optional<Anchor>{} : // If no mesh connections are allowed std::optional<Anchor>{} : // If no mesh connections are allowed
calculate_anchor_placement(ex_tbb, m_sm, fromj, calculate_anchor_placement(beam_ex_policy , m_sm, fromj,
to.pos.cast<double>()); to.pos.cast<double>());
if (anchor) { if (anchor) {
sla::Junction toj = {anchor->junction_point(), anchor->r_back_mm}; sla::Junction toj = {anchor->junction_point(), anchor->r_back_mm};
auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh,
Beam{{fromj.pos, fromj.r}, {toj.pos, toj.r}}, 0.); Beam{{fromj.pos, fromj.r}, {toj.pos, toj.r}}, 0.);
if (hit.distance() > distance(fromj.pos, toj.pos)) { if (hit.distance() > distance(fromj.pos, toj.pos)) {
@ -197,6 +289,70 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
return bool(anchor); return bool(anchor);
} }
static std::optional<Vec3f> get_avoidance(const GroundConnection &conn,
float maxdist)
{
std::optional<Vec3f> ret;
if (conn) {
if (conn.path.size() > 1) {
ret = conn.path[1].pos.cast<float>();
} else {
Vec3f pbeg = conn.path[0].pos.cast<float>();
Vec3f pend = conn.pillar_base->pos.cast<float>();
pbeg.z() = std::max(pbeg.z() - maxdist, pend.z());
ret = pbeg;
}
}
return ret;
}
std::optional<Vec3f> BranchingTreeBuilder::suggest_avoidance(
const branchingtree::Node &from, float max_bridge_len) const
{
std::optional<Vec3f> ret;
double glvl = ground_level(m_sm);
branchingtree::Node dst = from;
dst.pos.z() = glvl;
dst.weight += from.pos.z() - glvl;
sla::Junction j{from.pos.cast<double>(), get_radius(from)};
auto found_it = m_gnd_connections.end();
{
std::lock_guard lk{m_gnd_connections_mtx};
found_it = m_gnd_connections.find(from.id);
}
if (found_it != m_gnd_connections.end()) {
ret = get_avoidance(found_it->second, max_bridge_len);
} else {
auto conn = deepsearch_ground_connection(
beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN);
{
std::lock_guard lk{m_gnd_connections_mtx};
m_gnd_connections[from.id] = conn;
}
ret = get_avoidance(conn, max_bridge_len);
}
return ret;
}
inline void build_pillars(SupportTreeBuilder &builder,
BranchingTreeBuilder &vbuilder,
const SupportableMesh &sm)
{
for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) {
auto * conn = vbuilder.ground_conn(pill_id);
if (conn)
build_ground_connection(builder, sm, *conn);
}
}
void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &sm) void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &sm)
{ {
auto coordfn = [&sm](size_t id, size_t dim) { return sm.pts[id].pos(dim); }; auto coordfn = [&sm](size_t id, size_t dim) { return sm.pts[id].pos(dim); };
@ -221,7 +377,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s
for (auto &h : heads) for (auto &h : heads)
if (h && h->is_valid()) { if (h && h->is_valid()) {
leafs.emplace_back(h->junction_point().cast<float>(), h->r_back_mm); leafs.emplace_back(h->junction_point().cast<float>(), h->r_back_mm);
h->id = leafs.size() - 1; h->id = long(leafs.size() - 1);
builder.add_head(h->id, *h); builder.add_head(h->id, *h);
} }
@ -240,15 +396,29 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s
props.sampling_radius()); props.sampling_radius());
auto bedpts = branchingtree::sample_bed(props.bed_shape(), auto bedpts = branchingtree::sample_bed(props.bed_shape(),
props.ground_level(), float(props.ground_level()),
props.sampling_radius()); props.sampling_radius());
for (auto &bp : bedpts)
bp.Rmin = sm.cfg.head_back_radius_mm;
branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts), branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts),
std::move(leafs), props}; std::move(leafs), props};
BranchingTreeBuilder vbuilder{builder, sm, nodes}; BranchingTreeBuilder vbuilder{builder, sm, nodes};
execution::for_each(ex_tbb,
size_t(0),
nodes.get_leafs().size(),
[&nodes, &vbuilder](size_t leaf_idx) {
vbuilder.suggest_avoidance(nodes.get_leafs()[leaf_idx],
nodes.properties().max_branch_length());
});
branchingtree::build_tree(nodes, vbuilder); branchingtree::build_tree(nodes, vbuilder);
build_pillars(builder, vbuilder, sm);
for (size_t id : vbuilder.unroutable_pinheads()) for (size_t id : vbuilder.unroutable_pinheads())
builder.head(id).invalidate(); builder.head(id).invalidate();

View File

@ -340,14 +340,17 @@ bool DefaultSupportTree::connect_to_nearpillar(const Head &head,
return true; return true;
} }
bool DefaultSupportTree::create_ground_pillar(const Vec3d &hjp, bool DefaultSupportTree::create_ground_pillar(const Junction &hjp,
const Vec3d &sourcedir, const Vec3d &sourcedir,
double radius,
long head_id) long head_id)
{ {
auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy,
m_builder, m_sm, hjp, m_builder,
sourcedir, radius, radius, m_sm,
hjp.pos,
sourcedir,
hjp.r,
hjp.r,
head_id); head_id);
if (pillar_id >= 0) // Save the pillar endpoint in the spatial index if (pillar_id >= 0) // Save the pillar endpoint in the spatial index
@ -362,20 +365,19 @@ void DefaultSupportTree::add_pinheads()
// The minimum distance for two support points to remain valid. // The minimum distance for two support points to remain valid.
const double /*constexpr*/ D_SP = 0.1; const double /*constexpr*/ D_SP = 0.1;
// Get the points that are too close to each other and keep only the // Get the points that are too close to each other and keep only the
// first one // first one
auto aliases = cluster(m_points, D_SP, 2); auto aliases = cluster(m_points, D_SP, 2);
PtIndices filtered_indices; PtIndices filtered_indices;
filtered_indices.reserve(aliases.size()); filtered_indices.reserve(aliases.size());
m_iheads.reserve(aliases.size()); m_iheads.reserve(aliases.size());
m_iheadless.reserve(aliases.size());
for(auto& a : aliases) { for(auto& a : aliases) {
// Here we keep only the front point of the cluster. // Here we keep only the front point of the cluster.
filtered_indices.emplace_back(a.front()); filtered_indices.emplace_back(a.front());
} }
// calculate the normals to the triangles for filtered points // calculate the normals to the triangles for filtered points
auto nmls = normals(suptree_ex_policy, m_points, m_sm.emesh, auto nmls = normals(suptree_ex_policy, m_points, m_sm.emesh,
m_sm.cfg.head_front_radius_mm, m_thr, m_sm.cfg.head_front_radius_mm, m_thr,
filtered_indices); filtered_indices);
@ -404,22 +406,22 @@ void DefaultSupportTree::add_pinheads()
Vec3d n = nmls.row(Eigen::Index(i)); Vec3d n = nmls.row(Eigen::Index(i));
// for all normals we generate the spherical coordinates and // for all normals we generate the spherical coordinates and
// saturate the polar angle to 45 degrees from the bottom then // saturate the polar angle to 45 degrees from the bottom then
// convert back to standard coordinates to get the new normal. // convert back to standard coordinates to get the new normal.
// Then we just create a quaternion from the two normals // Then we just create a quaternion from the two normals
// (Quaternion::FromTwoVectors) and apply the rotation to the // (Quaternion::FromTwoVectors) and apply the rotation to the
// arrow head. // arrow head.
auto [polar, azimuth] = dir_to_spheric(n); auto [polar, azimuth] = dir_to_spheric(n);
// skip if the tilt is not sane // skip if the tilt is not sane
if (polar < PI - m_sm.cfg.normal_cutoff_angle) return; if (polar < PI - m_sm.cfg.normal_cutoff_angle) return;
// We saturate the polar angle to 3pi/4 // We saturate the polar angle to 3pi/4
polar = std::max(polar, PI - m_sm.cfg.bridge_slope); polar = std::max(polar, PI - m_sm.cfg.bridge_slope);
// save the head (pinpoint) position // save the head (pinpoint) position
Vec3d hp = m_points.row(fidx); Vec3d hp = m_points.row(fidx);
double lmin = m_sm.cfg.head_width_mm, lmax = lmin; double lmin = m_sm.cfg.head_width_mm, lmax = lmin;
@ -428,18 +430,17 @@ void DefaultSupportTree::add_pinheads()
lmin = 0., lmax = m_sm.cfg.head_penetration_mm; lmin = 0., lmax = m_sm.cfg.head_penetration_mm;
} }
// The distance needed for a pinhead to not collide with model. // The distance needed for a pinhead to not collide with model.
double w = lmin + 2 * back_r + 2 * m_sm.cfg.head_front_radius_mm - double w = lmin + 2 * back_r + 2 * m_sm.cfg.head_front_radius_mm -
m_sm.cfg.head_penetration_mm; m_sm.cfg.head_penetration_mm;
double pin_r = double(m_sm.pts[fidx].head_front_radius); double pin_r = double(m_sm.pts[fidx].head_front_radius);
// Reassemble the now corrected normal // Reassemble the now corrected normal
auto nn = spheric_to_dir(polar, azimuth).normalized(); auto nn = spheric_to_dir(polar, azimuth).normalized();
// check available distance // check available distance
AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, back_r, w);
back_r, w);
if (t.distance() < w) { if (t.distance() < w) {
// Let's try to optimize this angle, there might be a // Let's try to optimize this angle, there might be a
@ -509,10 +510,10 @@ void DefaultSupportTree::classify()
ground_head_indices.reserve(m_iheads.size()); ground_head_indices.reserve(m_iheads.size());
m_iheads_onmodel.reserve(m_iheads.size()); m_iheads_onmodel.reserve(m_iheads.size());
// First we decide which heads reach the ground and can be full // First we decide which heads reach the ground and can be full
// pillars and which shall be connected to the model surface (or // pillars and which shall be connected to the model surface (or
// search a suitable path around the surface that leads to the // search a suitable path around the surface that leads to the
// ground -- TODO) // ground -- TODO)
for(unsigned i : m_iheads) { for(unsigned i : m_iheads) {
m_thr(); m_thr();
@ -530,10 +531,10 @@ void DefaultSupportTree::classify()
m_head_to_ground_scans[i] = hit; m_head_to_ground_scans[i] = hit;
} }
// We want to search for clusters of points that are far enough // We want to search for clusters of points that are far enough
// from each other in the XY plane to not cross their pillar bases // from each other in the XY plane to not cross their pillar bases
// These clusters of support points will join in one pillar, // These clusters of support points will join in one pillar,
// possibly in their centroid support point. // possibly in their centroid support point.
auto pointfn = [this](unsigned i) { auto pointfn = [this](unsigned i) {
return m_builder.head(i).junction_point(); return m_builder.head(i).junction_point();
@ -559,13 +560,13 @@ void DefaultSupportTree::routing_to_ground()
for (auto &cl : m_pillar_clusters) { for (auto &cl : m_pillar_clusters) {
m_thr(); m_thr();
// place all the centroid head positions into the index. We // place all the centroid head positions into the index. We
// will query for alternative pillar positions. If a sidehead // will query for alternative pillar positions. If a sidehead
// cannot connect to the cluster centroid, we have to search // cannot connect to the cluster centroid, we have to search
// for another head with a full pillar. Also when there are two // for another head with a full pillar. Also when there are two
// elements in the cluster, the centroid is arbitrary and the // elements in the cluster, the centroid is arbitrary and the
// sidehead is allowed to connect to a nearby pillar to // sidehead is allowed to connect to a nearby pillar to
// increase structural stability. // increase structural stability.
if (cl.empty()) continue; if (cl.empty()) continue;
@ -587,7 +588,7 @@ void DefaultSupportTree::routing_to_ground()
Head &h = m_builder.head(hid); Head &h = m_builder.head(hid);
if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { if (!create_ground_pillar(h.junction(), h.dir, h.id)) {
BOOST_LOG_TRIVIAL(warning) BOOST_LOG_TRIVIAL(warning)
<< "Pillar cannot be created for support point id: " << hid; << "Pillar cannot be created for support point id: " << hid;
m_iheads_onmodel.emplace_back(h.id); m_iheads_onmodel.emplace_back(h.id);
@ -595,11 +596,11 @@ void DefaultSupportTree::routing_to_ground()
} }
} }
// now we will go through the clusters ones again and connect the // now we will go through the clusters ones again and connect the
// sidepoints with the cluster centroid (which is a ground pillar) // sidepoints with the cluster centroid (which is a ground pillar)
// or a nearby pillar if the centroid is unreachable. // or a nearby pillar if the centroid is unreachable.
size_t ci = 0; size_t ci = 0;
for (auto cl : m_pillar_clusters) { for (const std::vector<unsigned> &cl : m_pillar_clusters) {
m_thr(); m_thr();
auto cidx = cl_centroids[ci++]; auto cidx = cl_centroids[ci++];
@ -615,10 +616,9 @@ void DefaultSupportTree::routing_to_ground()
if (!connect_to_nearpillar(sidehead, centerpillarID) && if (!connect_to_nearpillar(sidehead, centerpillarID) &&
!search_pillar_and_connect(sidehead)) { !search_pillar_and_connect(sidehead)) {
Vec3d pstart = sidehead.junction_point();
// Vec3d pend = Vec3d{pstart.x(), pstart.y(), gndlvl}; // Vec3d pend = Vec3d{pstart.x(), pstart.y(), gndlvl};
// Could not find a pillar, create one // Could not find a pillar, create one
create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); create_ground_pillar(sidehead.junction(), sidehead.dir, sidehead.id);
} }
} }
} }
@ -664,10 +664,10 @@ bool DefaultSupportTree::connect_to_model_body(Head &head)
zangle = std::max(zangle, PI/4); zangle = std::max(zangle, PI/4);
double h = std::sin(zangle) * head.fullwidth(); double h = std::sin(zangle) * head.fullwidth();
// The width of the tail head that we would like to have... // The width of the tail head that we would like to have...
h = std::min(hit.distance() - head.r_back_mm, h); h = std::min(hit.distance() - head.r_back_mm, h);
// If this is a mini pillar dont bother with the tail width, can be 0. // If this is a mini pillar dont bother with the tail width, can be 0.
if (head.r_back_mm < m_sm.cfg.head_back_radius_mm) h = std::max(h, 0.); if (head.r_back_mm < m_sm.cfg.head_back_radius_mm) h = std::max(h, 0.);
else if (h <= 0.) return false; else if (h <= 0.) return false;
@ -773,23 +773,23 @@ void DefaultSupportTree::interconnect_pillars()
// Ideally every pillar should be connected with at least one of its // Ideally every pillar should be connected with at least one of its
// neighbors if that neighbor is within max_pillar_link_distance // neighbors if that neighbor is within max_pillar_link_distance
// Pillars with height exceeding H1 will require at least one neighbor // Pillars with height exceeding H1 will require at least one neighbor
// to connect with. Height exceeding H2 require two neighbors. // to connect with. Height exceeding H2 require two neighbors.
double H1 = m_sm.cfg.max_solo_pillar_height_mm; double H1 = m_sm.cfg.max_solo_pillar_height_mm;
double H2 = m_sm.cfg.max_dual_pillar_height_mm; double H2 = m_sm.cfg.max_dual_pillar_height_mm;
double d = m_sm.cfg.max_pillar_link_distance_mm; double d = m_sm.cfg.max_pillar_link_distance_mm;
//A connection between two pillars only counts if the height ratio is // A connection between two pillars only counts if the height ratio is
// bigger than 50% // bigger than 50%
double min_height_ratio = 0.5; double min_height_ratio = 0.5;
std::set<unsigned long> pairs; std::set<unsigned long> pairs;
// A function to connect one pillar with its neighbors. THe number of // A function to connect one pillar with its neighbors. THe number of
// neighbors is given in the configuration. This function if called // neighbors is given in the configuration. This function if called
// for every pillar in the pillar index. A pair of pillar will not // for every pillar in the pillar index. A pair of pillar will not
// be connected multiple times this is ensured by the 'pairs' set which // be connected multiple times this is ensured by the 'pairs' set which
// remembers the processed pillar pairs // remembers the processed pillar pairs
auto cascadefn = auto cascadefn =
[this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el)
{ {
@ -797,10 +797,10 @@ void DefaultSupportTree::interconnect_pillars()
const Pillar& pillar = m_builder.pillar(el.second); // actual pillar const Pillar& pillar = m_builder.pillar(el.second); // actual pillar
// Get the max number of neighbors a pillar should connect to // Get the max number of neighbors a pillar should connect to
unsigned neighbors = m_sm.cfg.pillar_cascade_neighbors; unsigned neighbors = m_sm.cfg.pillar_cascade_neighbors;
// connections are already enough for the pillar // connections are already enough for the pillar
if(pillar.links >= neighbors) return; if(pillar.links >= neighbors) return;
double max_d = d * pillar.r_start / m_sm.cfg.head_back_radius_mm; double max_d = d * pillar.r_start / m_sm.cfg.head_back_radius_mm;
@ -809,7 +809,7 @@ void DefaultSupportTree::interconnect_pillars()
return distance(e.first, qp) < max_d; return distance(e.first, qp) < max_d;
}); });
// sort the result by distance (have to check if this is needed) // sort the result by distance (have to check if this is needed)
std::sort(qres.begin(), qres.end(), std::sort(qres.begin(), qres.end(),
[qp](const PointIndexEl& e1, const PointIndexEl& e2){ [qp](const PointIndexEl& e1, const PointIndexEl& e2){
return distance(e1.first, qp) < distance(e2.first, qp); return distance(e1.first, qp) < distance(e2.first, qp);
@ -821,23 +821,23 @@ void DefaultSupportTree::interconnect_pillars()
auto a = el.second, b = re.second; auto a = el.second, b = re.second;
// Get unique hash for the given pair (order doesn't matter) // Get unique hash for the given pair (order doesn't matter)
auto hashval = pairhash(a, b); auto hashval = pairhash(a, b);
// Search for the pair amongst the remembered pairs // Search for the pair amongst the remembered pairs
if(pairs.find(hashval) != pairs.end()) continue; if(pairs.find(hashval) != pairs.end()) continue;
const Pillar& neighborpillar = m_builder.pillar(re.second); const Pillar& neighborpillar = m_builder.pillar(re.second);
// this neighbor is occupied, skip // this neighbor is occupied, skip
if (neighborpillar.links >= neighbors) continue; if (neighborpillar.links >= neighbors) continue;
if (neighborpillar.r_start < pillar.r_start) continue; if (neighborpillar.r_start < pillar.r_start) continue;
if(interconnect(pillar, neighborpillar)) { if(interconnect(pillar, neighborpillar)) {
pairs.insert(hashval); pairs.insert(hashval);
// If the interconnection length between the two pillars is // If the interconnection length between the two pillars is
// less than 50% of the longer pillar's height, don't count // less than 50% of the longer pillar's height, don't count
if(pillar.height < H1 || if(pillar.height < H1 ||
neighborpillar.height / pillar.height > min_height_ratio) neighborpillar.height / pillar.height > min_height_ratio)
m_builder.increment_links(pillar); m_builder.increment_links(pillar);
@ -848,30 +848,30 @@ void DefaultSupportTree::interconnect_pillars()
} }
// connections are enough for one pillar // connections are enough for one pillar
if(pillar.links >= neighbors) break; if(pillar.links >= neighbors) break;
} }
}; };
// Run the cascade for the pillars in the index // Run the cascade for the pillars in the index
m_pillar_index.foreach(cascadefn); m_pillar_index.foreach(cascadefn);
// We would be done here if we could allow some pillars to not be // We would be done here if we could allow some pillars to not be
// connected with any neighbors. But this might leave the support tree // connected with any neighbors. But this might leave the support tree
// unprintable. // unprintable.
// //
// The current solution is to insert additional pillars next to these // The current solution is to insert additional pillars next to these
// lonely pillars. One or even two additional pillar might get inserted // lonely pillars. One or even two additional pillar might get inserted
// depending on the length of the lonely pillar. // depending on the length of the lonely pillar.
size_t pillarcount = m_builder.pillarcount(); size_t pillarcount = m_builder.pillarcount();
// Again, go through all pillars, this time in the whole support tree // Again, go through all pillars, this time in the whole support tree
// not just the index. // not just the index.
for(size_t pid = 0; pid < pillarcount; pid++) { for(size_t pid = 0; pid < pillarcount; pid++) {
auto pillar = [this, pid]() { return m_builder.pillar(pid); }; auto pillar = [this, pid]() { return m_builder.pillar(pid); };
// Decide how many additional pillars will be needed: // Decide how many additional pillars will be needed:
unsigned needpillars = 0; unsigned needpillars = 0;
if (pillar().bridges > m_sm.cfg.max_bridges_on_pillar) if (pillar().bridges > m_sm.cfg.max_bridges_on_pillar)
@ -887,17 +887,17 @@ void DefaultSupportTree::interconnect_pillars()
needpillars = std::max(pillar().links, needpillars) - pillar().links; needpillars = std::max(pillar().links, needpillars) - pillar().links;
if (needpillars == 0) continue; if (needpillars == 0) continue;
// Search for new pillar locations: // Search for new pillar locations:
bool found = false; bool found = false;
double alpha = 0; // goes to 2Pi double alpha = 0; // goes to 2Pi
double r = 2 * m_sm.cfg.base_radius_mm; double r = 2 * m_sm.cfg.base_radius_mm;
Vec3d pillarsp = pillar().startpoint(); Vec3d pillarsp = pillar().startpoint();
// temp value for starting point detection // temp value for starting point detection
Vec3d sp(pillarsp.x(), pillarsp.y(), pillarsp.z() - r); Vec3d sp(pillarsp.x(), pillarsp.y(), pillarsp.z() - r);
// A vector of bool for placement feasbility // A vector of bool for placement feasbility
std::vector<bool> canplace(needpillars, false); std::vector<bool> canplace(needpillars, false);
std::vector<Vec3d> spts(needpillars); // vector of starting points std::vector<Vec3d> spts(needpillars); // vector of starting points
@ -916,12 +916,12 @@ void DefaultSupportTree::interconnect_pillars()
s.y() += std::sin(a) * r; s.y() += std::sin(a) * r;
spts[n] = s; spts[n] = s;
// Check the path vertically down // Check the path vertically down
Vec3d check_from = s + Vec3d{0., 0., pillar().r_start}; Vec3d check_from = s + Vec3d{0., 0., pillar().r_start};
auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r_start); auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r_start);
Vec3d gndsp{s.x(), s.y(), gnd}; Vec3d gndsp{s.x(), s.y(), gnd};
// If the path is clear, check for pillar base collisions // If the path is clear, check for pillar base collisions
canplace[n] = std::isinf(hr.distance()) && canplace[n] = std::isinf(hr.distance()) &&
std::sqrt(m_sm.emesh.squared_distance(gndsp)) > std::sqrt(m_sm.emesh.squared_distance(gndsp)) >
min_dist; min_dist;
@ -930,7 +930,7 @@ void DefaultSupportTree::interconnect_pillars()
found = std::all_of(canplace.begin(), canplace.end(), found = std::all_of(canplace.begin(), canplace.end(),
[](bool v) { return v; }); [](bool v) { return v; });
// 20 angles will be tried... // 20 angles will be tried...
alpha += 0.1 * PI; alpha += 0.1 * PI;
} }

View File

@ -1,7 +1,7 @@
#ifndef LEGACYSUPPORTTREE_HPP #ifndef LEGACYSUPPORTTREE_HPP
#define LEGACYSUPPORTTREE_HPP #define LEGACYSUPPORTTREE_HPP
#include "SupportTreeUtils.hpp" #include "SupportTreeUtilsLegacy.hpp"
#include <libslic3r/SLA/SpatIndex.hpp> #include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/Execution/ExecutionTBB.hpp> #include <libslic3r/Execution/ExecutionTBB.hpp>
@ -62,7 +62,6 @@ class DefaultSupportTree {
PtIndices m_iheads; // support points with pinhead PtIndices m_iheads; // support points with pinhead
PtIndices m_iheads_onmodel; PtIndices m_iheads_onmodel;
PtIndices m_iheadless; // headless support points
std::map<unsigned, AABBMesh::hit_result> m_head_to_ground_scans; std::map<unsigned, AABBMesh::hit_result> m_head_to_ground_scans;
@ -176,9 +175,8 @@ class DefaultSupportTree {
// jp is the starting junction point which needs to be routed down. // jp is the starting junction point which needs to be routed down.
// sourcedir is the allowed direction of an optional bridge between the // sourcedir is the allowed direction of an optional bridge between the
// jp junction and the final pillar. // jp junction and the final pillar.
bool create_ground_pillar(const Vec3d &jp, bool create_ground_pillar(const Junction &jp,
const Vec3d &sourcedir, const Vec3d &sourcedir,
double radius,
long head_id = SupportTreeNode::ID_UNSET); long head_id = SupportTreeNode::ID_UNSET);
void add_pillar_base(long pid) void add_pillar_base(long pid)

View File

@ -18,6 +18,8 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <libslic3r/I18N.hpp> #include <libslic3r/I18N.hpp>
#include <libnest2d/tools/benchmark.h>
//! macro used to mark string used at localization, //! macro used to mark string used at localization,
//! return same string //! return same string
#define L(s) Slic3r::I18N::translate(s) #define L(s) Slic3r::I18N::translate(s)
@ -30,6 +32,9 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm,
auto builder = make_unique<SupportTreeBuilder>(ctl); auto builder = make_unique<SupportTreeBuilder>(ctl);
if (sm.cfg.enabled) { if (sm.cfg.enabled) {
Benchmark bench;
bench.start();
switch (sm.cfg.tree_type) { switch (sm.cfg.tree_type) {
case SupportTreeType::Default: { case SupportTreeType::Default: {
create_default_tree(*builder, sm); create_default_tree(*builder, sm);
@ -39,9 +44,18 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm,
create_branching_tree(*builder, sm); create_branching_tree(*builder, sm);
break; break;
} }
case SupportTreeType::Organic: {
// TODO
}
default:; default:;
} }
bench.stop();
BOOST_LOG_TRIVIAL(info) << "Support tree creation took: "
<< bench.getElapsedSec()
<< " seconds";
builder->merge_and_cleanup(); // clean metadata, leave only the meshes. builder->merge_and_cleanup(); // clean metadata, leave only the meshes.
} }

View File

@ -50,7 +50,7 @@ struct SupportTreeConfig
// when bridges and pillars are merged. The resulting pillar should be a bit // when bridges and pillars are merged. The resulting pillar should be a bit
// thicker than the ones merging into it. How much thicker? I don't know // thicker than the ones merging into it. How much thicker? I don't know
// but it will be derived from this value. // but it will be derived from this value.
double pillar_widening_factor = .05; double pillar_widening_factor = .5;
// Radius in mm of the pillar base. // Radius in mm of the pillar base.
double base_radius_mm = 2.0; double base_radius_mm = 2.0;
@ -76,12 +76,20 @@ struct SupportTreeConfig
double pillar_base_safety_distance_mm = 0.5; double pillar_base_safety_distance_mm = 0.5;
unsigned max_bridges_on_pillar = 3; unsigned max_bridges_on_pillar = 3;
double max_weight_on_model_support = 10.f;
double head_fullwidth() const { double head_fullwidth() const {
return 2 * head_front_radius_mm + head_width_mm + return 2 * head_front_radius_mm + head_width_mm +
2 * head_back_radius_mm - head_penetration_mm; 2 * head_back_radius_mm - head_penetration_mm;
} }
double safety_distance() const { return safety_distance_mm; }
double safety_distance(double r) const
{
return std::min(safety_distance_mm, r * safety_distance_mm / head_back_radius_mm);
}
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
// Compile time configuration values (candidates for runtime) // Compile time configuration values (candidates for runtime)
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
@ -89,13 +97,15 @@ struct SupportTreeConfig
// The max Z angle for a normal at which it will get completely ignored. // The max Z angle for a normal at which it will get completely ignored.
static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0; static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0;
// The shortest distance of any support structure from the model surface // The safety gap between a support structure and model body. For support
// struts smaller than head_back_radius, the safety distance is scaled
// down accordingly. see method safety_distance()
static const double constexpr safety_distance_mm = 0.5; static const double constexpr safety_distance_mm = 0.5;
static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_solo_pillar_height_mm = 15.0;
static const double constexpr max_dual_pillar_height_mm = 35.0; static const double constexpr max_dual_pillar_height_mm = 35.0;
static const double constexpr optimizer_rel_score_diff = 1e-6; static const double constexpr optimizer_rel_score_diff = 1e-10;
static const unsigned constexpr optimizer_max_iterations = 1000; static const unsigned constexpr optimizer_max_iterations = 2000;
static const unsigned constexpr pillar_cascade_neighbors = 3; static const unsigned constexpr pillar_cascade_neighbors = 3;
}; };
@ -116,11 +126,11 @@ struct SupportableMesh
: emesh{trmsh}, pts{sp}, cfg{c} : emesh{trmsh}, pts{sp}, cfg{c}
{} {}
explicit SupportableMesh(const AABBMesh &em, // explicit SupportableMesh(const AABBMesh &em,
const SupportPoints &sp, // const SupportPoints &sp,
const SupportTreeConfig &c) // const SupportTreeConfig &c)
: emesh{em}, pts{sp}, cfg{c} // : emesh{em}, pts{sp}, cfg{c}
{} // {}
}; };
inline double ground_level(const SupportableMesh &sm) inline double ground_level(const SupportableMesh &sm)

View File

@ -67,25 +67,32 @@ struct SupportTreeNode
long id = ID_UNSET; // For identification withing a tree. long id = ID_UNSET; // For identification withing a tree.
}; };
// A junction connecting bridges and pillars
struct Junction: public SupportTreeNode {
double r = 1;
Vec3d pos;
Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
};
// A pinhead originating from a support point // A pinhead originating from a support point
struct Head: public SupportTreeNode { struct Head: public SupportTreeNode {
Vec3d dir = DOWN; Vec3d dir = DOWN;
Vec3d pos = {0, 0, 0}; Vec3d pos = {0, 0, 0};
double r_back_mm = 1; double r_back_mm = 1;
double r_pin_mm = 0.5; double r_pin_mm = 0.5;
double width_mm = 2; double width_mm = 2;
double penetration_mm = 0.5; double penetration_mm = 0.5;
// If there is a pillar connecting to this head, then the id will be set. // If there is a pillar connecting to this head, then the id will be set.
long pillar_id = ID_UNSET; long pillar_id = ID_UNSET;
long bridge_id = ID_UNSET; long bridge_id = ID_UNSET;
inline void invalidate() { id = ID_UNSET; } inline void invalidate() { id = ID_UNSET; }
inline bool is_valid() const { return id >= 0; } inline bool is_valid() const { return id >= 0; }
Head(double r_big_mm, Head(double r_big_mm,
double r_small_mm, double r_small_mm,
double length_mm, double length_mm,
@ -103,21 +110,21 @@ struct Head: public SupportTreeNode {
{ {
return real_width() - penetration_mm; return real_width() - penetration_mm;
} }
inline Junction junction() const
{
Junction j{pos + (fullwidth() - r_back_mm) * dir, r_back_mm};
j.id = -this->id; // Remember that this junction is from a head
return j;
}
inline Vec3d junction_point() const inline Vec3d junction_point() const
{ {
return pos + (fullwidth() - r_back_mm) * dir; return junction().pos;
} }
}; };
// A junction connecting bridges and pillars
struct Junction: public SupportTreeNode {
double r = 1;
Vec3d pos;
Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
};
// A straight pillar. Only has an endpoint and a height. No explicit starting // A straight pillar. Only has an endpoint and a height. No explicit starting
// point is given, as it would allow the pillar to be angled. // point is given, as it would allow the pillar to be angled.
// Some connection info with other primitives can also be tracked. // Some connection info with other primitives can also be tracked.
@ -189,6 +196,10 @@ struct DiffBridge: public Bridge {
DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e) DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e)
: Bridge{p_s, p_e, r_s}, end_r{r_e} : Bridge{p_s, p_e, r_s}, end_r{r_e}
{} {}
DiffBridge(const Junction &j_s, const Junction &j_e)
: Bridge{j_s.pos, j_e.pos, j_s.r}, end_r{j_e.r}
{}
}; };
// This class will hold the support tree parts (not meshes, but logical parts) // This class will hold the support tree parts (not meshes, but logical parts)

View File

@ -165,7 +165,8 @@ indexed_triangle_set halfcone(double baseheight,
{ {
assert(steps > 0); assert(steps > 0);
if (baseheight <= 0 || steps <= 0) return {}; if (baseheight <= 0 || steps <= 0 || (r_bottom <= 0. && r_top <= 0.))
return {};
indexed_triangle_set base; indexed_triangle_set base;

View File

@ -5,7 +5,7 @@
namespace Slic3r { namespace sla { namespace Slic3r { namespace sla {
enum class SupportTreeType { Default, Branching }; enum class SupportTreeType { Default, Branching, Organic };
enum class PillarConnectionMode { zigzag, cross, dynamic }; enum class PillarConnectionMode { zigzag, cross, dynamic };
}} // namespace Slic3r::sla }} // namespace Slic3r::sla

View File

@ -6,10 +6,13 @@
#include <libslic3r/Execution/Execution.hpp> #include <libslic3r/Execution/Execution.hpp>
#include <libslic3r/Optimize/NLoptOptimizer.hpp> #include <libslic3r/Optimize/NLoptOptimizer.hpp>
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
#include <libslic3r/MeshNormals.hpp> #include <libslic3r/MeshNormals.hpp>
#include <libslic3r/Geometry.hpp> #include <libslic3r/Geometry.hpp>
#include <libslic3r/SLA/SupportTreeBuilder.hpp> #include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <boost/variant.hpp>
#include <boost/container/small_vector.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
namespace Slic3r { namespace sla { namespace Slic3r { namespace sla {
@ -23,36 +26,12 @@ using Slic3r::opt::AlgNLoptGenetic;
using Slic3r::Geometry::dir_to_spheric; using Slic3r::Geometry::dir_to_spheric;
using Slic3r::Geometry::spheric_to_dir; using Slic3r::Geometry::spheric_to_dir;
// Helper function for pillar interconnection where pairs of already connected
// pillars should be checked for not to be processed again. This can be done
// in constant time with a set of hash values uniquely representing a pair of
// integers. The order of numbers within the pair should not matter, it has
// the same unique hash. The hash value has to have twice as many bits as the
// arguments need. If the same integral type is used for args and return val,
// make sure the arguments use only the half of the type's bit depth.
template<class I, class DoubleI = IntegerOnly<I>>
IntegerOnly<DoubleI> pairhash(I a, I b)
{
using std::ceil; using std::log2; using std::max; using std::min;
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
I g = min(a, b), l = max(a, b);
// Assume the hash will fit into the output variable
assert((g ? (ceil(log2(g))) : 0) <= shift);
assert((l ? (ceil(log2(l))) : 0) <= shift);
return (DoubleI(g) << shift) + l;
}
// Give points on a 3D ring with given center, radius and orientation // Give points on a 3D ring with given center, radius and orientation
// method based on: // method based on:
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
template<size_t N> template<size_t N>
class PointRing { class PointRing {
std::array<double, N> m_phis; std::array<double, N - 1> m_phis;
// Two vectors that will be perpendicular to each other and to the // Two vectors that will be perpendicular to each other and to the
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
@ -72,7 +51,7 @@ class PointRing {
public: public:
PointRing(const Vec3d &n) : m_phis{linspace_array<N>(0., 2 * PI)} PointRing(const Vec3d &n) : m_phis{linspace_array<N - 1>(0., 2 * PI)}
{ {
// We have to address the case when the direction vector v (same as // We have to address the case when the direction vector v (same as
// dir) is coincident with one of the world axes. In this case two of // dir) is coincident with one of the world axes. In this case two of
@ -91,7 +70,10 @@ public:
Vec3d get(size_t idx, const Vec3d &src, double r) const Vec3d get(size_t idx, const Vec3d &src, double r) const
{ {
double phi = m_phis[idx]; if (idx == 0)
return src;
double phi = m_phis[idx - 1];
double sinphi = std::sin(phi); double sinphi = std::sin(phi);
double cosphi = std::cos(phi); double cosphi = std::cos(phi);
@ -131,31 +113,40 @@ inline StopCriteria get_criteria(const SupportTreeConfig &cfg)
// A simple sphere with a center and a radius // A simple sphere with a center and a radius
struct Ball { Vec3d p; double R; }; struct Ball { Vec3d p; double R; };
struct Beam { // Defines a set of rays displaced along a cone's surface template<size_t Samples = 8>
static constexpr size_t SAMPLES = 8; struct Beam_ { // Defines a set of rays displaced along a cone's surface
static constexpr size_t SAMPLES = Samples;
Vec3d src; Vec3d src;
Vec3d dir; Vec3d dir;
double r1; double r1;
double r2; // radius of the beam 1 unit further from src in dir direction double r2; // radius of the beam 1 unit further from src in dir direction
Beam(const Vec3d &s, const Vec3d &d, double R1, double R2): Beam_(const Vec3d &s, const Vec3d &d, double R1, double R2)
src{s}, dir{d}, r1{R1}, r2{R2} {}; : src{s}, dir{d}, r1{R1}, r2{R2} {};
Beam(const Ball &src_ball, const Ball &dst_ball): Beam_(const Ball &src_ball, const Ball &dst_ball)
src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R} : src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R}
{ {
r2 = src_ball.R + r2 = src_ball.R;
(dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p); auto d = distance(src_ball.p, dst_ball.p);
if (d > EPSILON)
r2 += (dst_ball.R - src_ball.R) / d;
} }
Beam(const Vec3d &s, const Vec3d &d, double R) Beam_(const Vec3d &s, const Vec3d &d, double R)
: src{s}, dir{d}, r1{R}, r2{R} : src{s}, dir{d}, r1{R}, r2{R}
{} {}
}; };
template<class Ex> using Beam = Beam_<>;
Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
template<class Ex, size_t RayCount = Beam::SAMPLES>
Hit beam_mesh_hit(Ex policy,
const AABBMesh &mesh,
const Beam_<RayCount> &beam,
double sd)
{ {
Vec3d src = beam.src; Vec3d src = beam.src;
Vec3d dst = src + beam.dir; Vec3d dst = src + beam.dir;
@ -164,19 +155,19 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
Vec3d D = (dst - src); Vec3d D = (dst - src);
Vec3d dir = D.normalized(); Vec3d dir = D.normalized();
PointRing<Beam::SAMPLES> ring{dir}; PointRing<RayCount> ring{dir};
using Hit = AABBMesh::hit_result; using Hit = AABBMesh::hit_result;
// Hit results // Hit results
std::array<Hit, Beam::SAMPLES> hits; std::array<Hit, RayCount> hits;
execution::for_each( execution::for_each(
ex, size_t(0), hits.size(), policy, size_t(0), hits.size(),
[&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) { [&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) {
Hit &hit = hits[i]; Hit &hit = hits[i];
// Point on the circle on the pin sphere // Point on the circle on the pin sphere
Vec3d p_src = ring.get(i, src, r_src + sd); Vec3d p_src = ring.get(i, src, r_src + sd);
Vec3d p_dst = ring.get(i, dst, r_dst + sd); Vec3d p_dst = ring.get(i, dst, r_dst + sd);
Vec3d raydir = (p_dst - p_src).normalized(); Vec3d raydir = (p_dst - p_src).normalized();
@ -193,7 +184,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
} }
} else } else
hit = hr; hit = hr;
}, std::min(execution::max_concurrency(ex), Beam::SAMPLES)); }, std::min(execution::max_concurrency(policy), RayCount));
return min_hit(hits.begin(), hits.end()); return min_hit(hits.begin(), hits.end());
} }
@ -208,7 +199,10 @@ Hit pinhead_mesh_hit(Ex ex,
double width, double width,
double sd) double sd)
{ {
static const size_t SAMPLES = 8; // Support tree generation speed depends heavily on this value. 8 is almost
// ok, but to prevent rare cases of collision, 16 is necessary, which makes
// the algorithm run about 60% longer.
static const size_t SAMPLES = 16;
// Move away slightly from the touching point to avoid raycasting on the // Move away slightly from the touching point to avoid raycasting on the
// inner surface of the mesh. // inner surface of the mesh.
@ -283,199 +277,12 @@ Hit pinhead_mesh_hit(Ex ex,
template<class Ex> template<class Ex>
Hit pinhead_mesh_hit(Ex ex, Hit pinhead_mesh_hit(Ex ex,
const AABBMesh &mesh, const AABBMesh &mesh,
const Head &head, const Head &head,
double safety_d) double safety_d)
{ {
return pinhead_mesh_hit(ex, mesh, head.pos, head.dir, head.r_pin_mm, return pinhead_mesh_hit(ex, mesh, head.pos, head.dir, head.r_pin_mm,
head.r_back_mm, head.width_mm, safety_d); head.r_back_mm, head.width_mm, safety_d);
}
template<class Ex>
std::optional<DiffBridge> search_widening_path(Ex policy,
const SupportableMesh &sm,
const Vec3d &jp,
const Vec3d &dir,
double radius,
double new_radius)
{
double w = radius + 2 * sm.cfg.head_back_radius_mm;
double stopval = w + jp.z() - ground_level(sm);
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
auto [polar, azimuth] = dir_to_spheric(dir);
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
auto &[plr, azm, t] = input;
auto d = spheric_to_dir(plr, azm).normalized();
auto sd = new_radius * sm.cfg.safety_distance_mm /
sm.cfg.head_back_radius_mm;
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
new_radius, t, sd)
.distance();
Beam beam{jp + t * d, d, new_radius};
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
if (ret > t && std::isinf(down))
ret += jp.z() - ground_level(sm);
return ret;
},
initvals({polar, azimuth, w}), // start with what we have
bounds({
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
{-PI, PI}, // azimuth can be a full search
{radius + sm.cfg.head_back_radius_mm,
fallback_ratio * sm.cfg.max_bridge_length_mm}
}));
if (oresult.score >= stopval) {
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
double t = std::get<2>(oresult.optimum);
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
}
return {};
}
// This is a proxy function for pillar creation which will mind the gap
// between the pad and the model bottom in zero elevation mode.
// 'pinhead_junctionpt' is the starting junction point which needs to be
// routed down. sourcedir is the allowed direction of an optional bridge
// between the jp junction and the final pillar.
template<class Ex>
std::pair<bool, long> create_ground_pillar(
Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Vec3d &pinhead_junctionpt,
const Vec3d &sourcedir,
double radius,
double end_radius,
long head_id = SupportTreeNode::ID_UNSET)
{
Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir;
long pillar_id = SupportTreeNode::ID_UNSET;
bool can_add_base = false, non_head = false;
double gndlvl = 0.; // The Z level where pedestals should be
double jp_gnd = 0.; // The lowest Z where a junction center can be
double gap_dist = 0.; // The gap distance between the model and the pad
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
(bool base_en = true)
{
can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.;
gndlvl = ground_level(sm);
if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
};
eval_limits();
// We are dealing with a mini pillar that's potentially too long
if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
{
std::optional<DiffBridge> diffbr =
search_widening_path(policy, sm, jp, dir, radius,
sm.cfg.head_back_radius_mm);
if (diffbr && diffbr->endp.z() > jp_gnd) {
auto &br = builder.add_diffbridge(*diffbr);
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
endp = diffbr->endp;
radius = diffbr->end_r;
builder.add_junction(endp, radius);
non_head = true;
dir = diffbr->get_dir();
eval_limits();
} else return {false, pillar_id};
}
if (sm.cfg.object_elevation_mm < EPSILON)
{
// get a suitable direction for the corrector bridge. It is the
// original sourcedir's azimuth but the polar angle is saturated to the
// configured bridge slope.
auto [polar, azimuth] = dir_to_spheric(dir);
polar = PI - sm.cfg.bridge_slope;
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
t = 0.;
double zd = endp.z() - jp_gnd;
double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
tmax = std::min(tmax, tmax2);
Vec3d nexp = endp;
double dlast = 0.;
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) &&
t < tmax)
{
t += radius;
nexp = endp + t * d;
}
if (dlast < gap_dist && can_add_base) {
nexp = endp;
t = 0.;
can_add_base = false;
eval_limits(can_add_base);
zd = endp.z() - jp_gnd;
tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
tmax = std::min(tmax, tmax2);
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) {
t += radius;
nexp = endp + t * d;
}
}
// Could not find a path to avoid the pad gap
if (dlast < gap_dist) return {false, pillar_id};
if (t > 0.) { // Need to make additional bridge
const Bridge& br = builder.add_bridge(endp, nexp, radius);
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
builder.add_junction(nexp, radius);
endp = nexp;
non_head = true;
}
}
Vec3d gp = to_floor(endp);
double h = endp.z() - gp.z();
pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) :
builder.add_pillar(gp, h, radius, end_radius);
if (can_add_base)
builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm,
sm.cfg.base_radius_mm);
return {true, pillar_id};
} }
inline double distance(const SupportPoint &a, const SupportPoint &b) inline double distance(const SupportPoint &a, const SupportPoint &b)
@ -530,13 +337,13 @@ bool optimize_pinhead_placement(Ex policy,
double back_r = head.r_back_mm; double back_r = head.r_back_mm;
// skip if the tilt is not sane // skip if the tilt is not sane
if (polar < PI - m.cfg.normal_cutoff_angle) return false; if (polar < PI - m.cfg.normal_cutoff_angle) return false;
// We saturate the polar angle to 3pi/4 // We saturate the polar angle to 3pi/4
polar = std::max(polar, PI - m.cfg.bridge_slope); polar = std::max(polar, PI - m.cfg.bridge_slope);
// save the head (pinpoint) position // save the head (pinpoint) position
Vec3d hp = head.pos; Vec3d hp = head.pos;
double lmin = m.cfg.head_width_mm, lmax = lmin; double lmin = m.cfg.head_width_mm, lmax = lmin;
@ -545,28 +352,26 @@ bool optimize_pinhead_placement(Ex policy,
lmin = 0., lmax = m.cfg.head_penetration_mm; lmin = 0., lmax = m.cfg.head_penetration_mm;
} }
// The distance needed for a pinhead to not collide with model. // The distance needed for a pinhead to not collide with model.
double w = lmin + 2 * back_r + 2 * m.cfg.head_front_radius_mm - double w = lmin + 2 * back_r + 2 * m.cfg.head_front_radius_mm -
m.cfg.head_penetration_mm; m.cfg.head_penetration_mm;
double pin_r = head.r_pin_mm; double pin_r = head.r_pin_mm;
// Reassemble the now corrected normal // Reassemble the now corrected normal
auto nn = spheric_to_dir(polar, azimuth).normalized(); auto nn = spheric_to_dir(polar, azimuth).normalized();
double sd = back_r * m.cfg.safety_distance_mm / double sd = m.cfg.safety_distance(back_r);
m.cfg.head_back_radius_mm;
// check available distance // check available distance
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd);
sd);
if (t.distance() < w) { if (t.distance() < w) {
// Let's try to optimize this angle, there might be a // Let's try to optimize this angle, there might be a
// viable normal that doesn't collide with the model // viable normal that doesn't collide with the model
// geometry and its very close to the default. // geometry and its very close to the default.
Optimizer<AlgNLoptGenetic> solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); Optimizer<opt::AlgNLoptMLSL_Subplx> solver(get_criteria(m.cfg).stop_score(w).max_iterations(100));
solver.seed(0); // we want deterministic behavior solver.seed(0); // we want deterministic behavior
auto oresult = solver.to_max().optimize( auto oresult = solver.to_max().optimize(
@ -627,7 +432,7 @@ std::optional<Head> calculate_pinhead_placement(Ex policy,
}; };
if (optimize_pinhead_placement(policy, sm, head)) { if (optimize_pinhead_placement(policy, sm, head)) {
head.id = suppt_idx; head.id = long(suppt_idx);
return head; return head;
} }
@ -635,81 +440,335 @@ std::optional<Head> calculate_pinhead_placement(Ex policy,
return {}; return {};
} }
template<class Ex> struct GroundConnection {
std::pair<bool, long> connect_to_ground(Ex policy, // Currently, a ground connection will contain at most 2 additional junctions
SupportTreeBuilder &builder, // which will not require any allocations. If I come up with an algo that
const SupportableMesh &sm, // can produce a route to ground with more junctions, this design will be
const Junction &j, // able to handle that.
const Vec3d &dir, static constexpr size_t MaxExpectedJunctions = 3;
double end_r)
boost::container::small_vector<Junction, MaxExpectedJunctions> path;
std::optional<Pedestal> pillar_base;
operator bool() const { return pillar_base.has_value() && !path.empty(); }
};
inline long build_ground_connection(SupportTreeBuilder &builder,
const SupportableMesh &sm,
const GroundConnection &conn)
{ {
auto hjp = j.pos; long ret = SupportTreeNode::ID_UNSET;
double r = j.r;
auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); if (!conn)
double d = 0, tdown = 0; return ret;
t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm);
while (d < t && auto it = conn.path.begin();
!std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, auto itnx = std::next(it);
Beam{hjp + d * dir, DOWN, r, r2}, sd)
.distance())) { while (itnx != conn.path.end()) {
d += r; builder.add_diffbridge(*it, *itnx);
builder.add_junction(*itnx);
++it; ++itnx;
} }
if(!std::isinf(tdown)) auto gp = conn.path.back().pos;
return {false, SupportTreeNode::ID_UNSET}; gp.z() = ground_level(sm);
double h = conn.path.back().pos.z() - gp.z();
Vec3d endp = hjp + d * dir; if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) {
auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r); h += sm.pad_cfg.wall_thickness_mm;
gp.z() -= sm.pad_cfg.wall_thickness_mm;
}
if (ret.second >= 0) { // TODO: does not work yet
builder.add_bridge(hjp, endp, r); // if (conn.path.back().id < 0) {
builder.add_junction(endp, r); // // this is a head
// long head_id = std::abs(conn.path.back().id);
// ret = builder.add_pillar(head_id, h);
// } else
ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top);
if (conn.pillar_base->r_top >= sm.cfg.head_back_radius_mm)
builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom);
return ret;
}
template<class Fn>
constexpr bool IsWideningFn = std::is_invocable_r_v</*retval*/ double,
Fn,
Ball /*source*/,
Vec3d /*dir*/,
double /*length*/>;
// A widening function can determine how many ray samples should a beam contain
// (see in beam_mesh_hit)
template<class WFn> struct BeamSamples { static constexpr size_t Value = 8; };
template<class WFn> constexpr size_t BeamSamplesV = BeamSamples<remove_cvref_t<WFn>>::Value;
// To use with check_ground_route, full will check the bridge and the pillar,
// PillarOnly checks only the pillar for collisions.
enum class GroundRouteCheck { Full, PillarOnly };
// Returns the collision point with mesh if there is a collision or a ground point,
// given a source point with a direction of a potential avoidance bridge and
// a bridge length.
template<class Ex, class WideningFn,
class = std::enable_if_t<IsWideningFn<WideningFn>> >
Vec3d check_ground_route(
Ex policy,
const SupportableMesh &sm,
const Junction &source, // source location
const Vec3d &dir, // direction of the bridge from the source
double bridge_len, // lenght of the avoidance bridge
WideningFn &&wideningfn, // Widening strategy
GroundRouteCheck type = GroundRouteCheck::Full
)
{
static const constexpr auto Samples = BeamSamplesV<WideningFn>;
Vec3d ret;
const auto sd = sm.cfg.safety_distance(source.r);
const auto gndlvl = ground_level(sm);
// Intersection of the suggested bridge with ground plane. If the bridge
// spans below ground, stop it at ground level.
double t = (gndlvl - source.pos.z()) / dir.z();
bridge_len = std::min(t, bridge_len);
Vec3d bridge_end = source.pos + bridge_len * dir;
double down_l = bridge_end.z() - gndlvl;
double bridge_r = wideningfn(Ball{source.pos, source.r}, dir, bridge_len);
double brhit_dist = 0.;
if (bridge_len > EPSILON && type == GroundRouteCheck::Full) {
// beam_mesh_hit with a zero lenght bridge is invalid
Beam_<Samples> bridgebeam{Ball{source.pos, source.r},
Ball{bridge_end, bridge_r}};
auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd);
brhit_dist = brhit.distance();
} else {
brhit_dist = bridge_len;
}
if (brhit_dist < bridge_len) {
ret = (source.pos + brhit_dist * dir);
} else if (down_l > 0.) {
// check if pillar can be placed below
auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl};
double end_radius = wideningfn(
Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl);
Beam_<Samples> gndbeam {{bridge_end, bridge_r}, {gp, end_radius}};
auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd);
double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON);
if (source.r >= sm.cfg.head_back_radius_mm && gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) {
// Dealing with zero elevation mode, to not route pillars
// into the gap between the optional pad and the model
double gap = std::sqrt(sm.emesh.squared_distance(gp));
double base_r = std::max(sm.cfg.base_radius_mm, end_radius);
double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r;
if (gap < min_gap) {
gnd_hit_d = down_l - min_gap + gap;
}
}
ret = Vec3d{bridge_end.x(), bridge_end.y(), bridge_end.z() - gnd_hit_d};
} else {
ret = bridge_end;
} }
return ret; return ret;
} }
template<class Ex> // Searching a ground connection from an arbitrary source point.
std::pair<bool, long> search_ground_route(Ex policy, // Currently, the result will contain one avoidance bridge (at most) and a
SupportTreeBuilder &builder, // pillar to the ground, if it's feasible
const SupportableMesh &sm, template<class Ex, class WideningFn,
const Junction &j, class = std::enable_if_t<IsWideningFn<WideningFn>> >
double end_radius, GroundConnection deepsearch_ground_connection(
const Vec3d &init_dir = DOWN) Ex policy,
const SupportableMesh &sm,
const Junction &source,
WideningFn &&wideningfn,
const Vec3d &init_dir = DOWN)
{ {
double downdst = j.pos.z() - ground_level(sm); constexpr unsigned MaxIterationsGlobal = 5000;
constexpr unsigned MaxIterationsLocal = 100;
constexpr double RelScoreDiff = 0.05;
auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius); const auto gndlvl = ground_level(sm);
if (res.first)
return res;
// Optimize bridge direction: // The used solver (AlgNLoptMLSL_Subplx search method) is composed of a global (MLSL)
// Straight path failed so we will try to search for a suitable // and a local (Subplex) search method. Criteria can be set in a way that
// direction out of the cavity. // local searches are quick and less accurate. The global method will only
auto [polar, azimuth] = dir_to_spheric(init_dir); // consider the max iteration number and the stop score (Z level <= ground)
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg).stop_score(1e6)); auto criteria = get_criteria(sm.cfg); // get defaults from cfg
solver.seed(0); // we want deterministic behavior criteria.max_iterations(MaxIterationsGlobal);
criteria.abs_score_diff(NaNd);
criteria.rel_score_diff(NaNd);
criteria.stop_score(gndlvl);
auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; auto criteria_loc = criteria;
auto oresult = solver.to_max().optimize( criteria_loc.max_iterations(MaxIterationsLocal);
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { criteria_loc.abs_score_diff(EPSILON);
auto &[plr, azm] = input; criteria_loc.rel_score_diff(RelScoreDiff);
Vec3d n = spheric_to_dir(plr, azm).normalized();
Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}};
return beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
},
initvals({polar, azimuth}), // let's start with what we have
bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} })
);
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); Optimizer<opt::AlgNLoptMLSL_Subplx> solver(criteria);
solver.set_loc_criteria(criteria_loc);
solver.seed(0); // require repeatability
return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius); // functor returns the z height of collision point, given a polar and
// azimuth angles as bridge direction and bridge length. The route is
// traced from source, through this bridge and an attached pillar. If there
// is a collision with the mesh, the Z height is returned. Otherwise the
// z level of ground is returned.
auto z_fn = [&](const opt::Input<3> &input) {
// solver suggests polar, azimuth and bridge length values:
auto &[plr, azm, bridge_len] = input;
Vec3d n = spheric_to_dir(plr, azm);
Vec3d hitpt = check_ground_route(policy, sm, source, n, bridge_len, wideningfn);
return hitpt.z();
};
// Calculate the initial direction of the search by
// saturating the polar angle to max tilt defined in config
auto [plr_init, azm_init] = dir_to_spheric(init_dir);
plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope);
auto bound_constraints =
bounds({
{PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle
{-PI, PI}, // bounds for azimuth
{0., sm.cfg.max_bridge_length_mm} // bounds bridge length
});
// The optimizer can navigate fairly well on the mesh surface, finding
// lower and lower Z coordinates as collision points. MLSL is not a local
// search method, so it should not be trapped in a local minima. Eventually,
// this search should arrive at a ground location.
auto oresult = solver.to_min().optimize(
z_fn,
initvals({plr_init, azm_init, 0.}),
bound_constraints
);
GroundConnection conn;
// Extract and apply the result
auto [plr, azm, bridge_l] = oresult.optimum;
Vec3d n = spheric_to_dir(plr, azm);
assert(std::abs(n.norm() - 1.) < EPSILON);
double t = (gndlvl - source.pos.z()) / n.z();
bridge_l = std::min(t, bridge_l);
// Now the optimizer gave a possible route to ground with a bridge direction
// and length. This length can be shortened further by brute-force queries
// of free route straigt down for a possible pillar.
// NOTE: This requirement could be incorporated into the optimization as a
// constraint, but it would not find quickly enough an accurate solution,
// and it would be very hard to define a stop score which is very useful in
// terminating the search as soon as the ground is found.
double l = 0., l_max = bridge_l;
double zlvl = std::numeric_limits<double>::infinity();
while(zlvl > gndlvl && l <= l_max) {
zlvl = check_ground_route(policy, sm, source, n, l, wideningfn,
GroundRouteCheck::PillarOnly).z();
if (zlvl <= gndlvl)
bridge_l = l;
l += source.r;
}
Vec3d bridge_end = source.pos + bridge_l * n;
Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl};
double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l);
double down_l = bridge_end.z() - gndlvl;
double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l);
double base_r = std::max(sm.cfg.base_radius_mm, end_radius);
// Even if the search was not succesful, the result is populated by the
// source and the last best result of the optimization.
conn.path.emplace_back(source);
if (bridge_l > EPSILON)
conn.path.emplace_back(Junction{bridge_end, bridge_r});
// The resulting ground connection is only valid if the pillar base is set.
// At this point it will only be set if the search was succesful.
if (z_fn(opt::Input<3>({plr, azm, bridge_l})) <= gndlvl)
conn.pillar_base =
Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius};
return conn;
}
// Ground route search with a predefined end radius
template<class Ex>
GroundConnection deepsearch_ground_connection(Ex policy,
const SupportableMesh &sm,
const Junction &source,
double end_radius,
const Vec3d &init_dir = DOWN)
{
double gndlvl = ground_level(sm);
auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) {
if (len < EPSILON)
return src.R;
Vec3d dst = src.p + len * dir;
double widening = end_radius - src.R;
double zlen = dst.z() - gndlvl;
double full_len = len + zlen;
double r = src.R + widening * len / full_len;
return r;
};
static_assert(IsWideningFn<decltype(wfn)>, "Not a widening function");
return deepsearch_ground_connection(policy, sm, source, wfn, init_dir);
}
struct DefaultWideningModel {
static constexpr double WIDENING_SCALE = 0.02;
const SupportableMesh &sm;
double operator()(const Ball &src, const Vec3d & /*dir*/, double len) {
static_assert(IsWideningFn<DefaultWideningModel>,
"DefaultWideningModel is not a widening function");
double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len;
return std::max(src.R, sm.cfg.head_back_radius_mm) + w;
};
};
template<> struct BeamSamples<DefaultWideningModel> {
static constexpr size_t Value = 16;
};
template<class Ex>
GroundConnection deepsearch_ground_connection(Ex policy,
const SupportableMesh &sm,
const Junction &source,
const Vec3d &init_dir = DOWN)
{
return deepsearch_ground_connection(policy, sm, source,
DefaultWideningModel{sm}, init_dir);
} }
template<class Ex> template<class Ex>
@ -729,8 +788,7 @@ bool optimize_anchor_placement(Ex policy,
double lmax = std::min(sm.cfg.head_width_mm, double lmax = std::min(sm.cfg.head_width_mm,
distance(from.pos, anchor.pos) - 2 * from.r); distance(from.pos, anchor.pos) - 2 * from.r);
double sd = anchor.r_back_mm * sm.cfg.safety_distance_mm / double sd = sm.cfg.safety_distance(anchor.r_back_mm);
sm.cfg.head_back_radius_mm;
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg) Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg)
.stop_score(anchor.fullwidth()) .stop_score(anchor.fullwidth())
@ -796,6 +854,92 @@ std::optional<Anchor> calculate_anchor_placement(Ex policy,
return anchor; return anchor;
} }
inline bool is_outside_support_cone(const Vec3f &supp,
const Vec3f &pt,
float angle)
{
using namespace Slic3r;
Vec3d D = (pt - supp).cast<double>();
double dot_sq = -D.z() * std::abs(-D.z());
return dot_sq <
D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle));
}
inline // TODO: should be in a cpp
std::optional<Vec3f> find_merge_pt(const Vec3f &A,
const Vec3f &B,
float critical_angle)
{
// The idea is that A and B both have their support cones. But searching
// for the intersection of these support cones is difficult and its enough
// to reduce this problem to 2D and search for the intersection of two
// rays that merge somewhere between A and B. The 2D plane is a vertical
// slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and
// the 2D X axis is determined by the XY direction of the AB vector.
//
// Z^
// | A *
// | . . B *
// | . . . .
// | . . . .
// | . x .
// -------------------> XY
// Determine the transformation matrix for the 2D projection:
Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f};
Vec3f dir = diff.normalized(); // TODO: avoid normalization
Eigen::Matrix<float, 2, 3> tr2D;
tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()};
tr2D.row(1) = Vec3f{0.f, 0.f, 1.f};
// Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can
// omit 'a', pretend that its the origin and use BA as the vector b.
Vec2f b = tr2D * (B - A);
// Get the square sine of the ray emanating from 'a' towards 'b'. This ray might
// exceed the allowed angle but that is corrected subsequently.
// The sign of the original sine is also needed, hence b.y is multiplied by
// abs(b.y)
float b_sqn = b.squaredNorm();
float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f;
// sine2 from 'b' to 'a' is the opposite of sine2 from a to b
float sin2sig_b = -sin2sig_a;
// Derive the allowed angles from the given critical angle.
// critical_angle is measured from the horizontal X axis.
// The rays need to go downwards which corresponds to negative angles
float sincrit = std::sin(critical_angle); // sine of the critical angle
float sin2crit = -sincrit * sincrit; // signed sine squared
sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays
sin2sig_b = std::min(sin2sig_b, sin2crit); //
float sin2_a = std::abs(sin2sig_a); // Get cosine squared values
float sin2_b = std::abs(sin2sig_b);
float cos2_a = 1.f - sin2_a;
float cos2_b = 1.f - sin2_b;
// Derive the new direction vectors. This is by square rooting the sin2
// and cos2 values and restoring the original signs
Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)};
Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)};
// Determine where two rays ([0, 0], Da), (b, Db) intersect.
// Based on
// https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect
// One ray is emanating from (0, 0) so the formula is simplified
double t1 = (Db.y() * b.x() - b.y() * Db.x()) /
(Da.x() * Db.y() - Da.y() * Db.x());
Vec2f mp = t1 * Da;
Vec3f Mp = A + tr2D.transpose() * mp;
return t1 >= 0.f ? Mp : Vec3f{};
}
}} // namespace Slic3r::sla }} // namespace Slic3r::sla
#endif // SLASUPPORTTREEUTILS_H #endif // SLASUPPORTTREEUTILS_H

View File

@ -0,0 +1,300 @@
#ifndef SUPPORTTREEUTILSLEGACY_HPP
#define SUPPORTTREEUTILSLEGACY_HPP
#include "SupportTreeUtils.hpp"
// Old functions are gathered here that are used in DefaultSupportTree
// to maintain functionality that was well tested.
namespace Slic3r { namespace sla {
// Helper function for pillar interconnection where pairs of already connected
// pillars should be checked for not to be processed again. This can be done
// in constant time with a set of hash values uniquely representing a pair of
// integers. The order of numbers within the pair should not matter, it has
// the same unique hash. The hash value has to have twice as many bits as the
// arguments need. If the same integral type is used for args and return val,
// make sure the arguments use only the half of the type's bit depth.
template<class I, class DoubleI = IntegerOnly<I>>
IntegerOnly<DoubleI> pairhash(I a, I b)
{
using std::ceil; using std::log2; using std::max; using std::min;
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
I g = min(a, b), l = max(a, b);
// Assume the hash will fit into the output variable
assert((g ? (ceil(log2(g))) : 0) <= shift);
assert((l ? (ceil(log2(l))) : 0) <= shift);
return (DoubleI(g) << shift) + l;
}
template<class Ex>
std::optional<DiffBridge> search_widening_path(Ex policy,
const SupportableMesh &sm,
const Vec3d &jp,
const Vec3d &dir,
double radius,
double new_radius)
{
double w = radius + 2 * sm.cfg.head_back_radius_mm;
double stopval = w + jp.z() - ground_level(sm);
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
auto [polar, azimuth] = dir_to_spheric(dir);
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
auto &[plr, azm, t] = input;
auto d = spheric_to_dir(plr, azm).normalized();
auto sd = sm.cfg.safety_distance(new_radius);
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
new_radius, t, sd)
.distance();
Beam beam{jp + t * d, d, new_radius};
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
if (ret > t && std::isinf(down))
ret += jp.z() - ground_level(sm);
return ret;
},
initvals({polar, azimuth, w}), // start with what we have
bounds({
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
{-PI, PI}, // azimuth can be a full search
{radius + sm.cfg.head_back_radius_mm,
fallback_ratio * sm.cfg.max_bridge_length_mm}
}));
if (oresult.score >= stopval) {
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
double t = std::get<2>(oresult.optimum);
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
}
return {};
}
// This is a proxy function for pillar creation which will mind the gap
// between the pad and the model bottom in zero elevation mode.
// 'pinhead_junctionpt' is the starting junction point which needs to be
// routed down. sourcedir is the allowed direction of an optional bridge
// between the jp junction and the final pillar.
template<class Ex>
std::pair<bool, long> create_ground_pillar(
Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Vec3d &pinhead_junctionpt,
const Vec3d &sourcedir,
double radius,
double end_radius,
long head_id = SupportTreeNode::ID_UNSET)
{
Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir;
long pillar_id = SupportTreeNode::ID_UNSET;
bool can_add_base = false, non_head = false;
double gndlvl = 0.; // The Z level where pedestals should be
double jp_gnd = 0.; // The lowest Z where a junction center can be
double gap_dist = 0.; // The gap distance between the model and the pad
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
(bool base_en = true)
{
can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.;
gndlvl = ground_level(sm);
if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
};
eval_limits();
// We are dealing with a mini pillar that's potentially too long
if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
{
std::optional<DiffBridge> diffbr =
search_widening_path(policy, sm, jp, dir, radius,
sm.cfg.head_back_radius_mm);
if (diffbr && diffbr->endp.z() > jp_gnd) {
auto &br = builder.add_diffbridge(*diffbr);
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
endp = diffbr->endp;
radius = diffbr->end_r;
builder.add_junction(endp, radius);
non_head = true;
dir = diffbr->get_dir();
eval_limits();
} else return {false, pillar_id};
}
if (sm.cfg.object_elevation_mm < EPSILON)
{
// get a suitable direction for the corrector bridge. It is the
// original sourcedir's azimuth but the polar angle is saturated to the
// configured bridge slope.
auto [polar, azimuth] = dir_to_spheric(dir);
polar = PI - sm.cfg.bridge_slope;
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
t = 0.;
double zd = endp.z() - jp_gnd;
double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
tmax = std::min(tmax, tmax2);
Vec3d nexp = endp;
double dlast = 0.;
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) &&
t < tmax)
{
t += radius;
nexp = endp + t * d;
}
if (dlast < gap_dist && can_add_base) {
nexp = endp;
t = 0.;
can_add_base = false;
eval_limits(can_add_base);
zd = endp.z() - jp_gnd;
tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
tmax = std::min(tmax, tmax2);
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) {
t += radius;
nexp = endp + t * d;
}
}
// Could not find a path to avoid the pad gap
if (dlast < gap_dist) return {false, pillar_id};
if (t > 0.) { // Need to make additional bridge
const Bridge& br = builder.add_bridge(endp, nexp, radius);
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
builder.add_junction(nexp, radius);
endp = nexp;
non_head = true;
}
}
Vec3d gp = to_floor(endp);
double h = endp.z() - gp.z();
pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) :
builder.add_pillar(gp, h, radius, end_radius);
if (can_add_base)
builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm,
sm.cfg.base_radius_mm);
return {true, pillar_id};
}
template<class Ex>
std::pair<bool, long> connect_to_ground(Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Junction &j,
const Vec3d &dir,
double end_r)
{
auto hjp = j.pos;
double r = j.r;
auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
double d = 0, tdown = 0;
t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm);
while (d < t &&
!std::isinf(tdown = beam_mesh_hit(policy, sm.emesh,
Beam{hjp + d * dir, DOWN, r, r2}, sd)
.distance())) {
d += r;
}
if(!std::isinf(tdown))
return {false, SupportTreeNode::ID_UNSET};
Vec3d endp = hjp + d * dir;
auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r);
if (ret.second >= 0) {
builder.add_bridge(hjp, endp, r);
builder.add_junction(endp, r);
}
return ret;
}
template<class Ex>
std::pair<bool, long> search_ground_route(Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Junction &j,
double end_radius,
const Vec3d &init_dir = DOWN)
{
double downdst = j.pos.z() - ground_level(sm);
auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius);
if (res.first)
return res;
// Optimize bridge direction:
// Straight path failed so we will try to search for a suitable
// direction out of the cavity.
auto [polar, azimuth] = dir_to_spheric(init_dir);
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg).stop_score(1e6));
solver.seed(0); // we want deterministic behavior
auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
auto &[plr, azm] = input;
Vec3d n = spheric_to_dir(plr, azm).normalized();
Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}};
return beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
},
initvals({polar, azimuth}), // let's start with what we have
bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} })
);
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius);
}
}} // namespace Slic3r::sla
#endif // SUPPORTTREEUTILSLEGACY_HPP

View File

@ -40,28 +40,63 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c)
scfg.enabled = c.supports_enable.getBool(); scfg.enabled = c.supports_enable.getBool();
scfg.tree_type = c.support_tree_type.value; scfg.tree_type = c.support_tree_type.value;
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
scfg.head_back_radius_mm = pillar_r;
scfg.head_fallback_radius_mm =
0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
scfg.head_width_mm = c.support_head_width.getFloat();
scfg.object_elevation_mm = is_zero_elevation(c) ?
0. : c.support_object_elevation.getFloat();
scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ;
scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat();
scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat();
scfg.pillar_connection_mode = c.support_pillar_connection_mode.value;
scfg.ground_facing_only = c.support_buildplate_only.getBool();
scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat();
scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat();
scfg.base_height_mm = c.support_base_height.getFloat();
scfg.pillar_base_safety_distance_mm =
c.support_base_safety_distance.getFloat() < EPSILON ?
scfg.safety_distance_mm : c.support_base_safety_distance.getFloat();
scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt()); switch(scfg.tree_type) {
case sla::SupportTreeType::Default: {
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
scfg.head_back_radius_mm = pillar_r;
scfg.head_fallback_radius_mm =
0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
scfg.head_width_mm = c.support_head_width.getFloat();
scfg.object_elevation_mm = is_zero_elevation(c) ?
0. : c.support_object_elevation.getFloat();
scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ;
scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat();
scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat();
scfg.pillar_connection_mode = c.support_pillar_connection_mode.value;
scfg.ground_facing_only = c.support_buildplate_only.getBool();
scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat();
scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat();
scfg.base_height_mm = c.support_base_height.getFloat();
scfg.pillar_base_safety_distance_mm =
c.support_base_safety_distance.getFloat() < EPSILON ?
scfg.safety_distance_mm : c.support_base_safety_distance.getFloat();
scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt());
scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat();
break;
}
case sla::SupportTreeType::Branching:
[[fallthrough]];
case sla::SupportTreeType::Organic:{
scfg.head_front_radius_mm = 0.5*c.branchingsupport_head_front_diameter.getFloat();
double pillar_r = 0.5 * c.branchingsupport_pillar_diameter.getFloat();
scfg.head_back_radius_mm = pillar_r;
scfg.head_fallback_radius_mm =
0.01 * c.branchingsupport_small_pillar_diameter_percent.getFloat() * pillar_r;
scfg.head_penetration_mm = c.branchingsupport_head_penetration.getFloat();
scfg.head_width_mm = c.branchingsupport_head_width.getFloat();
scfg.object_elevation_mm = is_zero_elevation(c) ?
0. : c.branchingsupport_object_elevation.getFloat();
scfg.bridge_slope = c.branchingsupport_critical_angle.getFloat() * PI / 180.0 ;
scfg.max_bridge_length_mm = c.branchingsupport_max_bridge_length.getFloat();
scfg.max_pillar_link_distance_mm = c.branchingsupport_max_pillar_link_distance.getFloat();
scfg.pillar_connection_mode = c.branchingsupport_pillar_connection_mode.value;
scfg.ground_facing_only = c.branchingsupport_buildplate_only.getBool();
scfg.pillar_widening_factor = c.branchingsupport_pillar_widening_factor.getFloat();
scfg.base_radius_mm = 0.5*c.branchingsupport_base_diameter.getFloat();
scfg.base_height_mm = c.branchingsupport_base_height.getFloat();
scfg.pillar_base_safety_distance_mm =
c.branchingsupport_base_safety_distance.getFloat() < EPSILON ?
scfg.safety_distance_mm : c.branchingsupport_base_safety_distance.getFloat();
scfg.max_bridges_on_pillar = unsigned(c.branchingsupport_max_bridges_on_pillar.getInt());
scfg.max_weight_on_model_support = c.branchingsupport_max_weight_on_model.getFloat();
break;
}
}
return scfg; return scfg;
} }
@ -822,6 +857,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|| opt_key == "supports_enable" || opt_key == "supports_enable"
|| opt_key == "support_tree_type" || opt_key == "support_tree_type"
|| opt_key == "support_object_elevation" || opt_key == "support_object_elevation"
|| opt_key == "branchingsupport_object_elevation"
|| opt_key == "pad_around_object" || opt_key == "pad_around_object"
|| opt_key == "pad_around_object_everywhere" || opt_key == "pad_around_object_everywhere"
|| opt_key == "slice_closing_radius" || opt_key == "slice_closing_radius"
@ -839,6 +875,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|| opt_key == "support_pillar_diameter" || opt_key == "support_pillar_diameter"
|| opt_key == "support_pillar_widening_factor" || opt_key == "support_pillar_widening_factor"
|| opt_key == "support_small_pillar_diameter_percent" || opt_key == "support_small_pillar_diameter_percent"
|| opt_key == "support_max_weight_on_model"
|| opt_key == "support_max_bridges_on_pillar" || opt_key == "support_max_bridges_on_pillar"
|| opt_key == "support_pillar_connection_mode" || opt_key == "support_pillar_connection_mode"
|| opt_key == "support_buildplate_only" || opt_key == "support_buildplate_only"
@ -848,6 +885,24 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|| opt_key == "support_max_bridge_length" || opt_key == "support_max_bridge_length"
|| opt_key == "support_max_pillar_link_distance" || opt_key == "support_max_pillar_link_distance"
|| opt_key == "support_base_safety_distance" || opt_key == "support_base_safety_distance"
|| opt_key == "branchingsupport_head_front_diameter"
|| opt_key == "branchingsupport_head_penetration"
|| opt_key == "branchingsupport_head_width"
|| opt_key == "branchingsupport_pillar_diameter"
|| opt_key == "branchingsupport_pillar_widening_factor"
|| opt_key == "branchingsupport_small_pillar_diameter_percent"
|| opt_key == "branchingsupport_max_weight_on_model"
|| opt_key == "branchingsupport_max_bridges_on_pillar"
|| opt_key == "branchingsupport_pillar_connection_mode"
|| opt_key == "branchingsupport_buildplate_only"
|| opt_key == "branchingsupport_base_diameter"
|| opt_key == "branchingsupport_base_height"
|| opt_key == "branchingsupport_critical_angle"
|| opt_key == "branchingsupport_max_bridge_length"
|| opt_key == "branchingsupport_max_pillar_link_distance"
|| opt_key == "branchingsupport_base_safety_distance"
|| opt_key == "pad_object_gap" || opt_key == "pad_object_gap"
) { ) {
steps.emplace_back(slaposSupportTree); steps.emplace_back(slaposSupportTree);

View File

@ -1,5 +1,6 @@
#include "SupportSpotsGenerator.hpp" #include "SupportSpotsGenerator.hpp"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "ExtrusionEntity.hpp" #include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp" #include "ExtrusionEntityCollection.hpp"
@ -7,6 +8,7 @@
#include "Line.hpp" #include "Line.hpp"
#include "Point.hpp" #include "Point.hpp"
#include "Polygon.hpp" #include "Polygon.hpp"
#include "PrincipalComponents2D.hpp"
#include "Print.hpp" #include "Print.hpp"
#include "PrintBase.hpp" #include "PrintBase.hpp"
#include "Tesselate.hpp" #include "Tesselate.hpp"
@ -117,13 +119,14 @@ public:
size_t to_cell_index(const Vec3i &cell_coords) const size_t to_cell_index(const Vec3i &cell_coords) const
{ {
#ifdef DETAILED_DEBUG_LOGS
assert(cell_coords.x() >= 0); assert(cell_coords.x() >= 0);
assert(cell_coords.x() < cell_count.x()); assert(cell_coords.x() < cell_count.x());
assert(cell_coords.y() >= 0); assert(cell_coords.y() >= 0);
assert(cell_coords.y() < cell_count.y()); assert(cell_coords.y() < cell_count.y());
assert(cell_coords.z() >= 0); assert(cell_coords.z() >= 0);
assert(cell_coords.z() < cell_count.z()); assert(cell_coords.z() < cell_count.z());
#endif
return cell_coords.z() * cell_count.x() * cell_count.y() + cell_coords.y() * cell_count.x() + cell_coords.x(); return cell_coords.z() * cell_count.x() * cell_count.y() + cell_coords.y() * cell_count.x() + cell_coords.x();
} }
@ -244,6 +247,7 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
const float flow_width = get_flow_width(layer_region, entity->role()); const float flow_width = get_flow_width(layer_region, entity->role());
// Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points, std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points,
prev_layer_lines, flow_width, prev_layer_lines, flow_width,
params.bridge_distance); params.bridge_distance);
@ -262,6 +266,7 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) :
ExtrusionLine{}; ExtrusionLine{};
// correctify the distance sign using slice polygons
float sign = (prev_layer_boundary.distance_from_lines<true>(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; float sign = (prev_layer_boundary.distance_from_lines<true>(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f;
curr_point.distance *= sign; curr_point.distance *= sign;
@ -297,84 +302,39 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
} }
} }
// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance
// none of the values is divided/normalized by area.
// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous)
// f(x,y) = x^2 for second moment of area
// and f(x,y) = x*y for second moment of area covariance
std::tuple<float, Vec2f, Vec2f, float> compute_triangle_moments_of_area(const Vec2f &a, const Vec2f &b, const Vec2f &c)
{
// based on the following guide:
// Denote the vertices of S by a, b, c. Then the map
// g:(u,v)↦a+u(ba)+v(ca) ,
// which in coordinates appears as
// g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1a1)+v(c1a1)=a2+u(b2a2)+v(c2a2) ,(1)
// obviously maps S bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain
// ∫Sf(x,y)d(x,y)=∫Sf(x(u,v),y(u,v))Jg(u,v) d(u,v) .
// In the case at hand the Jacobian determinant is a constant: From (1) we obtain
// Jg(u,v)=det[xuyuxvyv]=(b1a1)(c2a2)(c1a1)(b2a2) .
// Therefore we can write
// ∫Sf(x,y)d(x,y)=Jg∫10∫1u0f~(u,v) dv du ,
// where f~ denotes the pullback of f to S:
// f~(u,v):=f(x(u,v),y(u,v)) .
// Don't forget taking the absolute value of Jg!
float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y()));
// coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x)
// coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y)
// second moment of area for x: f(x, y) = x^2;
// f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form)
// result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du
// integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2)
Vec2f second_moment_of_area_xy = jacobian_determinant_abs *
(a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) +
a.cwiseProduct(b + c)) /
12.0f;
// second moment of area covariance : f(x, y) = x*y;
// f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form)
//(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2))
// == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2))
// intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv =
// 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2
// b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u -
// 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du =
// 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2)
// result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du
float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) *
(a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() +
b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y());
float area = jacobian_determinant_abs * 0.5f;
Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f;
return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance};
};
SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer)
{ {
SliceConnection connection; SliceConnection connection;
const LayerSlice &slice = layer->lslices_ex[slice_idx]; const LayerSlice &slice = layer->lslices_ex[slice_idx];
ExPolygon slice_poly = layer->lslices[slice_idx]; Polygons slice_polys = to_polygons(layer->lslices[slice_idx]);
BoundingBox slice_bb = get_extents(slice_polys);
const Layer *lower_layer = layer->lower_layer; const Layer *lower_layer = layer->lower_layer;
ExPolygons below_polys{}; ExPolygons below{};
for (const auto &link : slice.overlaps_below) { below_polys.push_back(lower_layer->lslices[link.slice_idx]); } for (const auto &link : slice.overlaps_below) { below.push_back(lower_layer->lslices[link.slice_idx]); }
ExPolygons overlap = intersection_ex({slice_poly}, below_polys); Polygons below_polys = to_polygons(below);
std::vector<Vec2f> triangles = triangulate_expolygons_2f(overlap); BoundingBox below_bb = get_extents(below_polys);
for (size_t idx = 0; idx < triangles.size(); idx += 3) {
auto [area, first_moment_of_area, second_moment_area, Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb),
second_moment_of_area_covariance] = compute_triangle_moments_of_area(triangles[idx], triangles[idx + 1], triangles[idx + 2]); ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb));
connection.area += area;
connection.centroid_accumulator += Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); for (const Polygon &poly : overlap) {
connection.second_moment_of_area_accumulator += second_moment_area; Vec2f p0 = unscaled(poly.first_point()).cast<float>();
connection.second_moment_of_area_covariance_accumulator += second_moment_of_area_covariance; for (size_t i = 2; i < poly.points.size(); i++) {
Vec2f p1 = unscaled(poly.points[i - 1]).cast<float>();
Vec2f p2 = unscaled(poly.points[i]).cast<float>();
float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f;
auto [area, first_moment_of_area, second_moment_area,
second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2);
connection.area += sign * area;
connection.centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area);
connection.second_moment_of_area_accumulator += sign * second_moment_area;
connection.second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance;
}
} }
return connection; return connection;
@ -973,6 +933,9 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
std::vector<ExtrusionLine> current_layer_lines; std::vector<ExtrusionLine> current_layer_lines;
for (const LayerRegion *layer_region : l->regions()) { for (const LayerRegion *layer_region : l->regions()) {
for (const ExtrusionEntity *extrusion : layer_region->perimeters().flatten().entities) { for (const ExtrusionEntity *extrusion : layer_region->perimeters().flatten().entities) {
if (!extrusion->role().is_external_perimeter()) continue;
Points extrusion_pts; Points extrusion_pts;
extrusion->collect_points(extrusion_pts); extrusion->collect_points(extrusion_pts);
float flow_width = get_flow_width(layer_region, extrusion->role()); float flow_width = get_flow_width(layer_region, extrusion->role());

View File

@ -18,6 +18,8 @@
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include <string_view>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
@ -26,6 +28,8 @@
namespace Slic3r::FFFTreeSupport namespace Slic3r::FFFTreeSupport
{ {
using namespace std::literals;
// or warning // or warning
// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL() // had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL()
#define error_level_not_in_cache error #define error_level_not_in_cache error
@ -336,7 +340,7 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI
return (*result).get(); return (*result).get();
if (m_precalculated) { if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Collision requested.", false); tree_supports_show_error("Not precalculated Collision requested."sv, false);
} }
const_cast<TreeModelVolumes*>(this)->calculateCollision(radius, layer_idx); const_cast<TreeModelVolumes*>(this)->calculateCollision(radius, layer_idx);
return getCollision(orig_radius, layer_idx, min_xy_dist); return getCollision(orig_radius, layer_idx, min_xy_dist);
@ -351,7 +355,7 @@ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerInde
return (*result).get(); return (*result).get();
if (m_precalculated) { if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Holefree Collision requested.", false); tree_supports_show_error("Not precalculated Holefree Collision requested."sv, false);
} }
const_cast<TreeModelVolumes*>(this)->calculateCollisionHolefree({ radius, layer_idx }); const_cast<TreeModelVolumes*>(this)->calculateCollisionHolefree({ radius, layer_idx });
return getCollisionHolefree(radius, layer_idx); return getCollisionHolefree(radius, layer_idx);
@ -375,10 +379,10 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI
if (m_precalculated) { if (m_precalculated) {
if (to_model) { if (to_model) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Avoidance(to model) requested.", false); tree_supports_show_error("Not precalculated Avoidance(to model) requested."sv, false);
} else { } else {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested.", false); tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested."sv, false);
} }
} }
const_cast<TreeModelVolumes*>(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model); const_cast<TreeModelVolumes*>(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model);
@ -396,7 +400,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L
return (*result).get(); return (*result).get();
if (m_precalculated) { if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Placeable areas requested.", false); tree_supports_show_error("Not precalculated Placeable areas requested."sv, false);
} }
const_cast<TreeModelVolumes*>(this)->calculatePlaceables(radius, layer_idx); const_cast<TreeModelVolumes*>(this)->calculatePlaceables(radius, layer_idx);
return getPlaceableAreas(orig_radius, layer_idx); return getPlaceableAreas(orig_radius, layer_idx);
@ -422,7 +426,7 @@ const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius,
tree_supports_show_error( tree_supports_show_error(
min_xy_dist ? min_xy_dist ?
"Not precalculated Wall restriction of minimum xy distance requested )." : "Not precalculated Wall restriction of minimum xy distance requested )." :
"Not precalculated Wall restriction requested )." "Not precalculated Wall restriction requested )."sv
, false); , false);
} }
const_cast<TreeModelVolumes*>(this)->calculateWallRestrictions({ radius, layer_idx }); const_cast<TreeModelVolumes*>(this)->calculateWallRestrictions({ radius, layer_idx });

View File

@ -58,6 +58,7 @@ enum class LineStatus
using LineInformation = std::vector<std::pair<Point, LineStatus>>; using LineInformation = std::vector<std::pair<Point, LineStatus>>;
using LineInformations = std::vector<LineInformation>; using LineInformations = std::vector<LineInformation>;
using namespace std::literals;
static inline void validate_range(const Point &pt) static inline void validate_range(const Point &pt)
{ {
@ -191,10 +192,9 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
} }
#endif #endif
//todo Remove! Only relevant for public BETA!
static bool inline g_showed_critical_error = false; static bool inline g_showed_critical_error = false;
static bool inline g_showed_performance_warning = false; static bool inline g_showed_performance_warning = false;
void tree_supports_show_error(std::string message, bool critical) void tree_supports_show_error(std::string_view message, bool critical)
{ // todo Remove! ONLY FOR PUBLIC BETA!! { // todo Remove! ONLY FOR PUBLIC BETA!!
#if defined(_WIN32) && defined(TREE_SUPPORT_SHOW_ERRORS) #if defined(_WIN32) && defined(TREE_SUPPORT_SHOW_ERRORS)
@ -204,7 +204,7 @@ void tree_supports_show_error(std::string message, bool critical)
bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning); bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning);
(critical ? g_showed_critical_error : g_showed_performance_warning) = true; (critical ? g_showed_critical_error : g_showed_performance_warning) = true;
if (show) if (show)
MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + message + "\n" + bugtype).c_str(), MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + std::string(message) + "\n" + bugtype).c_str(),
"Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // WIN32 #endif // WIN32
} }
@ -572,7 +572,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
// In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here...
BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance <<
") is smaller than 100"; ") is smaller than 100";
tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing.", true); tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing."sv, true);
if (next_distance > 2 * current_distance) if (next_distance > 2 * current_distance)
// This case should never happen, but better safe than sorry. // This case should never happen, but better safe than sorry.
break; break;
@ -759,7 +759,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret);
if (safe_step_size < 0 || last_step_offset_without_check < 0) { if (safe_step_size < 0 || last_step_offset_without_check < 0) {
BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!";
tree_supports_show_error("Negative offset distance... How did you manage this ?", true); tree_supports_show_error("Negative offset distance... How did you manage this ?"sv, true);
return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret);
} }
@ -951,7 +951,7 @@ static void generate_initial_areas(
bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE;
if (!mesh_config.support_rests_on_model && !to_bp) { if (!mesh_config.support_rests_on_model && !to_bp) {
BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point";
tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly.", true); tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true);
return; return;
} }
Polygons circle{ base_circle }; Polygons circle{ base_circle };
@ -1517,7 +1517,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di
if (area(check_layer_data) < tiny_area_threshold) { if (area(check_layer_data) < tiny_area_threshold) {
BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " <<
volumes.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance); volumes.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance);
tree_supports_show_error("Area lost catching up radius. May not cause visible malformation.", true); tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true);
} }
} }
} }
@ -1783,7 +1783,7 @@ static void increase_areas_one_layer(
"Parent " << &parent << ": Radius: " << config.getCollisionRadius(parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << "Parent " << &parent << ": Radius: " << config.getCollisionRadius(parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx <<
" Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist <<
" to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until;
tree_supports_show_error("Potentially lost branch!", true); tree_supports_show_error("Potentially lost branch!"sv, true);
} else } else
result = increase_single_area(volumes, config, settings, layer_idx, parent, result = increase_single_area(volumes, config, settings, layer_idx, parent,
settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer);
@ -2270,7 +2270,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp
if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) { if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) {
if (area(elem.areas.influence_areas) < tiny_area_threshold) { if (area(elem.areas.influence_areas) < tiny_area_threshold) {
BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1;
tree_supports_show_error("Insert error of area after bypassing merge.\n", true); tree_supports_show_error("Insert error of area after bypassing merge.\n"sv, true);
} }
// Move the area to output. // Move the area to output.
this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(elem.areas.influence_areas)); this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(elem.areas.influence_areas));
@ -2303,7 +2303,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp
Polygons new_area = safe_union(elem.areas.influence_areas); Polygons new_area = safe_union(elem.areas.influence_areas);
if (area(new_area) < tiny_area_threshold) { if (area(new_area) < tiny_area_threshold) {
BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate; BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate;
tree_supports_show_error("Insert error of area after merge.\n", true); tree_supports_show_error("Insert error of area after merge.\n"sv, true);
} }
this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(new_area)); this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(new_area));
} }
@ -2331,7 +2331,7 @@ static void set_points_on_areas(const SupportElement &elem, SupportElements *lay
// Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated.
if (! elem.state.result_on_layer_is_set()) { if (! elem.state.result_on_layer_is_set()) {
BOOST_LOG_TRIVIAL(error) << "Uninitialized support element"; BOOST_LOG_TRIVIAL(error) << "Uninitialized support element";
tree_supports_show_error("Uninitialized support element. A branch may be missing.\n", true); tree_supports_show_error("Uninitialized support element. A branch may be missing.\n"sv, true);
return; return;
} }
@ -2394,7 +2394,7 @@ static void set_to_model_contact_to_model_gracious(
// Could not find valid placement, even though it should exist => error handling // Could not find valid placement, even though it should exist => error handling
if (last_successfull_layer == nullptr) { if (last_successfull_layer == nullptr) {
BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << first_elem.state.layer_idx; BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << first_elem.state.layer_idx;
tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches."sv, true);
first_elem.state.to_model_gracious = false; first_elem.state.to_model_gracious = false;
set_to_model_contact_simple(first_elem); set_to_model_contact_simple(first_elem);
} else { } else {
@ -2494,7 +2494,7 @@ static void create_nodes_from_area(
if (elem.state.to_buildplate) { if (elem.state.to_buildplate) {
BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() << ") " BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() << ") "
"at target_height: " << elem.state.target_height << " layer: " << layer_idx; "at target_height: " << elem.state.target_height << " layer: " << layer_idx;
tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially.", true); tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially."sv, true);
} }
// we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set
elem.state.deleted = true; elem.state.deleted = true;

View File

@ -17,7 +17,7 @@
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#define TREE_SUPPORT_SHOW_ERRORS // #define TREE_SUPPORT_SHOW_ERRORS
#ifdef SLIC3R_TREESUPPORTS_PROGRESS #ifdef SLIC3R_TREESUPPORTS_PROGRESS
// The various stages of the process can be weighted differently in the progress bar. // The various stages of the process can be weighted differently in the progress bar.
@ -576,8 +576,7 @@ public:
} }
}; };
// todo Remove! ONLY FOR PUBLIC BETA!! void tree_supports_show_error(std::string_view message, bool critical);
void tree_supports_show_error(std::string message, bool critical);
} // namespace FFFTreeSupport } // namespace FFFTreeSupport

View File

@ -385,6 +385,8 @@ inline IntegerOnly<I, I> fast_round_up(double a)
return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5)); return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5));
} }
template<class T> using SamePair = std::pair<T, T>;
} // namespace Slic3r } // namespace Slic3r
#endif // _libslic3r_h_ #endif // _libslic3r_h_

View File

@ -374,23 +374,43 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
bool is_default_tree = treetype == sla::SupportTreeType::Default; bool is_default_tree = treetype == sla::SupportTreeType::Default;
bool is_branching_tree = treetype == sla::SupportTreeType::Branching; bool is_branching_tree = treetype == sla::SupportTreeType::Branching;
toggle_field("support_head_front_diameter", supports_en); toggle_field("support_tree_type", supports_en);
toggle_field("support_head_penetration", supports_en);
toggle_field("support_head_width", supports_en); toggle_field("support_head_front_diameter", supports_en && is_default_tree);
toggle_field("support_pillar_diameter", supports_en); toggle_field("support_head_penetration", supports_en && is_default_tree);
toggle_field("support_small_pillar_diameter_percent", supports_en); toggle_field("support_head_width", supports_en && is_default_tree);
toggle_field("support_pillar_diameter", supports_en && is_default_tree);
toggle_field("support_small_pillar_diameter_percent", supports_en && is_default_tree);
toggle_field("support_max_bridges_on_pillar", supports_en && is_default_tree); toggle_field("support_max_bridges_on_pillar", supports_en && is_default_tree);
toggle_field("support_pillar_connection_mode", supports_en && is_default_tree); toggle_field("support_pillar_connection_mode", supports_en && is_default_tree);
toggle_field("support_tree_type", supports_en); toggle_field("support_buildplate_only", supports_en && is_default_tree);
toggle_field("support_buildplate_only", supports_en); toggle_field("support_base_diameter", supports_en && is_default_tree);
toggle_field("support_base_height", supports_en && is_default_tree);
toggle_field("support_base_safety_distance", supports_en && is_default_tree);
toggle_field("support_critical_angle", supports_en && is_default_tree);
toggle_field("support_max_bridge_length", supports_en && is_default_tree);
toggle_field("support_enforcers_only", supports_en); toggle_field("support_enforcers_only", supports_en);
toggle_field("support_base_diameter", supports_en);
toggle_field("support_base_height", supports_en);
toggle_field("support_base_safety_distance", supports_en);
toggle_field("support_critical_angle", supports_en);
toggle_field("support_max_bridge_length", supports_en);
toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree); toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree);
toggle_field("support_pillar_widening_factor", supports_en && is_branching_tree); toggle_field("support_pillar_widening_factor", false);
toggle_field("support_max_weight_on_model", false);
toggle_field("branchingsupport_head_front_diameter", supports_en && is_branching_tree);
toggle_field("branchingsupport_head_penetration", supports_en && is_branching_tree);
toggle_field("branchingsupport_head_width", supports_en && is_branching_tree);
toggle_field("branchingsupport_pillar_diameter", supports_en && is_branching_tree);
toggle_field("branchingsupport_small_pillar_diameter_percent", supports_en && is_branching_tree);
toggle_field("branchingsupport_max_bridges_on_pillar", false);
toggle_field("branchingsupport_pillar_connection_mode", false);
toggle_field("branchingsupport_buildplate_only", supports_en && is_branching_tree);
toggle_field("branchingsupport_base_diameter", supports_en && is_branching_tree);
toggle_field("branchingsupport_base_height", supports_en && is_branching_tree);
toggle_field("branchingsupport_base_safety_distance", supports_en && is_branching_tree);
toggle_field("branchingsupport_critical_angle", supports_en && is_branching_tree);
toggle_field("branchingsupport_max_bridge_length", supports_en && is_branching_tree);
toggle_field("branchingsupport_max_pillar_link_distance", false);
toggle_field("branchingsupport_pillar_widening_factor", supports_en && is_branching_tree);
toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree);
toggle_field("support_points_density_relative", supports_en); toggle_field("support_points_density_relative", supports_en);
toggle_field("support_points_minimal_distance", supports_en); toggle_field("support_points_minimal_distance", supports_en);
@ -407,7 +427,8 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
bool zero_elev = config->opt_bool("pad_around_object") && pad_en; bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
toggle_field("support_object_elevation", supports_en && !zero_elev); toggle_field("support_object_elevation", supports_en && is_default_tree && !zero_elev);
toggle_field("branchingsupport_object_elevation", supports_en && is_branching_tree && !zero_elev);
toggle_field("pad_object_gap", zero_elev); toggle_field("pad_object_gap", zero_elev);
toggle_field("pad_around_object_everywhere", zero_elev); toggle_field("pad_around_object_everywhere", zero_elev);
toggle_field("pad_object_connector_stride", zero_elev); toggle_field("pad_object_connector_stride", zero_elev);

File diff suppressed because it is too large Load Diff

View File

@ -60,18 +60,25 @@ enum Technology {
T_ANY = ~0, T_ANY = ~0,
}; };
enum BundleLocation{
IN_VENDOR,
IN_ARCHIVE,
IN_RESOURCES
};
struct Bundle struct Bundle
{ {
std::unique_ptr<PresetBundle> preset_bundle; std::unique_ptr<PresetBundle> preset_bundle;
VendorProfile* vendor_profile{ nullptr }; VendorProfile* vendor_profile{ nullptr };
bool is_in_resources{ false }; //bool is_in_resources{ false };
BundleLocation location;
bool is_prusa_bundle{ false }; bool is_prusa_bundle{ false };
Bundle() = default; Bundle() = default;
Bundle(Bundle&& other); Bundle(Bundle&& other);
// Returns false if not loaded. Reason for that is logged as boost::log error. // Returns false if not loaded. Reason for that is logged as boost::log error.
bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false); bool load(fs::path source_path, BundleLocation location, bool is_prusa_bundle = false);
const std::string& vendor_id() const { return vendor_profile->id; } const std::string& vendor_id() const { return vendor_profile->id; }
}; };
@ -84,74 +91,8 @@ struct BundleMap : std::map<std::string /* = vendor ID */, Bundle>
const Bundle& prusa_bundle() const; const Bundle& prusa_bundle() const;
}; };
struct Materials struct Materials;
{
Technology technology;
// use vector for the presets to purpose of save of presets sorting in the bundle
std::vector<const Preset*> presets;
// String is alias of material, size_t number of compatible counters
std::vector<std::pair<std::string, size_t>> compatibility_counter;
std::set<std::string> types;
std::set<const Preset*> printers;
Materials(Technology technology) : technology(technology) {}
void push(const Preset *preset);
void add_printer(const Preset* preset);
void clear();
bool containts(const Preset *preset) const {
//return std::find(presets.begin(), presets.end(), preset) != presets.end();
return std::find_if(presets.begin(), presets.end(),
[preset](const Preset* element) { return element == preset; }) != presets.end();
}
bool get_omnipresent(const Preset* preset) {
return get_printer_counter(preset) == printers.size();
}
const std::vector<const Preset*> get_presets_by_alias(const std::string name) {
std::vector<const Preset*> ret_vec;
for (auto it = presets.begin(); it != presets.end(); ++it) {
if ((*it)->alias == name)
ret_vec.push_back((*it));
}
return ret_vec;
}
size_t get_printer_counter(const Preset* preset) {
for (auto it : compatibility_counter) {
if (it.first == preset->alias)
return it.second;
}
return 0;
}
const std::string& appconfig_section() const;
const std::string& get_type(const Preset *preset) const;
const std::string& get_vendor(const Preset *preset) const;
template<class F> void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) {
for (auto preset : presets) {
const Preset& prst = *(preset);
const Preset& prntr = *printer;
if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) &&
(type.empty() || get_type(preset) == type) &&
(vendor.empty() || get_vendor(preset) == vendor)) {
cb(preset);
}
}
}
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset *preset);
static const std::string& get_filament_vendor(const Preset *preset);
static const std::string& get_material_type(const Preset *preset);
static const std::string& get_material_vendor(const Preset *preset);
};
struct PrinterPickerEvent; struct PrinterPickerEvent;
@ -344,6 +285,10 @@ struct PageMaterials: ConfigWizardPage
std::string empty_printers_label; std::string empty_printers_label;
bool first_paint = { false }; bool first_paint = { false };
static const std::string EMPTY; static const std::string EMPTY;
static const std::string TEMPLATES;
// notify user first time they choose template profile
bool template_shown = { false };
bool notification_shown = { false };
int last_hovered_item = { -1 } ; int last_hovered_item = { -1 } ;
PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name); PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name);
@ -368,6 +313,82 @@ struct PageMaterials: ConfigWizardPage
virtual void on_activate() override; virtual void on_activate() override;
}; };
struct Materials
{
Technology technology;
// use vector for the presets to purpose of save of presets sorting in the bundle
std::vector<const Preset*> presets;
// String is alias of material, size_t number of compatible counters
std::vector<std::pair<std::string, size_t>> compatibility_counter;
std::set<std::string> types;
std::set<const Preset*> printers;
Materials(Technology technology) : technology(technology) {}
void push(const Preset* preset);
void add_printer(const Preset* preset);
void clear();
bool containts(const Preset* preset) const {
//return std::find(presets.begin(), presets.end(), preset) != presets.end();
return std::find_if(presets.begin(), presets.end(),
[preset](const Preset* element) { return element == preset; }) != presets.end();
}
bool get_omnipresent(const Preset* preset) {
return get_printer_counter(preset) == printers.size();
}
const std::vector<const Preset*> get_presets_by_alias(const std::string name) {
std::vector<const Preset*> ret_vec;
for (auto it = presets.begin(); it != presets.end(); ++it) {
if ((*it)->alias == name)
ret_vec.push_back((*it));
}
return ret_vec;
}
size_t get_printer_counter(const Preset* preset) {
for (auto it : compatibility_counter) {
if (it.first == preset->alias)
return it.second;
}
return 0;
}
const std::string& appconfig_section() const;
const std::string& get_type(const Preset* preset) const;
const std::string& get_vendor(const Preset* preset) const;
template<class F> void filter_presets(const Preset* printer, const std::string& printer_name, const std::string& type, const std::string& vendor, F cb) {
for (auto preset : presets) {
const Preset& prst = *(preset);
const Preset& prntr = *printer;
if (((printer == nullptr && printer_name == PageMaterials::EMPTY) || (printer != nullptr && is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor)))) &&
(type.empty() || get_type(preset) == type) &&
(vendor.empty() || get_vendor(preset) == vendor) &&
prst.vendor && !prst.vendor->templates_profile) {
cb(preset);
}
else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor && prst.vendor->templates_profile &&
(type.empty() || get_type(preset) == type) &&
(vendor.empty() || get_vendor(preset) == vendor)) {
cb(preset);
}
}
}
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset* preset);
static const std::string& get_filament_vendor(const Preset* preset);
static const std::string& get_material_type(const Preset* preset);
static const std::string& get_material_vendor(const Preset* preset);
};
struct PageCustom: ConfigWizardPage struct PageCustom: ConfigWizardPage
{ {
PageCustom(ConfigWizard *parent); PageCustom(ConfigWizard *parent);
@ -608,9 +629,11 @@ struct ConfigWizard::priv
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
bool any_fff_selected; // Used to decide whether to display Filaments page bool any_fff_selected; // Used to decide whether to display Filaments page
bool any_sla_selected; // Used to decide whether to display SLA Materials page bool any_sla_selected; // Used to decide whether to display SLA Materials page
bool custom_printer_selected { false }; bool custom_printer_selected { false }; // New custom printer is requested
bool custom_printer_in_bundle { false }; // Older custom printer already exists when wizard starts
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers) // Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers)
bool only_sla_mode { false }; bool only_sla_mode { false };
bool template_profile_selected { false }; // This bool has one purpose - to tell that template profile should be installed if its not (because it cannot be added to appconfig)
wxScrolledWindow *hscroll = nullptr; wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr; wxBoxSizer *hscroll_sizer = nullptr;

View File

@ -163,6 +163,8 @@ void FileGet::priv::get_perform()
m_stopped = true; m_stopped = true;
fclose(file); fclose(file);
cancel = true; cancel = true;
if (m_written == 0)
std::remove(m_tmp_path.string().c_str());
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED); wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED);
evt->SetInt(m_id); evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt); m_evt_handler->QueueEvent(evt);

View File

@ -808,6 +808,7 @@ void GUI_App::post_init()
// Configuration is not compatible and reconfigure was refused by the user. Application is closing. // Configuration is not compatible and reconfigure was refused by the user. Application is closing.
return; return;
CallAfter([this] { CallAfter([this] {
// preset_updater->sync downloads profile updates on background so it must begin after config wizard finished.
bool cw_showed = this->config_wizard_startup(); bool cw_showed = this->config_wizard_startup();
this->preset_updater->sync(preset_bundle); this->preset_updater->sync(preset_bundle);
this->app_version_check(false); this->app_version_check(false);
@ -1248,7 +1249,7 @@ bool GUI_App::on_init_inner()
std::string evt_string = into_u8(evt.GetString()); std::string evt_string = into_u8(evt.GetString());
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) {
auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable);
this->plater_->get_notification_manager()->push_notification( notif_type this->plater_->get_notification_manager()->push_version_notification( notif_type
, NotificationManager::NotificationLevel::ImportantNotificationLevel , NotificationManager::NotificationLevel::ImportantNotificationLevel
, Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string)
, _u8L("See Releases page.") , _u8L("See Releases page.")
@ -1281,7 +1282,8 @@ bool GUI_App::on_init_inner()
associate_gcode_files(); associate_gcode_files();
#endif // __WXMSW__ #endif // __WXMSW__
} }
std::string delayed_error_load_presets;
// Suppress the '- default -' presets. // Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
try { try {
@ -1290,7 +1292,7 @@ bool GUI_App::on_init_inner()
// installation of a compatible system preset, thus nullifying the system preset substitutions. // installation of a compatible system preset, thus nullifying the system preset substitutions.
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
show_error(nullptr, ex.what()); delayed_error_load_presets = ex.what();
} }
#ifdef WIN32 #ifdef WIN32
@ -1307,6 +1309,9 @@ bool GUI_App::on_init_inner()
if (scrn && is_editor()) if (scrn && is_editor())
scrn->SetText(_L("Preparing settings tabs") + dots); scrn->SetText(_L("Preparing settings tabs") + dots);
if (!delayed_error_load_presets.empty())
show_error(nullptr, delayed_error_load_presets);
mainframe = new MainFrame(); mainframe = new MainFrame();
// hide settings tabs after first Layout // hide settings tabs after first Layout
if (is_editor()) if (is_editor())
@ -3007,6 +3012,9 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
if (reason == ConfigWizard::RR_USER) { if (reason == ConfigWizard::RR_USER) {
// Cancel sync before starting wizard to prevent two downloads at same time
preset_updater->cancel_sync();
preset_updater->update_index_db();
if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED)
return false; return false;
} }
@ -3189,6 +3197,7 @@ bool GUI_App::check_updates(const bool verbose)
{ {
PresetUpdater::UpdateResult updater_result; PresetUpdater::UpdateResult updater_result;
try { try {
preset_updater->update_index_db();
updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close(); mainframe->Close();
@ -3300,6 +3309,19 @@ void GUI_App::on_version_read(wxCommandEvent& evt)
return; return;
} }
if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) {
if (m_app_updater->get_triggered_by_user())
{
std::string text = (*Semver::parse(into_u8(evt.GetString())) == Semver())
? Slic3r::format(_u8L("Check for application update has failed."))
: Slic3r::format(_u8L("No new version is available. Latest release version is %1%."), evt.GetString());
this->plater_->get_notification_manager()->push_version_notification(NotificationType::NoNewReleaseAvailable
, NotificationManager::NotificationLevel::RegularNotificationLevel
, text
, std::string()
, std::function<bool(wxEvtHandler*)>()
);
}
return; return;
} }
// notification // notification

View File

@ -558,8 +558,8 @@ void MenuFactory::append_menu_items_add_volume(MenuType menu_type)
menu->Destroy(range_id); menu->Destroy(range_id);
if (wxGetApp().get_mode() == comSimple) { if (wxGetApp().get_mode() == comSimple) {
append_menu_item_add_text(menu, ModelVolumeType::MODEL_PART, false); //append_menu_item_add_text(menu, ModelVolumeType::MODEL_PART, false);
append_menu_item_add_text(menu, ModelVolumeType::NEGATIVE_VOLUME, false); //append_menu_item_add_text(menu, ModelVolumeType::NEGATIVE_VOLUME, false);
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "", append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "",
[](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); }, [](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); },

View File

@ -2480,6 +2480,9 @@ bool ObjectList::is_splittable(bool to_objects)
return false; return false;
} }
if (wxGetApp().get_mode() == comSimple)
return false; // suppress to split to parts for simple mode
ModelVolume* volume; ModelVolume* volume;
if (!get_volume_by_item(item, volume) || !volume) if (!get_volume_by_item(item, volume) || !volume)
return false; return false;

View File

@ -47,7 +47,6 @@
#define ALLOW_ADD_FONT_BY_OS_SELECTOR #define ALLOW_ADD_FONT_BY_OS_SELECTOR
#define SHOW_WX_FONT_DESCRIPTOR // OS specific descriptor | file path --> in edit style <tree header> #define SHOW_WX_FONT_DESCRIPTOR // OS specific descriptor | file path --> in edit style <tree header>
#define SHOW_FONT_FILE_PROPERTY // ascent, descent, line gap, cache --> in advanced <tree header> #define SHOW_FONT_FILE_PROPERTY // ascent, descent, line gap, cache --> in advanced <tree header>
#define SHOW_FONT_COUNT // count of enumerated font --> in font combo box
#define SHOW_CONTAIN_3MF_FIX // when contain fix matrix --> show gray '3mf' next to close button #define SHOW_CONTAIN_3MF_FIX // when contain fix matrix --> show gray '3mf' next to close button
#define SHOW_OFFSET_DURING_DRAGGING // when drag with text over surface visualize used center #define SHOW_OFFSET_DURING_DRAGGING // when drag with text over surface visualize used center
#define SHOW_IMGUI_ATLAS #define SHOW_IMGUI_ATLAS
@ -403,7 +402,7 @@ Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model)
if (!fix.has_value()) if (!fix.has_value())
return res; return res;
return res * (*fix); return res * fix->inverse();
} }
Transform3d priv::world_matrix(const Selection &selection) Transform3d priv::world_matrix(const Selection &selection)
@ -861,6 +860,9 @@ void GLGizmoEmboss::on_set_state()
if (m_volume == nullptr) { if (m_volume == nullptr) {
// reopen gizmo when new object is created // reopen gizmo when new object is created
GLGizmoBase::m_state = GLGizmoBase::Off; GLGizmoBase::m_state = GLGizmoBase::Off;
if (wxGetApp().get_mode() == comSimple)
// It's impossible to add a part in simple mode
return;
// start creating new object // start creating new object
create_volume(ModelVolumeType::MODEL_PART); create_volume(ModelVolumeType::MODEL_PART);
} }
@ -1830,6 +1832,9 @@ void GLGizmoEmboss::init_font_name_texture() {
face.cancel = nullptr; face.cancel = nullptr;
face.is_created = nullptr; face.is_created = nullptr;
} }
// Prepare filtration cache
m_face_names.hide = std::vector<bool>(m_face_names.faces.size(), {false});
} }
void GLGizmoEmboss::draw_font_preview(FaceName& face, bool is_visible) void GLGizmoEmboss::draw_font_preview(FaceName& face, bool is_visible)
@ -1960,13 +1965,60 @@ void GLGizmoEmboss::draw_font_list()
}; };
} }
// Code
const char *popup_id = "##font_list_popup";
const char *input_id = "##font_list_input";
ImGui::SetNextItemWidth(m_gui_cfg->input_width); ImGui::SetNextItemWidth(m_gui_cfg->input_width);
if (ImGui::BeginCombo("##font_selector", selected)) { ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_CharsUppercase;
if (!m_face_names.is_init) init_face_names();
if (m_face_names.texture_id == 0) init_font_name_texture(); // change color of hint to normal text
bool is_popup_open = ImGui::IsPopupOpen(popup_id);
if (!is_popup_open)
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyleColorVec4(ImGuiCol_Text));
if (ImGui::InputTextWithHint(input_id, selected, &m_face_names.search, input_flags)) {
// update filtration result
m_face_names.hide = std::vector<bool>(m_face_names.faces.size(), {false});
for (FaceName &face : m_face_names.faces) {
size_t index = &face - &m_face_names.faces.front();
std::string name(face.wx_name.ToUTF8().data());
std::transform(name.begin(), name.end(), name.begin(), ::toupper);
// It should use C++ 20 feature https://en.cppreference.com/w/cpp/string/basic_string/starts_with
bool start_with = boost::starts_with(name, m_face_names.search);
m_face_names.hide[index] = !start_with;
}
}
if (!is_popup_open)
ImGui::PopStyleColor(); // revert changes for hint color
const bool is_input_text_active = ImGui::IsItemActive();
// is_input_text_activated
if (ImGui::IsItemActivated())
ImGui::OpenPopup(popup_id);
ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMax().y));
ImGui::SetNextWindowSize({2*m_gui_cfg->input_width, ImGui::GetTextLineHeight()*10});
ImGuiWindowFlags popup_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_ChildWindow;
if (ImGui::BeginPopup(popup_id, popup_flags))
{
bool set_selection_focus = false;
if (!m_face_names.is_init) {
init_face_names();
set_selection_focus = true;
}
if (m_face_names.texture_id == 0)
init_font_name_texture();
for (FaceName &face : m_face_names.faces) { for (FaceName &face : m_face_names.faces) {
const wxString &wx_face_name = face.wx_name; const wxString &wx_face_name = face.wx_name;
size_t index = &face - &m_face_names.faces.front(); size_t index = &face - &m_face_names.faces.front();
// Filter for face names
if (m_face_names.hide[index])
continue;
ImGui::PushID(index); ImGui::PushID(index);
ScopeGuard sg([]() { ImGui::PopID(); }); ScopeGuard sg([]() { ImGui::PopID(); });
bool is_selected = (actual_face_name == wx_face_name); bool is_selected = (actual_face_name == wx_face_name);
@ -1978,24 +2030,32 @@ void GLGizmoEmboss::draw_font_list()
wxMessageBox(GUI::format(_L("Font face \"%1%\" can't be selected."), wx_face_name)); wxMessageBox(GUI::format(_L("Font face \"%1%\" can't be selected."), wx_face_name));
} }
} }
// tooltip as full name of font face
if (ImGui::IsItemHovered()) if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", wx_face_name.ToUTF8().data()); ImGui::SetTooltip("%s", wx_face_name.ToUTF8().data());
if (is_selected) ImGui::SetItemDefaultFocus();
draw_font_preview(face, ImGui::IsItemVisible());
}
#ifdef SHOW_FONT_COUNT
ImGui::TextColored(ImGuiWrapper::COL_GREY_LIGHT, "Count %d",
static_cast<int>(m_face_names.names.size()));
#endif // SHOW_FONT_COUNT
ImGui::EndCombo();
} else if (m_face_names.is_init) {
// Just one after close combo box
// free texture and set id to zero
// on first draw set focus on selected font
if (set_selection_focus && is_selected)
ImGui::SetScrollHereY();
draw_font_preview(face, ImGui::IsItemVisible());
}
if (!ImGui::IsWindowFocused() ||
!is_input_text_active && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape))) {
// closing of popup
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
} else if (m_face_names.is_init) {
// Just one after close combo box
// free texture and set id to zero
m_face_names.is_init = false; m_face_names.is_init = false;
m_face_names.search.clear();
m_face_names.hide.clear();
// cancel all process for generation of texture // cancel all process for generation of texture
for (FaceName &face : m_face_names.faces) for (FaceName &face : m_face_names.faces)
if (face.cancel != nullptr) face.cancel->store(true); if (face.cancel != nullptr)
face.cancel->store(true);
glsafe(::glDeleteTextures(1, &m_face_names.texture_id)); glsafe(::glDeleteTextures(1, &m_face_names.texture_id));
m_face_names.texture_id = 0; m_face_names.texture_id = 0;
} }

View File

@ -270,6 +270,10 @@ private:
// hash created from enumerated font from OS // hash created from enumerated font from OS
// check when new font was installed // check when new font was installed
size_t hash = 0; size_t hash = 0;
// filtration pattern
std::string search = "";
std::vector<bool> hide; // result of filtration
} m_face_names; } m_face_names;
static bool store(const Facenames &facenames); static bool store(const Facenames &facenames);
static bool load(Facenames &facenames); static bool load(Facenames &facenames);

View File

@ -517,14 +517,6 @@ bool GLGizmoFdmSupports::has_backend_supports()
return done; return done;
} }
void GLGizmoFdmSupports::reslice_FDM_supports(bool postpone_error_messages) const {
wxGetApp().CallAfter(
[this, postpone_error_messages]() {
wxGetApp().plater()->reslice_FFF_until_step(posSupportSpotsSearch,
*m_c->selection_info()->model_object(), postpone_error_messages);
});
}
void GLGizmoFdmSupports::auto_generate() void GLGizmoFdmSupports::auto_generate()
{ {
ModelObject *mo = m_c->selection_info()->model_object(); ModelObject *mo = m_c->selection_info()->model_object();
@ -549,8 +541,10 @@ void GLGizmoFdmSupports::auto_generate()
} }
} }
this->waiting_for_autogenerated_supports = true; wxGetApp().CallAfter([this]() {
wxGetApp().CallAfter([this]() { reslice_FDM_supports(); }); wxGetApp().plater()->reslice_FFF_until_step(posSupportSpotsSearch, *m_c->selection_info()->model_object(), false);
this->waiting_for_autogenerated_supports = true;
});
} }
} }

View File

@ -44,7 +44,6 @@ private:
bool waiting_for_autogenerated_supports = false; bool waiting_for_autogenerated_supports = false;
bool has_backend_supports(); bool has_backend_supports();
void reslice_FDM_supports(bool postpone_error_messages = false) const;
void auto_generate(); void auto_generate();
void apply_data_from_backend(); void apply_data_from_backend();
}; };

View File

@ -2246,6 +2246,29 @@ void NotificationManager::push_simplify_suggestion_notification(const std::stri
notification->object_id = object_id; notification->object_id = object_id;
push_notification_data(std::move(notification), 0); push_notification_data(std::move(notification), 0);
} }
void NotificationManager::push_version_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext, std::function<bool(wxEvtHandler*)> callback)
{
assert (type == NotificationType::NewAlphaAvailable
|| type == NotificationType::NewBetaAvailable
|| type == NotificationType::NoNewReleaseAvailable);
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
// NoNewReleaseAvailable must not show if alfa / beta is on.
NotificationType nttype = notification->get_type();
if (type == NotificationType::NoNewReleaseAvailable
&& (notification->get_type() == NotificationType::NewAlphaAvailable
|| notification->get_type() == NotificationType::NewBetaAvailable)) {
return;
}
// NoNewReleaseAvailable must close if alfa / beta is being push.
if (notification->get_type() == NotificationType::NoNewReleaseAvailable
&& (type == NotificationType::NewAlphaAvailable
|| type == NotificationType::NewBetaAvailable)) {
notification->close();
}
}
push_notification(type, level, text, hypertext, callback);
}
void NotificationManager::close_notification_of_type(const NotificationType type) void NotificationManager::close_notification_of_type(const NotificationType type)
{ {
for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) { for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {

View File

@ -56,6 +56,7 @@ enum class NotificationType
// Like NewAppAvailable but with text and link for alpha / bet release // Like NewAppAvailable but with text and link for alpha / bet release
NewAlphaAvailable, NewAlphaAvailable,
NewBetaAvailable, NewBetaAvailable,
NoNewReleaseAvailable,
// Notification on the start of PrusaSlicer, when updates of system profiles are detected. // Notification on the start of PrusaSlicer, when updates of system profiles are detected.
// Contains a hyperlink to execute installation of the new system profiles. // Contains a hyperlink to execute installation of the new system profiles.
PresetUpdateAvailable, PresetUpdateAvailable,
@ -191,6 +192,9 @@ public:
// Object warning with ObjectID, closes when object is deleted. ID used is of object not print like in slicing warning. // Object warning with ObjectID, closes when object is deleted. ID used is of object not print like in slicing warning.
void push_simplify_suggestion_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "", void push_simplify_suggestion_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "",
std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>()); std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>());
// Could be either NewAlphaAvailable, NewBetaAvailable or NoNewReleaseAvailable - this function only makes sure only 1 is visible.
void push_version_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext,
std::function<bool(wxEvtHandler*)> callback);
// Close object warnings, whose ObjectID is not in the list. // Close object warnings, whose ObjectID is not in the list.
// living_oids is expected to be sorted. // living_oids is expected to be sorted.
void remove_simplify_suggestion_of_released_objects(const std::vector<ObjectID>& living_oids); void remove_simplify_suggestion_of_released_objects(const std::vector<ObjectID>& living_oids);

View File

@ -553,12 +553,12 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
const bool supports_enable = selection == _("None") ? false : true; const bool supports_enable = selection == _("None") ? false : true;
new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable)); new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable));
new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(false)); std::string treetype = get_sla_suptree_prefix(new_conf);
if (selection == _("Everywhere")) if (selection == _("Everywhere"))
new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false)); new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(false));
else if (selection == _("Support on build plate only")) else if (selection == _("Support on build plate only"))
new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true)); new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(true));
else if (selection == _("For support enforcers only")) { else if (selection == _("For support enforcers only")) {
new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(true)); new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(true));
} }
@ -2643,7 +2643,6 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
if (model.looks_like_multipart_object()) { if (model.looks_like_multipart_object()) {
//wxMessageDialog msg_dlg(q, _L(
MessageDialog msg_dlg(q, _L( MessageDialog msg_dlg(q, _L(
"This file contains several objects positioned at multiple heights.\n" "This file contains several objects positioned at multiple heights.\n"
"Instead of considering them as multiple objects, should \n" "Instead of considering them as multiple objects, should \n"
@ -2654,10 +2653,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
} }
} }
else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
//wxMessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n",
MessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n", MessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n",
_L("Detected advanced data"), wxICON_WARNING | wxYES | wxNO); _L("Detected advanced data"), wxICON_WARNING | wxYES | wxCANCEL);
if (msg_dlg.ShowModal() == wxID_YES) { if (msg_dlg.ShowModal() == wxID_YES) {
Slic3r::GUI::wxGetApp().save_mode(comAdvanced); Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
view3D->set_as_dirty(); view3D->set_as_dirty();
@ -6147,7 +6145,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
break; break;
} }
case ProjectDropDialog::LoadType::LoadGeometry: { case ProjectDropDialog::LoadType::LoadGeometry: {
Plater::TakeSnapshot snapshot(this, _L("Import Object")); // Plater::TakeSnapshot snapshot(this, _L("Import Object"));
load_files({ *it }, true, false); load_files({ *it }, true, false);
break; break;
} }

View File

@ -301,6 +301,11 @@ void PreferencesDialog::build()
L("Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available."), L("Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available."),
app_config->get("no_defaults") == "1"); app_config->get("no_defaults") == "1");
append_bool_option(m_optgroup_general, "no_templates",
L("Suppress \" Template \" filament presets"),
L("Suppress \" Template \" filament presets in configuration wizard and sidebar visibility."),
app_config->get("no_templates") == "1");
append_bool_option(m_optgroup_general, "show_incompatible_presets", append_bool_option(m_optgroup_general, "show_incompatible_presets",
L("Show incompatible print and filament presets"), L("Show incompatible print and filament presets"),
L("When checked, the print and filament presets are shown in the preset editor " L("When checked, the print and filament presets are shown in the preset editor "
@ -692,6 +697,8 @@ void PreferencesDialog::accept(wxEvent&)
DesktopIntegrationDialog::perform_desktop_integration(true); DesktopIntegrationDialog::perform_desktop_integration(true);
#endif // __linux__ #endif // __linux__
bool update_filament_sidebar = (m_values.find("no_templates") != m_values.end());
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" }; std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" };
for (const std::string& option : options_to_recreate_GUI) { for (const std::string& option : options_to_recreate_GUI) {
@ -761,6 +768,9 @@ void PreferencesDialog::accept(wxEvent&)
wxGetApp().update_ui_from_settings(); wxGetApp().update_ui_from_settings();
clear_cache(); clear_cache();
if (update_filament_sidebar)
wxGetApp().plater()->sidebar().update_presets(Preset::Type::TYPE_FILAMENT);
} }
void PreferencesDialog::revert(wxEvent&) void PreferencesDialog::revert(wxEvent&)

View File

@ -798,7 +798,7 @@ void PlaterPresetComboBox::show_edit_menu()
wxString PlaterPresetComboBox::get_preset_name(const Preset& preset) wxString PlaterPresetComboBox::get_preset_name(const Preset& preset)
{ {
std::string name = preset.alias.empty() ? preset.name : preset.alias; std::string name = preset.alias.empty() ? preset.name : (preset.vendor && preset.vendor->templates_profile ? preset.name : preset.alias);
return from_u8(name + suffix(preset)); return from_u8(name + suffix(preset));
} }
@ -836,6 +836,7 @@ void PlaterPresetComboBox::update()
null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width; null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width;
std::map<wxString, wxBitmapBundle*> nonsys_presets; std::map<wxString, wxBitmapBundle*> nonsys_presets;
std::map<wxString, wxBitmapBundle*> template_presets;
wxString selected_user_preset; wxString selected_user_preset;
wxString tooltip; wxString tooltip;
@ -883,10 +884,18 @@ void PlaterPresetComboBox::update()
const std::string name = preset.alias.empty() ? preset.name : preset.alias; const std::string name = preset.alias.empty() ? preset.name : preset.alias;
if (preset.is_default || preset.is_system) { if (preset.is_default || preset.is_system) {
Append(get_preset_name(preset), *bmp); if (preset.vendor && preset.vendor->templates_profile) {
validate_selection(is_selected); template_presets.emplace(get_preset_name(preset), bmp);
if (is_selected) if (is_selected) {
tooltip = from_u8(preset.name); selected_user_preset = get_preset_name(preset);
tooltip = from_u8(preset.name);
}
} else {
Append(get_preset_name(preset), *bmp);
validate_selection(is_selected);
if (is_selected)
tooltip = from_u8(preset.name);
}
} }
else else
{ {
@ -899,6 +908,8 @@ void PlaterPresetComboBox::update()
if (i + 1 == m_collection->num_default_presets()) if (i + 1 == m_collection->num_default_presets())
set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); set_label_marker(Append(separator(L("System presets")), NullBitmapBndl()));
} }
if (!nonsys_presets.empty()) if (!nonsys_presets.empty())
{ {
set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); set_label_marker(Append(separator(L("User presets")), NullBitmapBndl()));
@ -908,6 +919,15 @@ void PlaterPresetComboBox::update()
} }
} }
const AppConfig* app_config = wxGetApp().app_config;
if (!template_presets.empty() && app_config->get("no_templates") == "0") {
set_label_marker(Append(separator(L("Template presets")), wxNullBitmap));
for (std::map<wxString, wxBitmapBundle*>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) {
Append(it->first, *it->second);
validate_selection(it->first == selected_user_preset);
}
}
if (m_type == Preset::TYPE_PRINTER) if (m_type == Preset::TYPE_PRINTER)
{ {
// add Physical printers, if any exists // add Physical printers, if any exists
@ -1046,6 +1066,8 @@ void TabPresetComboBox::update()
const std::deque<Preset>& presets = m_collection->get_presets(); const std::deque<Preset>& presets = m_collection->get_presets();
std::map<wxString, std::pair<wxBitmapBundle*, bool>> nonsys_presets; std::map<wxString, std::pair<wxBitmapBundle*, bool>> nonsys_presets;
std::map<wxString, std::pair<wxBitmapBundle*, bool>> template_presets;
wxString selected = ""; wxString selected = "";
if (!presets.front().is_visible) if (!presets.front().is_visible)
set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); set_label_marker(Append(separator(L("System presets")), NullBitmapBndl()));
@ -1078,11 +1100,19 @@ void TabPresetComboBox::update()
auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default);
assert(bmp); assert(bmp);
if (preset.is_default || preset.is_system) { if (preset.is_default || preset.is_system) {
int item_id = Append(get_preset_name(preset), *bmp); if (preset.vendor && preset.vendor->templates_profile) {
if (!is_enabled) template_presets.emplace(get_preset_name(preset), std::pair<wxBitmapBundle*, bool>(bmp, is_enabled));
set_label_marker(item_id, LABEL_ITEM_DISABLED); if (i == idx_selected)
validate_selection(i == idx_selected); selected = get_preset_name(preset);
} else {
int item_id = Append(get_preset_name(preset), *bmp);
if (!is_enabled)
set_label_marker(item_id, LABEL_ITEM_DISABLED);
validate_selection(i == idx_selected);
}
} }
else else
{ {
@ -1094,6 +1124,7 @@ void TabPresetComboBox::update()
if (i + 1 == m_collection->num_default_presets()) if (i + 1 == m_collection->num_default_presets())
set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); set_label_marker(Append(separator(L("System presets")), NullBitmapBndl()));
} }
if (!nonsys_presets.empty()) if (!nonsys_presets.empty())
{ {
set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); set_label_marker(Append(separator(L("User presets")), NullBitmapBndl()));
@ -1105,7 +1136,19 @@ void TabPresetComboBox::update()
validate_selection(it->first == selected); validate_selection(it->first == selected);
} }
} }
const AppConfig* app_config = wxGetApp().app_config;
if (!template_presets.empty() && app_config->get("no_templates") == "0") {
set_label_marker(Append(separator(L("Template presets")), wxNullBitmap));
for (std::map<wxString, std::pair<wxBitmapBundle*, bool>>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) {
int item_id = Append(it->first, *it->second.first);
bool is_enabled = it->second.second;
if (!is_enabled)
set_label_marker(item_id, LABEL_ITEM_DISABLED);
validate_selection(it->first == selected);
}
}
if (m_type == Preset::TYPE_PRINTER) if (m_type == Preset::TYPE_PRINTER)
{ {
// add Physical printers, if any exists // add Physical printers, if any exists

View File

@ -285,17 +285,17 @@ void SavePresetDialog::Item::Enable(bool enable /*= true*/)
// SavePresetDialog // SavePresetDialog
//----------------------------------------------- //-----------------------------------------------
SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix) SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix, bool template_filament)
: DPIDialog(parent, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) : DPIDialog(parent, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER)
{ {
build(std::vector<Preset::Type>{type}, suffix); build(std::vector<Preset::Type>{type}, suffix, template_filament);
} }
SavePresetDialog::SavePresetDialog(wxWindow* parent, std::vector<Preset::Type> types, std::string suffix, PresetBundle* preset_bundle/* = nullptr*/) SavePresetDialog::SavePresetDialog(wxWindow* parent, std::vector<Preset::Type> types, std::string suffix, bool template_filament/* =false*/, PresetBundle* preset_bundle/* = nullptr*/)
: DPIDialog(parent, wxID_ANY, _L("Save presets"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER), : DPIDialog(parent, wxID_ANY, _L("Save presets"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER),
m_preset_bundle(preset_bundle) m_preset_bundle(preset_bundle)
{ {
build(types, suffix); build(types, suffix, template_filament);
} }
SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention) SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention)
@ -312,7 +312,7 @@ SavePresetDialog::~SavePresetDialog()
} }
} }
void SavePresetDialog::build(std::vector<Preset::Type> types, std::string suffix) void SavePresetDialog::build(std::vector<Preset::Type> types, std::string suffix, bool template_filament)
{ {
#if defined(__WXMSW__) #if defined(__WXMSW__)
// ys_FIXME! temporary workaround for correct font scaling // ys_FIXME! temporary workaround for correct font scaling
@ -341,6 +341,15 @@ void SavePresetDialog::build(std::vector<Preset::Type> types, std::string suffix
btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(enable_ok_btn()); }); btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(enable_ok_btn()); });
topSizer->Add(m_presets_sizer, 0, wxEXPAND | wxALL, BORDER_W); topSizer->Add(m_presets_sizer, 0, wxEXPAND | wxALL, BORDER_W);
// Add checkbox for Template filament saving
if (template_filament && types.size() == 1 && *types.begin() == Preset::Type::TYPE_FILAMENT) {
m_template_filament_checkbox = new wxCheckBox(this, wxID_ANY, _L("Save as profile derived from current printer only."));
wxBoxSizer* check_sizer = new wxBoxSizer(wxVERTICAL);
check_sizer->Add(m_template_filament_checkbox);
topSizer->Add(check_sizer, 0, wxEXPAND | wxALL, BORDER_W);
}
topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W);
SetSizer(topSizer); SetSizer(topSizer);
@ -371,6 +380,15 @@ std::string SavePresetDialog::get_name(Preset::Type type)
return ""; return "";
} }
bool SavePresetDialog::get_template_filament_checkbox()
{
if (m_template_filament_checkbox)
{
return m_template_filament_checkbox->GetValue();
}
return false;
}
bool SavePresetDialog::enable_ok_btn() const bool SavePresetDialog::enable_ok_btn() const
{ {
for (const Item* item : m_items) for (const Item* item : m_items)

View File

@ -76,6 +76,7 @@ private:
wxStaticText* m_label {nullptr}; wxStaticText* m_label {nullptr};
wxBoxSizer* m_radio_sizer {nullptr}; wxBoxSizer* m_radio_sizer {nullptr};
ActionType m_action {UndefAction}; ActionType m_action {UndefAction};
wxCheckBox* m_template_filament_checkbox {nullptr};
std::string m_ph_printer_name; std::string m_ph_printer_name;
std::string m_old_preset_name; std::string m_old_preset_name;
@ -88,8 +89,8 @@ public:
const wxString& get_info_line_extention() { return m_info_line_extention; } const wxString& get_info_line_extention() { return m_info_line_extention; }
SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = ""); SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = "", bool template_filament = false);
SavePresetDialog(wxWindow* parent, std::vector<Preset::Type> types, std::string suffix = "", PresetBundle* preset_bundle = nullptr); SavePresetDialog(wxWindow* parent, std::vector<Preset::Type> types, std::string suffix = "", bool template_filament = false, PresetBundle* preset_bundle = nullptr);
SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention); SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention);
~SavePresetDialog() override; ~SavePresetDialog() override;
@ -105,12 +106,13 @@ public:
bool Layout() override; bool Layout() override;
bool is_for_rename() { return m_use_for_rename; } bool is_for_rename() { return m_use_for_rename; }
bool get_template_filament_checkbox();
protected: protected:
void on_dpi_changed(const wxRect& suggested_rect) override; void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override {} void on_sys_color_changed() override {}
private: private:
void build(std::vector<Preset::Type> types, std::string suffix = ""); void build(std::vector<Preset::Type> types, std::string suffix = "", bool template_filament = false);
void update_physical_printers(const std::string& preset_name); void update_physical_printers(const std::string& preset_name);
void accept(); void accept();
}; };

View File

@ -1038,8 +1038,11 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo
static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff) static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff)
{ {
std::string slatree = is_fff ? "" : get_sla_suptree_prefix(config);
const std::string support = is_fff ? "support_material" : "supports_enable"; const std::string support = is_fff ? "support_material" : "supports_enable";
const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : "support_buildplate_only"; const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : slatree + "support_buildplate_only";
return return
! config.opt_bool(support) ? ! config.opt_bool(support) ?
_("None") : _("None") :
@ -1082,7 +1085,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
if (is_fff ? if (is_fff ?
(opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") : (opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") :
(opt_key == "supports_enable" || opt_key == "support_buildplate_only")) (opt_key == "supports_enable" || opt_key == "support_tree_type" || opt_key == get_sla_suptree_prefix(*m_config) + "support_buildplate_only"))
og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff));
if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_around_object")) if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_around_object"))
@ -3712,11 +3715,25 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach)
// focus currently.is there anything better than this ? // focus currently.is there anything better than this ?
//! m_treectrl->OnSetFocus(); //! m_treectrl->OnSetFocus();
auto& old_preset = m_presets->get_edited_preset();
bool from_template = false;
std::string edited_printer;
if (m_type == Preset::TYPE_FILAMENT && old_preset.vendor && old_preset.vendor->templates_profile)
{
//TODO: is this really the best way to get "printer_model" option of currently edited printer?
edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt<ConfigOptionString>("printer_model")->serialize();
if (!edited_printer.empty())
from_template = true;
}
if (name.empty()) { if (name.empty()) {
SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : ""); SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : "", from_template);
if (dlg.ShowModal() != wxID_OK) if (dlg.ShowModal() != wxID_OK)
return; return;
name = dlg.get_name(); name = dlg.get_name();
if (from_template)
from_template = dlg.get_template_filament_checkbox();
} }
if (detach && m_type == Preset::TYPE_PRINTER) if (detach && m_type == Preset::TYPE_PRINTER)
@ -3728,6 +3745,19 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach)
if (detach && m_type == Preset::TYPE_PRINTER) if (detach && m_type == Preset::TYPE_PRINTER)
wxGetApp().mainframe->on_config_changed(m_config); wxGetApp().mainframe->on_config_changed(m_config);
// Update compatible printers
if (from_template && !edited_printer.empty()) {
auto& new_preset = m_presets->get_edited_preset();
std::string cond = new_preset.compatible_printers_condition();
if (!cond.empty())
cond += " and ";
cond += "printer_model == \""+edited_printer+"\"";
new_preset.config.set("compatible_printers_condition", cond);
new_preset.save();
m_presets->save_current_preset(name, detach);
load_current_preset();
}
// Mark the print & filament enabled if they are compatible with the currently selected preset. // 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. // 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.
m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never);
@ -4821,6 +4851,60 @@ void TabSLAMaterial::update()
wxGetApp().mainframe->on_config_changed(m_config); wxGetApp().mainframe->on_config_changed(m_config);
} }
static void add_options_into_line(ConfigOptionsGroupShp &optgroup,
const std::vector<SamePair<std::string>> &prefixes,
const std::string &optkey)
{
auto opt = optgroup->get_option(prefixes.front().first + optkey);
Line line{ opt.opt.label, "" };
line.full_width = 1;
for (auto &prefix : prefixes) {
opt = optgroup->get_option(prefix.first + optkey);
opt.opt.label = prefix.second;
opt.opt.width = 12; // TODO
line.append_option(opt);
}
optgroup->append_line(line);
}
void TabSLAPrint::build_sla_support_params(const std::vector<SamePair<std::string>> &prefixes,
const Slic3r::GUI::PageShp &page)
{
auto optgroup = page->new_optgroup(L("Support head"));
add_options_into_line(optgroup, prefixes, "support_head_front_diameter");
add_options_into_line(optgroup, prefixes, "support_head_penetration");
add_options_into_line(optgroup, prefixes, "support_head_width");
optgroup = page->new_optgroup(L("Support pillar"));
add_options_into_line(optgroup, prefixes, "support_pillar_diameter");
add_options_into_line(optgroup, prefixes, "support_small_pillar_diameter_percent");
add_options_into_line(optgroup, prefixes, "support_max_bridges_on_pillar");
add_options_into_line(optgroup, prefixes, "support_pillar_connection_mode");
add_options_into_line(optgroup, prefixes, "support_buildplate_only");
add_options_into_line(optgroup, prefixes, "support_pillar_widening_factor");
add_options_into_line(optgroup, prefixes, "support_max_weight_on_model");
add_options_into_line(optgroup, prefixes, "support_base_diameter");
add_options_into_line(optgroup, prefixes, "support_base_height");
add_options_into_line(optgroup, prefixes, "support_base_safety_distance");
// Mirrored parameter from Pad page for toggling elevation on the same page
add_options_into_line(optgroup, prefixes, "support_object_elevation");
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
return description_line_widget(parent, &m_support_object_elevation_description_line);
};
optgroup->append_line(line);
optgroup = page->new_optgroup(L("Connection of the support sticks and junctions"));
add_options_into_line(optgroup, prefixes, "support_critical_angle");
add_options_into_line(optgroup, prefixes, "support_max_bridge_length");
add_options_into_line(optgroup, prefixes, "support_max_pillar_link_distance");
}
void TabSLAPrint::build() void TabSLAPrint::build()
{ {
m_presets = &m_preset_bundle->sla_prints; m_presets = &m_preset_bundle->sla_prints;
@ -4833,42 +4917,14 @@ void TabSLAPrint::build()
optgroup->append_single_option_line("faded_layers"); optgroup->append_single_option_line("faded_layers");
page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); page = add_options_page(L("Supports"), "support"/*"sla_supports"*/);
optgroup = page->new_optgroup(L("Supports")); optgroup = page->new_optgroup(L("Supports"));
optgroup->append_single_option_line("supports_enable"); optgroup->append_single_option_line("supports_enable");
optgroup->append_single_option_line("support_tree_type"); optgroup->append_single_option_line("support_tree_type");
optgroup = page->new_optgroup(L("Support head"));
optgroup->append_single_option_line("support_head_front_diameter");
optgroup->append_single_option_line("support_head_penetration");
optgroup->append_single_option_line("support_head_width");
optgroup = page->new_optgroup(L("Support pillar"));
optgroup->append_single_option_line("support_pillar_diameter");
optgroup->append_single_option_line("support_small_pillar_diameter_percent");
optgroup->append_single_option_line("support_max_bridges_on_pillar");
optgroup->append_single_option_line("support_pillar_connection_mode");
optgroup->append_single_option_line("support_buildplate_only");
optgroup->append_single_option_line("support_enforcers_only"); optgroup->append_single_option_line("support_enforcers_only");
optgroup->append_single_option_line("support_pillar_widening_factor");
optgroup->append_single_option_line("support_base_diameter");
optgroup->append_single_option_line("support_base_height");
optgroup->append_single_option_line("support_base_safety_distance");
// Mirrored parameter from Pad page for toggling elevation on the same page build_sla_support_params({{"", "Default"}, {"branching", "Branching"}}, page);
optgroup->append_single_option_line("support_object_elevation");
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
return description_line_widget(parent, &m_support_object_elevation_description_line);
};
optgroup->append_line(line);
optgroup = page->new_optgroup(L("Connection of the support sticks and junctions"));
optgroup->append_single_option_line("support_critical_angle");
optgroup->append_single_option_line("support_max_bridge_length");
optgroup->append_single_option_line("support_max_pillar_link_distance");
optgroup = page->new_optgroup(L("Automatic generation")); optgroup = page->new_optgroup(L("Automatic generation"));
optgroup->append_single_option_line("support_points_density_relative"); optgroup->append_single_option_line("support_points_density_relative");

View File

@ -339,7 +339,7 @@ public:
void on_roll_back_value(const bool to_sys = false); void on_roll_back_value(const bool to_sys = false);
PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false);
static wxString translate_category(const wxString& title, Preset::Type preset_type); static wxString translate_category(const wxString& title, Preset::Type preset_type);
virtual void OnActivate(); virtual void OnActivate();
@ -526,6 +526,12 @@ public:
class TabSLAPrint : public Tab class TabSLAPrint : public Tab
{ {
// Methods are a vector of method prefix -> method label pairs
// method prefix is the prefix whith which all the config values are prefixed
// for a particular method. The label is the friendly name for the method
void build_sla_support_params(const std::vector<SamePair<std::string>> &methods,
const Slic3r::GUI::PageShp &page);
public: public:
TabSLAPrint(wxBookCtrlBase* parent) : TabSLAPrint(wxBookCtrlBase* parent) :
Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {} Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {}

View File

@ -323,6 +323,11 @@ void AppUpdater::priv::parse_version_string(const std::string& body)
return; return;
#endif // 0 #endif // 0
BOOST_LOG_TRIVIAL(error) << "Could not find property tree in version file. Checking for application update has failed."; BOOST_LOG_TRIVIAL(error) << "Could not find property tree in version file. Checking for application update has failed.";
// Lets send event with current version, this way if user triggered this check, it will notify him about no new version online.
std::string version = Semver().to_string();
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
return; return;
} }
std::string tree_string = body.substr(start); std::string tree_string = body.substr(start);

View File

@ -12,14 +12,17 @@
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <curl/curl.h>
#include <wx/app.h> #include <wx/app.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/format.hpp" #include "libslic3r/format.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/UpdateDialogs.hpp" #include "slic3r/GUI/UpdateDialogs.hpp"
@ -49,7 +52,7 @@ namespace Slic3r {
static const char *INDEX_FILENAME = "index.idx"; static const char *INDEX_FILENAME = "index.idx";
static const char *TMP_EXTENSION = ".download"; static const char *TMP_EXTENSION = ".download";
namespace {
void copy_file_fix(const fs::path &source, const fs::path &target) void copy_file_fix(const fs::path &source, const fs::path &target)
{ {
BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target); BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target);
@ -66,7 +69,21 @@ void copy_file_fix(const fs::path &source, const fs::path &target)
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(target, perms); fs::permissions(target, perms);
} }
std::string escape_string_url(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size());
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
}
struct Update struct Update
{ {
fs::path source; fs::path source;
@ -144,6 +161,7 @@ struct PresetUpdater::priv
std::string version_check_url; std::string version_check_url;
fs::path cache_path; fs::path cache_path;
fs::path cache_vendor_path;
fs::path rsrc_path; fs::path rsrc_path;
fs::path vendor_path; fs::path vendor_path;
@ -155,19 +173,25 @@ struct PresetUpdater::priv
priv(); priv();
void set_download_prefs(AppConfig *app_config); void set_download_prefs(const AppConfig *app_config);
bool get_file(const std::string &url, const fs::path &target_path) const; bool get_file(const std::string &url, const fs::path &target_path) const;
void prune_tmps() const; void prune_tmps() const;
void sync_config(const VendorMap vendors); void sync_config(const VendorMap vendors, const std::string& index_archive_url);
void check_install_indices() const; void check_install_indices() const;
Updates get_config_updates(const Semver& old_slic3r_version) const; Updates get_config_updates(const Semver& old_slic3r_version) const;
bool perform_updates(Updates &&updates, bool snapshot = true) const; bool perform_updates(Updates &&updates, bool snapshot = true) const;
void set_waiting_updates(Updates u); void set_waiting_updates(Updates u);
// checks existence and downloads resource to cache
void get_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const;
// checks existence and downloads resource to vendor or copy from cache to vendor
void get_or_copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const;
void update_index_db();
}; };
PresetUpdater::priv::priv() PresetUpdater::priv::priv()
: cache_path(fs::path(Slic3r::data_dir()) / "cache") : cache_path(fs::path(Slic3r::data_dir()) / "cache")
, cache_vendor_path(cache_path / "vendor")
, rsrc_path(fs::path(resources_dir()) / "profiles") , rsrc_path(fs::path(resources_dir()) / "profiles")
, vendor_path(fs::path(Slic3r::data_dir()) / "vendor") , vendor_path(fs::path(Slic3r::data_dir()) / "vendor")
, cancel(false) , cancel(false)
@ -179,8 +203,13 @@ PresetUpdater::priv::priv()
index_db = Index::load_db(); index_db = Index::load_db();
} }
void PresetUpdater::priv::update_index_db()
{
index_db = Index::load_db();
}
// Pull relevant preferences from AppConfig // Pull relevant preferences from AppConfig
void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) void PresetUpdater::priv::set_download_prefs(const AppConfig *app_config)
{ {
enabled_version_check = app_config->get("notify_release") != "none"; enabled_version_check = app_config->get("notify_release") != "none";
version_check_url = app_config->version_check_url(); version_check_url = app_config->version_check_url();
@ -232,46 +261,208 @@ void PresetUpdater::priv::prune_tmps() const
} }
} }
void PresetUpdater::priv::get_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const
{
if (filename.empty() || vendor.empty())
return;
if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") &&
!boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/"))
{
throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url));
}
std::string escaped_filename = escape_string_url(filename);
const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename));
const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename));
const fs::path file_in_cache(cache_path / (vendor + "/" + filename));
if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything.
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in vendor folder. No need to download.";
return;
}
if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything.
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in resources folder. No need to download.";
return;
}
if (fs::exists(file_in_cache)) { // In cache/venodr_name/ dir. No need to do anything.
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in cache folder. No need to download.";
return;
}
BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading.";
const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", escaped_filename); // vendor should already be in url
if (!fs::exists(file_in_cache.parent_path()))
fs::create_directory(file_in_cache.parent_path());
get_file(resource_url, file_in_cache);
return;
}
void PresetUpdater::priv::get_or_copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const
{
if (filename.empty() || vendor.empty())
return;
std::string escaped_filename = escape_string_url(filename);
const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename));
const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename));
const fs::path file_in_cache(cache_path / (vendor + "/" + filename));
if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything.
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in vendor folder. No need to download.";
return;
}
if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything.
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in resources folder. No need to download.";
return;
}
if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir.
if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") &&
!boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/"))
{
throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url));
}
BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename;
const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", escaped_filename); // vendor should already be in url
if (!fs::exists(file_in_vendor.parent_path()))
fs::create_directory(file_in_vendor.parent_path());
get_file(resource_url, file_in_vendor);
return;
}
if (!fs::exists(file_in_vendor.parent_path())) // create vendor_name dir in vendor
fs::create_directory(file_in_vendor.parent_path());
BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor;
copy_file_fix(file_in_cache, file_in_vendor);
}
// Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Download vendor indices. Also download new bundles if an index indicates there's a new one available.
// Both are saved in cache. // Both are saved in cache.
void PresetUpdater::priv::sync_config(const VendorMap vendors) void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& index_archive_url)
{ {
BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache";
if (!enabled_config_update) { return; } if (!enabled_config_update) { return; }
// Donwload vendor preset bundles // Download profiles archive zip
// dk: Do we want to return here on error? Or skip archive dwnld and unzip and work with previous run state cache / vendor? I think return.
// Any error here also doesnt show any info in UI. Do we want maybe notification?
fs::path archive_path(cache_path / "vendor_indices.zip");
if (index_archive_url.empty()) {
BOOST_LOG_TRIVIAL(error) << "Downloading profile archive failed - url has no value.";
return;
}
BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip from " << index_archive_url;
//check if idx_url is leading to our site
if (!boost::starts_with(index_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") &&
!boost::starts_with(index_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/"))
{
BOOST_LOG_TRIVIAL(error) << "Unsafe url path for vedor profiles archive zip. Download is rejected.";
return;
}
if (!get_file(index_archive_url, archive_path)) {
BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed.";
return;
}
if (cancel) {
return;
}
enum class VendorStatus
{
IN_ARCHIVE,
IN_CACHE,
NEW_VERSION,
INSTALLED
};
std::vector<std::pair<std::string, VendorStatus>> vendors_with_status;
// Unzip archive to cache / vendor
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, archive_path.string())) {
BOOST_LOG_TRIVIAL(error) << "Couldn't open zipped bundle.";
return;
} else {
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
// loop the entries
mz_zip_archive_file_stat stat;
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
std::string name(stat.m_filename);
if (stat.m_uncomp_size > 0) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0) {
BOOST_LOG_TRIVIAL(error) << "Failed to unzip " << stat.m_filename;
continue;
}
// create file from buffer
fs::path tmp_path(cache_vendor_path / (name + ".tmp"));
if (!fs::exists(tmp_path.parent_path())) {
BOOST_LOG_TRIVIAL(error) << "Failed to unzip file " << name << ". Directories are not supported. Skipping file.";
continue;
}
fs::path target_path(cache_vendor_path / name);
fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
file.write(buffer.c_str(), buffer.size());
file.close();
boost::system::error_code ec;
bool exists = fs::exists(tmp_path, ec);
if(!exists || ec) {
BOOST_LOG_TRIVIAL(error) << "Failed to find unzipped file at " << tmp_path << ". Terminating Preset updater synchorinzation." ;
close_zip_reader(&archive);
return;
}
fs::rename(tmp_path, target_path, ec);
if (ec) {
BOOST_LOG_TRIVIAL(error) << "Failed to rename unzipped file at " << tmp_path << ". Terminating Preset updater synchorinzation. Error message: " << ec.message();
close_zip_reader(&archive);
return;
}
// TODO: what if unexpected happens here (folder inside zip) - crash!
if (name.substr(name.size() - 3) == "idx")
vendors_with_status.emplace_back(name.substr(0, name.size() - 4), VendorStatus::IN_ARCHIVE); // asume for now its only in archive - if not, it will change later.
}
}
}
close_zip_reader(&archive);
}
// Update vendor preset bundles if in Vendor
// Over all indices from the cache directory: // Over all indices from the cache directory:
for (auto &index : index_db) { for (auto &index : index_db) {
if (cancel) { return; } if (cancel) {
return;
}
auto archive_it = std::find_if(vendors_with_status.begin(), vendors_with_status.end(),
[&index](const std::pair<std::string, VendorStatus>& element) { return element.first == index.vendor(); });
//assert(archive_it != vendors_with_status.end()); // this would mean there is a index for vendor that is missing in recently downloaded archive
const auto vendor_it = vendors.find(index.vendor()); const auto vendor_it = vendors.find(index.vendor());
if (vendor_it == vendors.end()) { if (vendor_it == vendors.end()) {
BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor(); // Not installed vendor yet we need to check missing thumbnails (of new printers)
BOOST_LOG_TRIVIAL(debug) << "No such vendor: " << index.vendor();
if (archive_it != vendors_with_status.end())
archive_it->second = VendorStatus::IN_CACHE;
continue; continue;
} }
if (archive_it != vendors_with_status.end())
archive_it->second = VendorStatus::INSTALLED;
const VendorProfile &vendor = vendor_it->second; const VendorProfile &vendor = vendor_it->second;
if (vendor.config_update_url.empty()) {
BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name;
continue;
}
// Download a fresh index
BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name;
const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME;
const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); const std::string idx_path = (cache_path / (vendor.id + ".idx")).string();
const std::string idx_path_temp = idx_path + "-update"; const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string();
//check if idx_url is leading to our site
if (! boost::starts_with(idx_url, "http://files.prusa3d.com/wp-content/uploads/repository/") &&
! boost::starts_with(idx_url, "https://files.prusa3d.com/wp-content/uploads/repository/"))
{
BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url;
continue;
}
if (!get_file(idx_url, idx_path_temp)) { continue; }
if (cancel) { return; }
// Load the fresh index up // Load the fresh index up
{ {
Index new_index; Index new_index;
@ -282,10 +473,11 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
continue; continue;
} }
if (new_index.version() < index.version()) { if (new_index.version() < index.version()) {
BOOST_LOG_TRIVIAL(warning) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name); BOOST_LOG_TRIVIAL(info) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name);
continue; continue;
} }
Slic3r::rename_file(idx_path_temp, idx_path); copy_file_fix(idx_path_temp, idx_path);
//if we rename path we need to change it in Index object too or create the object again //if we rename path we need to change it in Index object too or create the object again
//index = std::move(new_index); //index = std::move(new_index);
try { try {
@ -315,12 +507,265 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
if (vendor.config_version >= recommended) { continue; } if (vendor.config_version >= recommended) { continue; }
// Download a fresh bundle // vendors that are checked here, doesnt need to be checked again later
if (archive_it != vendors_with_status.end())
archive_it->second = VendorStatus::NEW_VERSION;
// Download recomended ini to cache
const auto path_in_cache = cache_path / (vendor.id + ".ini");
BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name; BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name;
const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string()); const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string());
const auto bundle_path = cache_path / (vendor.id + ".ini"); const auto bundle_path = cache_path / (vendor.id + ".ini");
if (! get_file(bundle_url, bundle_path)) { continue; } if (!get_file(bundle_url, bundle_path))
if (cancel) { return; } continue;
if (cancel)
return;
// vp is fully loaded to get all resources
VendorProfile vp;
try {
vp = VendorProfile::from_ini(bundle_path, true);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.id, bundle_path, e.what());
continue;
}
// check the fresh bundle for missing resources
// for that, the ini file must be parsed (done above)
for (const auto& model : vp.models) {
for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail/*id +"_thumbnail.png"*/} ) {
if (! res.empty()) {
try
{
get_missing_resource(vp.id, res, vendor.config_update_url);
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Failed to get " << res << " for " << vp.id << " " << model.id << ": " << e.what();
}
}
if (cancel)
return;
}
}
}
// Download missing thumbnails for not-installed vendors.
//for (const std::string& vendor : vendors_only_in_archive)
for (const std::pair<std::string, VendorStatus >& vendor : vendors_with_status) {
if (vendor.second == VendorStatus::IN_ARCHIVE) {
// index in archive and not in cache and not installed vendor
const auto idx_path_in_archive = cache_vendor_path / (vendor.first + ".idx");
const auto ini_path_in_archive = cache_vendor_path / (vendor.first + ".ini");
if (!fs::exists(idx_path_in_archive))
continue;
Index index;
try {
index.load(idx_path_in_archive);
}
catch (const std::exception& /* err */) {
BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_archive, vendor.first);
continue;
}
const auto recommended_it = index.recommended();
if (recommended_it == index.end()) {
BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_archive);
continue;
}
const auto recommended = recommended_it->config_version;
if (!fs::exists(ini_path_in_archive)){
// Download recommneded to vendor - we do not have any existing ini file so we have to use hardcoded url.
const std::string fixed_url = GUI::wxGetApp().app_config->profile_folder_url();
const auto bundle_url = format("%1%/%2%/%3%.ini", fixed_url, vendor.first, recommended.to_string());
if (!get_file(bundle_url, ini_path_in_archive))
continue;
} else {
// check existing ini version
// then download recommneded to vendor if needed
VendorProfile vp;
try {
vp = VendorProfile::from_ini(ini_path_in_archive, true);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what());
continue;
}
if (vp.config_version != recommended) {
const std::string fixed_url = GUI::wxGetApp().app_config->profile_folder_url();
const auto bundle_url = format("%1%/%2%/%3%.ini", fixed_url, vendor.first, recommended.to_string());
if (!get_file(bundle_url, ini_path_in_archive))
continue;
}
}
// check missing thumbnails
VendorProfile vp;
try {
vp = VendorProfile::from_ini(ini_path_in_archive, true);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what());
continue;
}
for (const auto& model : vp.models) {
if (!model.thumbnail.empty()) {
try
{
get_missing_resource(vp.id, model.thumbnail, vp.config_update_url);
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what();
}
}
if (cancel)
return;
}
} else if (vendor.second == VendorStatus::IN_CACHE) {
// find those where archive index recommends other version than index in cache and get it if not present
const auto idx_path_in_archive = cache_vendor_path / (vendor.first + ".idx");
const auto ini_path_in_archive = cache_vendor_path / (vendor.first + ".ini");
const auto idx_path_in_cache = cache_path / (vendor.first + ".idx");
if (!fs::exists(idx_path_in_archive) || !fs::exists(idx_path_in_cache))
continue;
// Compare index in cache and recetly downloaded one as part of zip archive
Index index_cache;
try {
index_cache.load(idx_path_in_cache);
}
catch (const std::exception& /* err */) {
BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_cache, vendor.first);
continue;
}
const auto recommended_it_cache = index_cache.recommended();
if (recommended_it_cache == index_cache.end()) {
BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_cache);
continue;
}
const auto recommended_cache = recommended_it_cache->config_version;
Index index_archive;
try {
index_archive.load(idx_path_in_archive);
}
catch (const std::exception& /* err */) {
BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_archive, vendor.first);
continue;
}
const auto recommended_it_archive = index_archive.recommended();
if (recommended_it_archive == index_archive.end()) {
BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_archive);
continue;
}
const auto recommended_archive = recommended_it_archive->config_version;
if (recommended_archive <= recommended_cache) {
// There isn't more recent recomended version online. This vendor is also not istalled.
// Thus only .ini is in resources and came with installation.
// And we expect all resources are present.
continue;
}
// Download new .ini if needed. So next time user runs Wizard, most recent profiles are shown & installed.
if (!fs::exists(ini_path_in_archive) || fs::is_empty(ini_path_in_archive)) {
// download recommneded to vendor
const fs::path ini_path_in_rsrc = rsrc_path / (vendor.first + ".ini");
if (!fs::exists(ini_path_in_rsrc)) {
// THIS SHOULD NOT HAPPEN
continue;
}
// Get download path from existing ini.
VendorProfile vp;
try {
vp = VendorProfile::from_ini(ini_path_in_rsrc, false);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_rsrc, e.what());
continue;
}
const auto bundle_url = format("%1%/%2%.ini", vp.config_update_url, recommended_archive.to_string());
if (!get_file(bundle_url, ini_path_in_archive)) {
BOOST_LOG_TRIVIAL(error) << format("Failed to open vendor .ini file when checking missing resources: %1%", ini_path_in_rsrc);
continue;
}
} else {
// Check existing ini version.
// Then download recommneded to vendor if needed.
VendorProfile vp;
try {
vp = VendorProfile::from_ini(ini_path_in_archive, false);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what());
continue;
}
if (vp.config_version != recommended_archive) {
const auto bundle_url = format("%1%/%2%.ini", vp.config_update_url, recommended_archive.to_string());
if (!get_file(bundle_url, ini_path_in_archive)) {
BOOST_LOG_TRIVIAL(error) << format("Failed to open vendor .ini file when checking missing resources: %1%", ini_path_in_archive);
continue;
}
}
}
if (!fs::exists(ini_path_in_archive)) {
BOOST_LOG_TRIVIAL(error) << "Resources check failed to find ini file for vendor: " << vendor.first;
continue;
}
// check missing thumbnails
VendorProfile vp;
try {
vp = VendorProfile::from_ini(ini_path_in_archive, true);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what());
continue;
}
for (const auto& model : vp.models) {
if (!model.thumbnail.empty()) {
try
{
get_missing_resource(vp.id, model.thumbnail, vp.config_update_url);
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what();
}
}
if (cancel)
return;
}
} else if (vendor.second == VendorStatus::INSTALLED || vendor.second == VendorStatus::NEW_VERSION) {
// Installed vendors need to check that no resource is missing. Do this only for files in vendor folder (not in resorces)
// VendorStatus::NEW_VERSION might seem like a mistake here since files are downloaded when preparing update higher in this function.
// But this is a check for ini file in vendor where resources might be still missing since last update.
const auto path_in_vendor = vendor_path / (vendor.first + ".ini");
if(!fs::exists(path_in_vendor))
continue;
VendorProfile vp;
try {
vp = VendorProfile::from_ini(path_in_vendor, true);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, path_in_vendor, e.what());
continue;
}
for (const auto& model : vp.models) {
for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail }) {
if (!model.thumbnail.empty()) {
try
{
get_or_copy_missing_resource(vp.id, res, vp.config_update_url);
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what();
}
}
if (cancel)
return;
}
}
}
} }
} }
@ -370,8 +815,14 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
} }
// Perform a basic load and check the version of the installed preset bundle. // Perform a basic load and check the version of the installed preset bundle.
auto vp = VendorProfile::from_ini(bundle_path, false); VendorProfile vp;
try {
vp = VendorProfile::from_ini(bundle_path, false);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", idx.vendor(), bundle_path, e.what());
continue;
}
// Getting a recommended version from the latest index, wich may have been downloaded // Getting a recommended version from the latest index, wich may have been downloaded
// from the internet, or installed / updated from the installation resources. // from the internet, or installed / updated from the installation resources.
auto recommended = idx.recommended(); auto recommended = idx.recommended();
@ -401,7 +852,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
bool current_not_supported = false; //if slcr is incompatible but situation is not downgrade, we do forced updated and this bool is information to do it bool current_not_supported = false; //if slcr is incompatible but situation is not downgrade, we do forced updated and this bool is information to do it
if (ver_current_found && !ver_current->is_current_slic3r_supported()){ if (ver_current_found && !ver_current->is_current_slic3r_supported()){
if(ver_current->is_current_slic3r_downgrade()) { if (ver_current->is_current_slic3r_downgrade()) {
// "Reconfigure" situation. // "Reconfigure" situation.
BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string();
updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name); updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name);
@ -443,10 +894,13 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
fs::path bundle_path_idx_to_install; fs::path bundle_path_idx_to_install;
if (fs::exists(path_in_cache)) { if (fs::exists(path_in_cache)) {
try { try {
VendorProfile new_vp = VendorProfile::from_ini(path_in_cache, false); VendorProfile new_vp = VendorProfile::from_ini(path_in_cache, true);
if (new_vp.config_version == recommended->config_version) { if (new_vp.config_version == recommended->config_version) {
// The config bundle from the cache directory matches the recommended version of the index from the cache directory. // The config bundle from the cache directory matches the recommended version of the index from the cache directory.
// This is the newest known recommended config. Use it. // This is the newest known recommended config. Use it.
if (!PresetUtils::vendor_profile_has_all_resources(new_vp)) {
BOOST_LOG_TRIVIAL(warning) << "Some resources are missing for update of vedor " << new_vp.id;
}
new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported);
// and install the config index from the cache into vendor's directory. // and install the config index from the cache into vendor's directory.
bundle_path_idx_to_install = idx.path(); bundle_path_idx_to_install = idx.path();
@ -564,6 +1018,9 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size()); BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size());
wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles") , 100, nullptr, wxPD_AUTO_HIDE);
progress_dialog.Pulse();
for (const auto &update : updates.updates) { for (const auto &update : updates.updates) {
BOOST_LOG_TRIVIAL(info) << '\t' << update; BOOST_LOG_TRIVIAL(info) << '\t' << update;
@ -601,12 +1058,40 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
for (const auto &name : bundle.obsolete_presets.sla_prints) { obsolete_remover("sla_print", name); } for (const auto &name : bundle.obsolete_presets.sla_prints) { obsolete_remover("sla_print", name); }
for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); }
for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); }
// check if any resorces of installed bundle are missing. If so, new ones should be already downloaded at cache/vendor_id/
VendorProfile vp;
try {
vp = VendorProfile::from_ini(update.target, true);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", update.target, e.what());
continue;
}
progress_dialog.Update(1, GUI::format_wxstr(_L("Downloading resources for %1%."),vp.id));
progress_dialog.Pulse();
for (const auto& model : vp.models) {
for (const std::string& resource : { model.bed_texture, model.bed_model, model.thumbnail }) {
if (resource.empty())
continue;
try
{
get_or_copy_missing_resource(vp.id, resource, vp.config_update_url);
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Failed to prepare " << resource << " for " << vp.id << " " << model.id << ": " << e.what();
}
}
}
} }
progress_dialog.Destroy();
} }
return true; return true;
} }
void PresetUpdater::priv::set_waiting_updates(Updates u) void PresetUpdater::priv::set_waiting_updates(Updates u)
{ {
waiting_updates = u; waiting_updates = u;
@ -630,7 +1115,7 @@ PresetUpdater::~PresetUpdater()
} }
} }
void PresetUpdater::sync(PresetBundle *preset_bundle) void PresetUpdater::sync(const PresetBundle *preset_bundle)
{ {
p->set_download_prefs(GUI::wxGetApp().app_config); p->set_download_prefs(GUI::wxGetApp().app_config);
if (!p->enabled_version_check && !p->enabled_config_update) { return; } if (!p->enabled_version_check && !p->enabled_config_update) { return; }
@ -639,13 +1124,24 @@ void PresetUpdater::sync(PresetBundle *preset_bundle)
// Unfortunatelly as of C++11, it needs to be copied again // Unfortunatelly as of C++11, it needs to be copied again
// into the closure (but perhaps the compiler can elide this). // into the closure (but perhaps the compiler can elide this).
VendorMap vendors = preset_bundle->vendors; VendorMap vendors = preset_bundle->vendors;
std::string index_archive_url = GUI::wxGetApp().app_config->index_archive_url();
p->thread = std::thread([this, vendors]() { p->thread = std::thread([this, vendors, index_archive_url]() {
this->p->prune_tmps(); this->p->prune_tmps();
this->p->sync_config(std::move(vendors)); this->p->sync_config(std::move(vendors), index_archive_url);
}); });
} }
void PresetUpdater::cancel_sync()
{
if (p && p->thread.joinable()) {
// This will stop transfers being done by the thread, if any.
// Cancelling takes some time, but should complete soon enough.
p->cancel = true;
p->thread.join();
}
}
void PresetUpdater::slic3r_update_notify() void PresetUpdater::slic3r_update_notify()
{ {
if (! p->enabled_version_check) if (! p->enabled_version_check)
@ -810,7 +1306,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
return R_NOOP; return R_NOOP;
} }
bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector<std::string> bundles, bool snapshot) const
{ {
Updates updates; Updates updates;
@ -818,8 +1314,66 @@ bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
for (const auto &bundle : bundles) { for (const auto &bundle : bundles) {
auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini"); auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini");
auto path_in_cache_vendor = (p->cache_vendor_path / bundle).replace_extension(".ini");
auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini");
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
bool is_in_rsrc = fs::exists(path_in_rsrc);
bool is_in_cache_vendor = fs::exists(path_in_cache_vendor) && !fs::is_empty(path_in_cache_vendor);
// find if in cache vendor is newer version than in resources
if (is_in_cache_vendor) {
Semver version_cache = Semver::zero();
try {
auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false);
version_cache = vp_cache.config_version;
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", path_in_cache_vendor, e.what());
// lets use file in resources
if (is_in_rsrc) {
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
}
continue;
}
Semver version_rsrc = Semver::zero();
try {
if (is_in_rsrc) {
auto vp = VendorProfile::from_ini(path_in_rsrc, false);
version_rsrc = vp.config_version;
}
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", path_in_rsrc, e.what());
continue;
}
if (!is_in_rsrc || version_cache > version_rsrc) {
// in case we are installing from cache / vendor. we should also copy index to cache
// This needs to be done now bcs the current one would be missing this version on next start
// dk: Should we copy it to vendor dir too?
auto path_idx_cache_vendor(path_in_cache_vendor);
path_idx_cache_vendor.replace_extension(".idx");
auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx");
// DK: do this during perform_updates() too?
if (fs::exists(path_idx_cache_vendor))
copy_file_fix(path_idx_cache_vendor, path_idx_cache);
else // Should we dialog this?
BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string());
updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", "");
} else {
if (is_in_rsrc)
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
}
} else {
if (! is_in_rsrc) {
// This should not happen. Instead of an assert, make it crash in Release mode too.
BOOST_LOG_TRIVIAL(error) << "Internal error in PresetUpdater! Terminating the application.";
std::terminate();
}
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
}
} }
return p->perform_updates(std::move(updates), snapshot); return p->perform_updates(std::move(updates), snapshot);
@ -857,4 +1411,9 @@ bool PresetUpdater::version_check_enabled() const
return p->enabled_version_check; return p->enabled_version_check;
} }
void PresetUpdater::update_index_db()
{
p->update_index_db();
}
} }

View File

@ -26,7 +26,8 @@ public:
~PresetUpdater(); ~PresetUpdater();
// If either version check or config updating is enabled, get the appropriate data in the background and cache it. // If either version check or config updating is enabled, get the appropriate data in the background and cache it.
void sync(PresetBundle *preset_bundle); void sync(const PresetBundle *preset_bundle);
void cancel_sync();
// If version check is enabled, check if chaced online slic3r version is newer, notify if so. // If version check is enabled, check if chaced online slic3r version is newer, notify if so.
void slic3r_update_notify(); void slic3r_update_notify();
@ -52,9 +53,11 @@ public:
// Providing old slic3r version upgrade profiles on upgrade of an application even in case // Providing old slic3r version upgrade profiles on upgrade of an application even in case
// that the config index installed from the Internet is equal to the index contained in the installation package. // that the config index installed from the Internet is equal to the index contained in the installation package.
UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const; UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const;
void update_index_db();
// "Update" a list of bundles from resources (behaves like an online update). // "Update" a list of bundles from resources or cache/vendor (behaves like an online update).
bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const; bool install_bundles_rsrc_or_cache_vendor(std::vector<std::string> bundles, bool snapshot = true) const;
void on_update_notification_confirm(); void on_update_notification_confirm();

76
tests/data/U_overhang.obj Normal file
View File

@ -0,0 +1,76 @@
####
#
# OBJ File Generated by Meshlab
#
####
# Object U_overhang.obj
#
# Vertices: 16
# Faces: 28
#
####
vn 1.570797 1.570796 1.570796
v 10.000000 10.000000 11.000000
vn 4.712389 1.570796 -1.570796
v 10.000000 1.000000 10.000000
vn 1.570796 1.570796 -1.570796
v 10.000000 10.000000 10.000000
vn 1.570796 -1.570796 1.570796
v 10.000000 0.000000 11.000000
vn 4.712389 1.570796 1.570796
v 10.000000 1.000000 1.000000
vn 1.570797 1.570796 -1.570796
v 10.000000 10.000000 0.000000
vn 1.570796 1.570796 1.570796
v 10.000000 10.000000 1.000000
vn 1.570796 -1.570796 -1.570796
v 10.000000 0.000000 0.000000
vn -1.570796 1.570796 1.570796
v 0.000000 10.000000 1.000000
vn -4.712389 1.570796 1.570796
v 0.000000 1.000000 1.000000
vn -1.570796 -1.570796 -1.570796
v 0.000000 0.000000 0.000000
vn -1.570797 1.570796 -1.570796
v 0.000000 10.000000 0.000000
vn -4.712389 1.570796 -1.570796
v 0.000000 1.000000 10.000000
vn -1.570797 1.570796 1.570796
v 0.000000 10.000000 11.000000
vn -1.570796 1.570796 -1.570796
v 0.000000 10.000000 10.000000
vn -1.570796 -1.570796 1.570796
v 0.000000 0.000000 11.000000
# 16 vertices, 0 vertices normals
f 1//1 2//2 3//3
f 2//2 4//4 5//5
f 4//4 2//2 1//1
f 5//5 6//6 7//7
f 5//5 8//8 6//6
f 8//8 5//5 4//4
f 9//9 5//5 7//7
f 5//5 9//9 10//10
f 11//11 6//6 8//8
f 6//6 11//11 12//12
f 12//12 10//10 9//9
f 10//10 11//11 13//13
f 11//11 10//10 12//12
f 13//13 14//14 15//15
f 13//13 16//16 14//14
f 16//16 13//13 11//11
f 6//6 9//9 7//7
f 9//9 6//6 12//12
f 11//11 4//4 16//16
f 4//4 11//11 8//8
f 13//13 3//3 2//2
f 3//3 13//13 15//15
f 5//5 13//13 2//2
f 13//13 5//5 10//10
f 14//14 4//4 1//1
f 4//4 14//14 16//16
f 3//3 14//14 1//1
f 14//14 3//3 15//15
# 28 faces, 0 coords texture
# End of File

View File

@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
sla_test_utils.hpp sla_test_utils.cpp sla_test_utils.hpp sla_test_utils.cpp
sla_supptgen_tests.cpp sla_supptgen_tests.cpp
sla_raycast_tests.cpp sla_raycast_tests.cpp
sla_supptreeutils_tests.cpp
sla_archive_readwrite_tests.cpp) sla_archive_readwrite_tests.cpp)
# mold linker for successful linking needs also to link TBB library and link it before libslic3r. # mold linker for successful linking needs also to link TBB library and link it before libslic3r.

View File

@ -1,4 +1,3 @@
#include <unordered_set>
#include <unordered_map> #include <unordered_map>
#include <random> #include <random>
#include <numeric> #include <numeric>
@ -32,13 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = {
} // namespace } // namespace
TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") {
test_pairhash<int, int>();
test_pairhash<int, long>();
test_pairhash<unsigned, unsigned>();
test_pairhash<unsigned, unsigned long>();
}
TEST_CASE("Support point generator should be deterministic if seeded", TEST_CASE("Support point generator should be deterministic if seeded",
"[SLASupportGeneration], [SLAPointGen]") { "[SLASupportGeneration], [SLAPointGen]") {
TriangleMesh mesh = load_model("A_upsidedown.obj"); TriangleMesh mesh = load_model("A_upsidedown.obj");
@ -173,106 +165,6 @@ TEST_CASE("DefaultSupports::FloorSupportsDoNotPierceModel", "[SLASupportGenerati
// for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg); // for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg);
//} //}
bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt, float angle)
{
Vec3d D = (pt - supp).cast<double>();
double dot_sq = -D.z() * std::abs(-D.z());
return dot_sq <
D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle));
}
TEST_CASE("BranchingSupports::MergePointFinder", "[SLASupportGeneration][Branching]") {
SECTION("Identical points have the same merge point") {
Vec3f a{0.f, 0.f, 0.f}, b = a;
auto slope = float(PI / 4.);
auto mergept = branchingtree::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).norm() < EPSILON);
REQUIRE((*mergept - a).norm() < EPSILON);
}
// ^ Z
// | a *
// |
// | b * <= mergept
SECTION("Points at different heights have the lower point as mergepoint") {
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f};
auto slope = float(PI / 4.);
auto mergept = branchingtree::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON);
}
// -|---------> X
// a b
// * *
// * <= mergept
SECTION("Points at different X have mergept in the middle at lower Z") {
Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f};
auto slope = float(PI / 4.);
auto mergept = branchingtree::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
// Distance of mergept should be equal from both input points
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
REQUIRE(D < EPSILON);
REQUIRE(!is_outside_support_cone(a, *mergept, slope));
REQUIRE(!is_outside_support_cone(b, *mergept, slope));
}
// -|---------> Y
// a b
// * *
// * <= mergept
SECTION("Points at different Y have mergept in the middle at lower Z") {
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f};
auto slope = float(PI / 4.);
auto mergept = branchingtree::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
// Distance of mergept should be equal from both input points
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
REQUIRE(D < EPSILON);
REQUIRE(!is_outside_support_cone(a, *mergept, slope));
REQUIRE(!is_outside_support_cone(b, *mergept, slope));
}
SECTION("Points separated by less than critical angle have the lower point as mergept") {
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f};
auto slope = float(PI / 4.);
auto mergept = branchingtree::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).norm() < 2 * EPSILON);
}
// -|----------------------------> Y
// a b
// * * <= mergept *
//
SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") {
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f};
auto slope = EPSILON;
auto mergept = branchingtree::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
Vec3f middle = (b + a) / 2.;
REQUIRE((*mergept - middle).norm() < 4 * EPSILON);
}
}
TEST_CASE("BranchingSupports::ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration][Branching]") { TEST_CASE("BranchingSupports::ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration][Branching]") {

View File

@ -0,0 +1,377 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <unordered_set>
#include "libslic3r/Execution/ExecutionSeq.hpp"
#include "libslic3r/SLA/SupportTreeUtils.hpp"
#include "libslic3r/SLA/SupportTreeUtilsLegacy.hpp"
// Test pair hash for 'nums' random number pairs.
template <class I, class II> void test_pairhash()
{
const constexpr size_t nums = 1000;
I A[nums] = {0}, B[nums] = {0};
std::unordered_set<I> CH;
std::unordered_map<II, std::pair<I, I>> ints;
std::random_device rd;
std::mt19937 gen(rd());
const I Ibits = int(sizeof(I) * CHAR_BIT);
const II IIbits = int(sizeof(II) * CHAR_BIT);
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
if (std::is_signed<I>::value) bits -= 1;
const I Imin = 0;
const I Imax = I(std::pow(2., bits) - 1);
std::uniform_int_distribution<I> dis(Imin, Imax);
for (size_t i = 0; i < nums;) {
I a = dis(gen);
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
}
for (size_t i = 0; i < nums;) {
I b = dis(gen);
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
}
for (size_t i = 0; i < nums; ++i) {
I a = A[i], b = B[i];
REQUIRE(a != b);
II hash_ab = Slic3r::sla::pairhash<I, II>(a, b);
II hash_ba = Slic3r::sla::pairhash<I, II>(b, a);
REQUIRE(hash_ab == hash_ba);
auto it = ints.find(hash_ab);
if (it != ints.end()) {
REQUIRE((
(it->second.first == a && it->second.second == b) ||
(it->second.first == b && it->second.second == a)
));
} else
ints[hash_ab] = std::make_pair(a, b);
}
}
TEST_CASE("Pillar pairhash should be unique", "[suptreeutils]") {
test_pairhash<int, int>();
test_pairhash<int, long>();
test_pairhash<unsigned, unsigned>();
test_pairhash<unsigned, unsigned long>();
}
static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn,
const Slic3r::sla::SupportableMesh &sm,
const Slic3r::sla::Junction &j,
double end_r,
const std::string &stl_fname = "output.stl")
{
using namespace Slic3r;
//#ifndef NDEBUG
sla::SupportTreeBuilder builder;
if (!conn)
builder.add_junction(j);
sla::build_ground_connection(builder, sm, conn);
indexed_triangle_set mesh = *sm.emesh.get_triangle_mesh();
its_merge(mesh, builder.merged_mesh());
its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh);
//#endif
REQUIRE(bool(conn));
// The route should include the source and one avoidance junction.
REQUIRE(conn.path.size() == 2);
// Check if the radius increases with each node
REQUIRE(conn.path.front().r < conn.path.back().r);
REQUIRE(conn.path.back().r < conn.pillar_base->r_top);
// The end radius and the pillar base's upper radius should match
REQUIRE(conn.pillar_base->r_top == Approx(end_r));
}
TEST_CASE("Pillar search dumb case", "[suptreeutils]") {
using namespace Slic3r;
constexpr double FromR = 0.5;
auto j = sla::Junction{Vec3d::Zero(), FromR};
SECTION("with empty mesh") {
sla::SupportableMesh sm{indexed_triangle_set{},
sla::SupportPoints{},
sla::SupportTreeConfig{}};
constexpr double EndR = 1.;
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN);
REQUIRE(conn);
// REQUIRE(conn.path.size() == 1);
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
}
SECTION("with zero R source and destination") {
sla::SupportableMesh sm{indexed_triangle_set{},
sla::SupportPoints{},
sla::SupportTreeConfig{}};
j.r = 0.;
constexpr double EndR = 0.;
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN);
REQUIRE(conn);
// REQUIRE(conn.path.size() == 1);
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
REQUIRE(conn.pillar_base->r_top == Approx(0.));
}
SECTION("with zero init direction") {
sla::SupportableMesh sm{indexed_triangle_set{},
sla::SupportPoints{},
sla::SupportTreeConfig{}};
constexpr double EndR = 1.;
Vec3d init_dir = Vec3d::Zero();
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir);
REQUIRE(conn);
// REQUIRE(conn.path.size() == 1);
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
}
}
TEST_CASE("Avoid disk below junction", "[suptreeutils]")
{
// In this test there will be a disk mesh with some radius, centered at
// (0, 0, 0) and above the disk, a junction from which the support pillar
// should be routed. The algorithm needs to find an avoidance route.
using namespace Slic3r;
constexpr double FromRadius = .5;
constexpr double EndRadius = 1.;
constexpr double CylRadius = 4.;
constexpr double CylHeight = 1.;
sla::SupportTreeConfig cfg;
indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight);
// 2.5 * CyRadius height should be enough to be able to insert a bridge
// with 45 degree tilt above the disk.
sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius};
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
SECTION("with elevation") {
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN);
eval_ground_conn(conn, sm, j, EndRadius, "disk.stl");
// Check if the avoidance junction is indeed outside of the disk barrier's
// edge.
auto p = conn.path.back().pos;
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
REQUIRE(pR + FromRadius > CylRadius);
}
SECTION("without elevation") {
sm.cfg.object_elevation_mm = 0.;
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN);
eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl");
// Check if the avoidance junction is indeed outside of the disk barrier's
// edge.
auto p = conn.path.back().pos;
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
REQUIRE(pR + FromRadius > CylRadius);
}
}
TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]")
{
// In this test there will be a disk mesh with some radius, centered at
// (0, 0, 0) and above the disk, a junction from which the support pillar
// should be routed. The algorithm needs to find an avoidance route.
using namespace Slic3r;
constexpr double FromRadius = .5;
constexpr double EndRadius = 1.;
constexpr double CylRadius = 4.;
constexpr double CylHeight = 1.;
constexpr double JElevX = 2.5;
sla::SupportTreeConfig cfg;
indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight);
indexed_triangle_set wall = its_make_cube(1., 2 * CylRadius, JElevX * CylRadius);
its_translate(wall, Vec3f{float(FromRadius), -float(CylRadius), 0.f});
its_merge(disk, wall);
// 2.5 * CyRadius height should be enough to be able to insert a bridge
// with 45 degree tilt above the disk.
sla::Junction j{Vec3d{0., 0., JElevX * CylRadius}, FromRadius};
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
SECTION("with elevation") {
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier.stl");
// Check if the avoidance junction is indeed outside of the disk barrier's
// edge.
auto p = conn.path.back().pos;
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
REQUIRE(pR + FromRadius > CylRadius);
}
SECTION("without elevation") {
sm.cfg.object_elevation_mm = 0.;
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier_ze.stl");
// Check if the avoidance junction is indeed outside of the disk barrier's
// edge.
auto p = conn.path.back().pos;
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
REQUIRE(pR + FromRadius > CylRadius);
}
}
TEST_CASE("Find ground route just above ground", "[suptreeutils]") {
using namespace Slic3r;
sla::SupportTreeConfig cfg;
cfg.object_elevation_mm = 0.;
sla::Junction j{Vec3d{0., 0., 2. * cfg.head_back_radius_mm}, cfg.head_back_radius_mm};
sla::SupportableMesh sm{{}, sla::SupportPoints{}, cfg};
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, Geometry::spheric_to_dir(3 * PI/ 4, PI));
REQUIRE(conn);
REQUIRE(conn.pillar_base->pos.z() >= Approx(ground_level(sm)));
}
TEST_CASE("BranchingSupports::MergePointFinder", "[suptreeutils]") {
using namespace Slic3r;
SECTION("Identical points have the same merge point") {
Vec3f a{0.f, 0.f, 0.f}, b = a;
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).norm() < EPSILON);
REQUIRE((*mergept - a).norm() < EPSILON);
}
// ^ Z
// | a *
// |
// | b * <= mergept
SECTION("Points at different heights have the lower point as mergepoint") {
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f};
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON);
}
// -|---------> X
// a b
// * *
// * <= mergept
SECTION("Points at different X have mergept in the middle at lower Z") {
Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f};
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
// Distance of mergept should be equal from both input points
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
REQUIRE(D < EPSILON);
REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope));
REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope));
}
// -|---------> Y
// a b
// * *
// * <= mergept
SECTION("Points at different Y have mergept in the middle at lower Z") {
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f};
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
// Distance of mergept should be equal from both input points
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
REQUIRE(D < EPSILON);
REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope));
REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope));
}
SECTION("Points separated by less than critical angle have the lower point as mergept") {
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f};
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).norm() < 2 * EPSILON);
}
// -|----------------------------> Y
// a b
// * * <= mergept *
//
SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") {
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f};
auto slope = EPSILON;
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
Vec3f middle = (b + a) / 2.;
REQUIRE((*mergept - middle).norm() < 4 * EPSILON);
}
}

View File

@ -118,7 +118,7 @@ void test_supports(const std::string &obj_filename,
// Create the special index-triangle mesh with spatial indexing which // Create the special index-triangle mesh with spatial indexing which
// is the input of the support point and support mesh generators // is the input of the support point and support mesh generators
AABBMesh emesh{mesh}; sla::SupportableMesh sm{mesh.its, {}, supportcfg};
#ifdef SLIC3R_HOLE_RAYCASTER #ifdef SLIC3R_HOLE_RAYCASTER
if (hollowingcfg.enabled) if (hollowingcfg.enabled)
@ -130,23 +130,23 @@ void test_supports(const std::string &obj_filename,
// Create the support point generator // Create the support point generator
sla::SupportPointGenerator::Config autogencfg; sla::SupportPointGenerator::Config autogencfg;
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm); autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}}; sla::SupportPointGenerator point_gen{sm.emesh, autogencfg, [] {}, [](int) {}};
point_gen.seed(0); // Make the test repeatable point_gen.seed(0); // Make the test repeatable
point_gen.execute(out.model_slices, out.slicegrid); point_gen.execute(out.model_slices, out.slicegrid);
// Get the calculated support points. // Get the calculated support points.
std::vector<sla::SupportPoint> support_points = point_gen.output(); sm.pts = point_gen.output();
int validityflags = ASSUME_NO_REPAIR; int validityflags = ASSUME_NO_REPAIR;
// If there is no elevation, support points shall be removed from the // If there is no elevation, support points shall be removed from the
// bottom of the object. // bottom of the object.
if (std::abs(supportcfg.object_elevation_mm) < EPSILON) { if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm); sla::remove_bottom_points(sm.pts, zmin + supportcfg.base_height_mm);
} else { } else {
// Should be support points at least on the bottom of the model // Should be support points at least on the bottom of the model
REQUIRE_FALSE(support_points.empty()); REQUIRE_FALSE(sm.pts.empty());
// Also the support mesh should not be empty. // Also the support mesh should not be empty.
validityflags |= ASSUME_NO_EMPTY; validityflags |= ASSUME_NO_EMPTY;
@ -154,7 +154,6 @@ void test_supports(const std::string &obj_filename,
// Generate the actual support tree // Generate the actual support tree
sla::SupportTreeBuilder treebuilder; sla::SupportTreeBuilder treebuilder;
sla::SupportableMesh sm{emesh, support_points, supportcfg};
switch (sm.cfg.tree_type) { switch (sm.cfg.tree_type) {
case sla::SupportTreeType::Default: { case sla::SupportTreeType::Default: {
@ -182,6 +181,18 @@ void test_supports(const std::string &obj_filename,
if (std::abs(supportcfg.object_elevation_mm) < EPSILON) if (std::abs(supportcfg.object_elevation_mm) < EPSILON)
allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm; allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm;
#ifndef NDEBUG
if (!(obb.min.z() >= Approx(allowed_zmin)) || !(obb.max.z() <= Approx(zmax)))
{
indexed_triangle_set its;
treebuilder.retrieve_full_mesh(its);
TriangleMesh m{its};
m.merge(mesh);
m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" +
obj_filename).c_str());
}
#endif
REQUIRE(obb.min.z() >= Approx(allowed_zmin)); REQUIRE(obb.min.z() >= Approx(allowed_zmin));
REQUIRE(obb.max.z() <= Approx(zmax)); REQUIRE(obb.max.z() <= Approx(zmax));

View File

@ -14,14 +14,12 @@
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/SLA/Pad.hpp" #include "libslic3r/SLA/Pad.hpp"
#include "libslic3r/SLA/SupportTreeBuilder.hpp" #include "libslic3r/SLA/SupportTreeBuilder.hpp"
#include "libslic3r/SLA/SupportTreeUtils.hpp"
#include "libslic3r/SLA/SupportPointGenerator.hpp" #include "libslic3r/SLA/SupportPointGenerator.hpp"
#include "libslic3r/SLA/AGGRaster.hpp" #include "libslic3r/SLA/AGGRaster.hpp"
#include "libslic3r/SLA/ConcaveHull.hpp" #include "libslic3r/SLA/ConcaveHull.hpp"
#include "libslic3r/MTUtils.hpp" #include "libslic3r/MTUtils.hpp"
#include "libslic3r/SVG.hpp" #include "libslic3r/SVG.hpp"
#include "libslic3r/Format/OBJ.hpp"
using namespace Slic3r; using namespace Slic3r;
@ -111,58 +109,6 @@ inline void test_support_model_collision(
test_support_model_collision(obj_filename, input_supportcfg, hcfg, {}); test_support_model_collision(obj_filename, input_supportcfg, hcfg, {});
} }
// Test pair hash for 'nums' random number pairs.
template <class I, class II> void test_pairhash()
{
const constexpr size_t nums = 1000;
I A[nums] = {0}, B[nums] = {0};
std::unordered_set<I> CH;
std::unordered_map<II, std::pair<I, I>> ints;
std::random_device rd;
std::mt19937 gen(rd());
const I Ibits = int(sizeof(I) * CHAR_BIT);
const II IIbits = int(sizeof(II) * CHAR_BIT);
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
if (std::is_signed<I>::value) bits -= 1;
const I Imin = 0;
const I Imax = I(std::pow(2., bits) - 1);
std::uniform_int_distribution<I> dis(Imin, Imax);
for (size_t i = 0; i < nums;) {
I a = dis(gen);
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
}
for (size_t i = 0; i < nums;) {
I b = dis(gen);
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
}
for (size_t i = 0; i < nums; ++i) {
I a = A[i], b = B[i];
REQUIRE(a != b);
II hash_ab = sla::pairhash<I, II>(a, b);
II hash_ba = sla::pairhash<I, II>(b, a);
REQUIRE(hash_ab == hash_ba);
auto it = ints.find(hash_ab);
if (it != ints.end()) {
REQUIRE((
(it->second.first == a && it->second.second == b) ||
(it->second.first == b && it->second.second == a)
));
} else
ints[hash_ab] = std::make_pair(a, b);
}
}
// SLA Raster test utils: // SLA Raster test utils:
using TPixel = uint8_t; using TPixel = uint8_t;