mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 18:59:02 +08:00
Merge branch 'master' into et_tm_sla_volumes_6-SPE-1285
This commit is contained in:
commit
76d0e11699
2
resources/profiles/Templates.idx
Normal file
2
resources/profiles/Templates.idx
Normal file
@ -0,0 +1,2 @@
|
||||
min_slic3r_version = 2.6.0-alpha0
|
||||
1.0.0 Initial
|
2144
resources/profiles/Templates.ini
Normal file
2144
resources/profiles/Templates.ini
Normal file
File diff suppressed because it is too large
Load Diff
@ -67,9 +67,9 @@ public:
|
||||
double m_t = infty();
|
||||
int m_face_id = -1;
|
||||
const AABBMesh *m_mesh = nullptr;
|
||||
Vec3d m_dir;
|
||||
Vec3d m_source;
|
||||
Vec3d m_normal;
|
||||
Vec3d m_dir = Vec3d::Zero();
|
||||
Vec3d m_source = Vec3d::Zero();
|
||||
Vec3d m_normal = Vec3d::Zero();
|
||||
friend class AABBMesh;
|
||||
|
||||
// A valid object of this class can only be obtained from
|
||||
|
@ -36,6 +36,10 @@ static const std::string MODEL_PREFIX = "model:";
|
||||
// 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.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_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 (get("no_defaults").empty())
|
||||
set("no_defaults", "1");
|
||||
if (get("no_templates").empty())
|
||||
set("no_templates", "0");
|
||||
if (get("show_incompatible_presets").empty())
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return boost::filesystem::exists(config_path());
|
||||
|
@ -139,6 +139,11 @@ public:
|
||||
// Get the Slic3r version check url.
|
||||
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
|
||||
std::string version_check_url() const;
|
||||
// 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
|
||||
// by the current version
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
|
||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
namespace Slic3r { namespace branchingtree {
|
||||
|
||||
@ -76,18 +76,20 @@ void build_tree(PointCloud &nodes, Builder &builder)
|
||||
switch (type) {
|
||||
case BED: {
|
||||
closest_node.weight = w;
|
||||
if (closest_it->dst_branching > nodes.properties().max_branch_length()) {
|
||||
auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f;
|
||||
Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin};
|
||||
new_node.id = int(nodes.next_junction_id());
|
||||
new_node.weight = nodes.get(node_id).weight + hl_br_len;
|
||||
double max_br_len = nodes.properties().max_branch_length();
|
||||
if (closest_it->dst_branching > max_br_len) {
|
||||
std::optional<Vec3f> avo = builder.suggest_avoidance(node, max_br_len);
|
||||
if (!avo)
|
||||
break;
|
||||
|
||||
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))) {
|
||||
size_t new_idx = nodes.insert_junction(new_node);
|
||||
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;
|
||||
nodes.get(closest_node_id) = closest_node;
|
||||
nodes.mark_unreachable(closest_node_id);
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include <admesh/stl.h>
|
||||
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r { namespace branchingtree {
|
||||
|
||||
@ -21,6 +20,7 @@ class Properties
|
||||
ExPolygons m_bed_shape;
|
||||
|
||||
public:
|
||||
|
||||
// Maximum slope for bridges of the tree
|
||||
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
|
||||
// method as a callback and implement the actions that need to be done.
|
||||
class Builder
|
||||
@ -100,6 +105,12 @@ public:
|
||||
// Add an anchor bridge to the model body
|
||||
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)
|
||||
virtual void report_unroutable(const Node &j) = 0;
|
||||
|
||||
|
@ -1,82 +1,15 @@
|
||||
#include "PointCloud.hpp"
|
||||
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Tesselate.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||
|
||||
#include <igl/random_points_on_mesh.h>
|
||||
|
||||
namespace Slic3r { namespace branchingtree {
|
||||
|
||||
std::optional<Vec3f> find_merge_pt(const Vec3f &A,
|
||||
const Vec3f &B,
|
||||
float critical_angle)
|
||||
std::optional<Vec3f> find_merge_pt(const Vec3f &A, const Vec3f &B, float max_slope)
|
||||
{
|
||||
// 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{};
|
||||
return sla::find_merge_pt(A, B, max_slope);
|
||||
}
|
||||
|
||||
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<Vec3f> ret;
|
||||
|
||||
auto triangles = triangulate_expolygons_3d(bed, z);
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(triangles.size());
|
||||
@ -199,6 +130,9 @@ PointCloud::PointCloud(std::vector<Node> meshpts,
|
||||
for (size_t i = 0; i < m_leafs.size(); ++i) {
|
||||
Node &n = m_leafs[i];
|
||||
n.id = int(LEAFS_BEGIN + i);
|
||||
n.left = Node::ID_NONE;
|
||||
n.right = Node::ID_NONE;
|
||||
|
||||
m_ktree.insert({n.pos, n.id});
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include "BranchingTree.hpp"
|
||||
|
||||
#include "libslic3r/Execution/Execution.hpp"
|
||||
//#include "libslic3r/Execution/Execution.hpp"
|
||||
#include "libslic3r/MutablePriorityQueue.hpp"
|
||||
|
||||
#include "libslic3r/BoostAdapter.hpp"
|
||||
@ -78,14 +78,6 @@ private:
|
||||
rtree<PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>
|
||||
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>
|
||||
static auto *get_node(PC &&pc, size_t id)
|
||||
{
|
||||
@ -104,6 +96,14 @@ private:
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
if (auto nodeptr = pc.find(root); nodeptr != nullptr) {
|
||||
auto &nroot = *nodeptr;
|
||||
TraverseReturnT ret{true, true};
|
||||
|
||||
if constexpr (std::is_same_v<std::invoke_result_t<Fn, decltype(nroot)>, void>) {
|
||||
// Our fn has no return value
|
||||
fn(nroot);
|
||||
if (nroot.left >= 0) traverse(pc, nroot.left, fn);
|
||||
if (nroot.right >= 0) traverse(pc, nroot.right, fn);
|
||||
} 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);
|
||||
|
||||
inline void build_tree(PointCloud &&pc, Builder &builder)
|
||||
{
|
||||
build_tree(pc, builder);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::branchingtree
|
||||
|
||||
#endif // POINTCLOUD_HPP
|
||||
|
@ -82,12 +82,11 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
|
||||
|
||||
if (floating_polylines.empty()) {
|
||||
// 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, 3.0);
|
||||
if (pc2 == Vec2d::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
|
||||
auto [pc1, pc2] = compute_principal_components(overhang_area);
|
||||
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
|
||||
return {Vec2d{1.0,0.0}, 0.0};
|
||||
} else {
|
||||
return {pc2.normalized(), 0.0};
|
||||
return {pc2.normalized().cast<double>(), 0.0};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -331,6 +331,7 @@ set(SLIC3R_SOURCES
|
||||
SLA/SupportTreeMesher.hpp
|
||||
SLA/SupportTreeMesher.cpp
|
||||
SLA/SupportTreeUtils.hpp
|
||||
SLA/SupportTreeUtilsLegacy.hpp
|
||||
SLA/SupportTreeBuilder.cpp
|
||||
SLA/SupportTree.hpp
|
||||
SLA/SupportTree.cpp
|
||||
|
@ -445,8 +445,11 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
|
||||
if (surface_fill.params.pattern == ipLightning)
|
||||
dynamic_cast<FillLightning::Filler*>(f.get())->generator = lightning_generator;
|
||||
if (surface_fill.params.pattern == ipLightning) {
|
||||
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) {
|
||||
FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get());
|
||||
|
@ -13,7 +13,7 @@ void Filler::_fill_surface_single(
|
||||
ExPolygon expolygon,
|
||||
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));
|
||||
|
||||
if (params.dont_connect() || fill_lines.size() <= 1) {
|
||||
|
@ -22,6 +22,7 @@ public:
|
||||
~Filler() override = default;
|
||||
|
||||
Generator *generator { nullptr };
|
||||
size_t num_raft_layers { 0 };
|
||||
protected:
|
||||
Fill* clone() const override { return new Filler(*this); }
|
||||
|
||||
|
@ -577,12 +577,22 @@ void Layer::sort_perimeters_into_islands(
|
||||
}
|
||||
if (! sample_set) {
|
||||
// If there is no infill, take a sample of some inner perimeter.
|
||||
for (uint32_t iperimeter : extrusions.first)
|
||||
if (const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter]; ! ee.role().is_external()) {
|
||||
for (uint32_t iperimeter : extrusions.first) {
|
||||
const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter];
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
loop_end:
|
||||
if (! sample_set) {
|
||||
if (! extrusions.second.empty()) {
|
||||
// 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 ®ion_config = this_layer_region.region().config();
|
||||
const auto bbox_eps = scaled<coord_t>(
|
||||
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 =
|
||||
[&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps]
|
||||
(const size_t lslice_idx, const Point &point) {
|
||||
|
@ -13,7 +13,9 @@ template<size_t N>
|
||||
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
|
||||
{
|
||||
long ret = 0;
|
||||
for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i);
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
ret += idx[i] * std::pow(gridsz, i);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <libslic3r/Optimize/Optimizer.hpp>
|
||||
#include "Optimizer.hpp"
|
||||
|
||||
namespace Slic3r { namespace opt {
|
||||
|
||||
@ -40,69 +40,163 @@ struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
|
||||
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>
|
||||
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
|
||||
|
||||
struct NLopt { // Helper RAII class for nlopt_opt
|
||||
struct NLoptRAII { // Helper RAII class for nlopt_opt
|
||||
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)...);
|
||||
}
|
||||
|
||||
NLopt(const NLopt&) = delete;
|
||||
NLopt(NLopt&&) = delete;
|
||||
NLopt& operator=(const NLopt&) = delete;
|
||||
NLopt& operator=(NLopt&&) = delete;
|
||||
NLoptRAII(const NLoptRAII&) = delete;
|
||||
NLoptRAII(NLoptRAII&&) = delete;
|
||||
NLoptRAII& operator=(const NLoptRAII&) = 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.
|
||||
template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> {
|
||||
protected:
|
||||
return fn;
|
||||
}
|
||||
|
||||
// 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;
|
||||
OptDir m_dir;
|
||||
StopCriteria m_loc_stopcr;
|
||||
OptDir m_dir = OptDir::MIN;
|
||||
|
||||
template<class Fn> using TOptData =
|
||||
std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
|
||||
static constexpr double ConstraintEps = 1e-6;
|
||||
|
||||
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>
|
||||
static double optfunc(unsigned n, const double *params,
|
||||
double *gradient,
|
||||
void *data)
|
||||
double *gradient, 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())
|
||||
nlopt_force_stop(std::get<2>(*tdata));
|
||||
if (tdata->self->m_stopcr.stop_condition())
|
||||
nlopt_force_stop(tdata->opt_raw);
|
||||
|
||||
auto fnptr = std::get<0>(*tdata);
|
||||
auto funval = to_arr<N>(params);
|
||||
|
||||
double scoreval = 0.;
|
||||
using RetT = decltype((*fnptr)(funval));
|
||||
using RetT = decltype(tdata->fn(funval));
|
||||
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];
|
||||
scoreval = score.score;
|
||||
} else {
|
||||
scoreval = (*fnptr)(funval);
|
||||
scoreval = tdata->fn(funval);
|
||||
}
|
||||
|
||||
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>
|
||||
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;
|
||||
|
||||
@ -114,23 +208,45 @@ protected:
|
||||
nlopt_set_lower_bounds(nl.ptr, lb.data());
|
||||
nlopt_set_upper_bounds(nl.ptr, ub.data());
|
||||
|
||||
double abs_diff = m_stopcr.abs_score_diff();
|
||||
double rel_diff = m_stopcr.rel_score_diff();
|
||||
double stopval = m_stopcr.stop_score();
|
||||
double abs_diff = stopcr.abs_score_diff();
|
||||
double rel_diff = stopcr.rel_score_diff();
|
||||
double stopval = stopcr.stop_score();
|
||||
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(stopval)) nlopt_set_stopval(nl.ptr, stopval);
|
||||
|
||||
if(m_stopcr.max_iterations() > 0)
|
||||
nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations());
|
||||
if(stopcr.max_iterations() > 0)
|
||||
nlopt_set_maxeval(nl.ptr, stopcr.max_iterations());
|
||||
}
|
||||
|
||||
template<class Fn, size_t N>
|
||||
Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals)
|
||||
template<class Fn, size_t N, class...EqFns, class...IneqFns>
|
||||
Result<N> optimize(NLoptRAII &nl, Fn &&fn, const Input<N> &initvals,
|
||||
const std::tuple<EqFns...> &equalities,
|
||||
const std::tuple<IneqFns...> &inequalities)
|
||||
{
|
||||
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) {
|
||||
case OptDir::MIN:
|
||||
@ -147,51 +263,81 @@ protected:
|
||||
|
||||
public:
|
||||
|
||||
template<class Func, size_t N>
|
||||
Result<N> optimize(Func&& func,
|
||||
template<class Fn, size_t N, class...EqFns, class...IneqFns>
|
||||
Result<N> optimize(Fn&& f,
|
||||
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};
|
||||
set_up(nl, bounds);
|
||||
if constexpr (IsAUGLAG<M>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
|
||||
assert(false);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
explicit NLoptOpt(const StopCriteria &stopcr_glob = {})
|
||||
: m_stopcr(stopcr_glob)
|
||||
{}
|
||||
|
||||
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
|
||||
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); }
|
||||
};
|
||||
|
||||
template<nlopt_algorithm glob, nlopt_algorithm loc>
|
||||
class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>>
|
||||
{
|
||||
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} {}
|
||||
template<class Alg> struct AlgFeatures_ {
|
||||
static constexpr bool SupportsInequalities = false;
|
||||
static constexpr bool SupportsEqualities = false;
|
||||
};
|
||||
|
||||
} // 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.
|
||||
template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
|
||||
detail::NLoptOpt<M> m_opt;
|
||||
@ -201,12 +347,24 @@ public:
|
||||
Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); 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,
|
||||
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) {}
|
||||
@ -219,14 +377,56 @@ public:
|
||||
const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
|
||||
|
||||
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
|
||||
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
||||
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
||||
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
||||
using AlgNLoptCobyla = detail::NLoptAlg<NLOPT_LN_COBYLA>;
|
||||
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
|
||||
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>;
|
||||
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
|
||||
|
||||
|
@ -12,6 +12,15 @@
|
||||
|
||||
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.
|
||||
template<size_t N> struct Result {
|
||||
int resultcode; // Method dependent
|
||||
@ -79,7 +88,7 @@ public:
|
||||
|
||||
double stop_score() const { return m_stop_score; }
|
||||
|
||||
StopCriteria & max_iterations(double val)
|
||||
StopCriteria & max_iterations(unsigned val)
|
||||
{
|
||||
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 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
|
||||
// behavior.
|
||||
//
|
||||
// Func can return a score of type double or optionally a ScoreGradient
|
||||
// class to indicate the function gradient for a optimization methods that
|
||||
// make use of the gradient.
|
||||
template<class Func, size_t N>
|
||||
template<class Func, size_t N, class...EqConstraints, class...IneqConstraints>
|
||||
Result<N> optimize(Func&& /*func*/,
|
||||
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:
|
||||
void seed(long /*s*/) {}
|
||||
|
@ -146,6 +146,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
|
||||
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) {
|
||||
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_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())
|
||||
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)
|
||||
{
|
||||
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.
|
||||
return false;
|
||||
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)
|
||||
{
|
||||
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.
|
||||
return false;
|
||||
auto &condition = preset.preset.compatible_printers_condition();
|
||||
@ -495,12 +506,14 @@ static std::vector<std::string> s_Preset_sla_print_options {
|
||||
"faded_layers",
|
||||
"supports_enable",
|
||||
"support_tree_type",
|
||||
|
||||
"support_head_front_diameter",
|
||||
"support_head_penetration",
|
||||
"support_head_width",
|
||||
"support_pillar_diameter",
|
||||
"support_small_pillar_diameter_percent",
|
||||
"support_max_bridges_on_pillar",
|
||||
"support_max_weight_on_model",
|
||||
"support_pillar_connection_mode",
|
||||
"support_buildplate_only",
|
||||
"support_enforcers_only",
|
||||
@ -512,6 +525,25 @@ static std::vector<std::string> s_Preset_sla_print_options {
|
||||
"support_max_bridge_length",
|
||||
"support_max_pillar_link_distance",
|
||||
"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_minimal_distance",
|
||||
"slice_closing_radius",
|
||||
@ -1165,6 +1197,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
|
||||
if (opt)
|
||||
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
|
||||
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) {
|
||||
bool selected = idx_preset == m_idx_selected;
|
||||
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);
|
||||
if (selected)
|
||||
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.
|
||||
if (m_idx_selected >= m_num_default_presets && m_default_suppressed)
|
||||
for (size_t i = 0; i < m_num_default_presets; ++ i)
|
||||
@ -2079,6 +2134,25 @@ namespace PresetUtils {
|
||||
}
|
||||
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 Slic3r
|
||||
|
@ -34,6 +34,7 @@ public:
|
||||
Semver config_version;
|
||||
std::string config_update_url;
|
||||
std::string changelog_url;
|
||||
bool templates_profile { false };
|
||||
|
||||
struct PrinterVariant {
|
||||
PrinterVariant() {}
|
||||
@ -52,6 +53,7 @@ public:
|
||||
// Vendor & Printer Model specific print bed model & texture.
|
||||
std::string bed_model;
|
||||
std::string bed_texture;
|
||||
std::string thumbnail;
|
||||
|
||||
PrinterVariant* variant(const std::string &name) {
|
||||
for (auto &v : this->variants)
|
||||
@ -619,6 +621,7 @@ namespace PresetUtils {
|
||||
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
|
||||
std::string system_printer_bed_model(const Preset& preset);
|
||||
std::string system_printer_bed_texture(const Preset& preset);
|
||||
bool vendor_profile_has_all_resources(const VendorProfile& vp);
|
||||
} // namespace PresetUtils
|
||||
|
||||
|
||||
|
@ -159,6 +159,7 @@ void PresetBundle::setup_directories()
|
||||
data_dir,
|
||||
data_dir / "vendor",
|
||||
data_dir / "cache",
|
||||
data_dir / "cache" / "vendor",
|
||||
data_dir / "shapes",
|
||||
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
|
||||
// Store the print/filament/printer presets into a "presets" directory.
|
||||
@ -1307,17 +1308,26 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
|
||||
try {
|
||||
pt::read_ini(ifs, tree);
|
||||
} 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;
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) {
|
||||
auto vp = VendorProfile::from_ini(tree, path);
|
||||
if (vp.models.size() == 0) {
|
||||
VendorProfile vp;
|
||||
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;
|
||||
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;
|
||||
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:")) {
|
||||
presets = &this->filaments;
|
||||
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:")) {
|
||||
presets = &this->sla_prints;
|
||||
preset_name = section.first.substr(10);
|
||||
|
@ -3,53 +3,97 @@
|
||||
|
||||
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)
|
||||
{
|
||||
// USING UNSCALED VALUES
|
||||
const Vec2d pixel_size = Vec2d(unscaled_resolution, unscaled_resolution);
|
||||
const auto bb = get_extents(polys);
|
||||
const Vec2i pixel_count = unscaled(bb.size()).cwiseQuotient(pixel_size).cast<int>() + Vec2i::Ones();
|
||||
|
||||
std::vector<Linef> lines{};
|
||||
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;
|
||||
|
||||
// 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)
|
||||
{
|
||||
// based on the following guide:
|
||||
// Denote the vertices of S by a, b, c. Then the map
|
||||
// g:(u,v)↦a+u(b−a)+v(c−a) ,
|
||||
// which in coordinates appears as
|
||||
// g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(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)=∫S′f(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]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) .
|
||||
// Therefore we can write
|
||||
// ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(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};
|
||||
};
|
||||
|
||||
double pixel_area = pixel_size.x() * pixel_size.y();
|
||||
Vec2d centroid_accumulator = Vec2d::Zero();
|
||||
Vec2d second_moment_of_area_accumulator = Vec2d::Zero();
|
||||
double second_moment_of_area_covariance_accumulator = 0.0;
|
||||
double area = 0.0;
|
||||
// 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 (int x = 0; x < pixel_count.x(); x++) {
|
||||
for (int y = 0; y < pixel_count.y(); y++) {
|
||||
Vec2d position = unscaled(bb.min) + pixel_size.cwiseProduct(Vec2d{x, y});
|
||||
if (is_inside(position)) {
|
||||
area += pixel_area;
|
||||
centroid_accumulator += pixel_area * position;
|
||||
second_moment_of_area_accumulator += pixel_area * position.cwiseProduct(position);
|
||||
second_moment_of_area_covariance_accumulator += pixel_area * position.x() * position.y();
|
||||
}
|
||||
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) {
|
||||
return {Vec2d::Zero(), Vec2d::Zero()};
|
||||
return {Vec2f::Zero(), Vec2f::Zero()};
|
||||
}
|
||||
|
||||
Vec2d centroid = centroid_accumulator / area;
|
||||
Vec2d variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid);
|
||||
Vec2f centroid = centroid_accumulator / area;
|
||||
Vec2f variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid);
|
||||
double covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y();
|
||||
#if 0
|
||||
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;
|
||||
#endif
|
||||
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()) {
|
||||
return {std::get<1>(result), std::get<0>(result)};
|
||||
} 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
|
||||
// Eigenvector for eigenvalue l is any vector v such that Cv = lv
|
||||
|
||||
double eigenvalue_a = 0.5 * (variance.x() + variance.y() +
|
||||
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance));
|
||||
double eigenvalue_b = 0.5 * (variance.x() + variance.y() -
|
||||
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance));
|
||||
Vec2d eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0};
|
||||
Vec2d eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0};
|
||||
float eigenvalue_a = 0.5f * (variance.x() + variance.y() +
|
||||
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance));
|
||||
float eigenvalue_b = 0.5f * (variance.x() + variance.y() -
|
||||
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance));
|
||||
Vec2f eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0f};
|
||||
Vec2f eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0f};
|
||||
|
||||
#if 0
|
||||
std::cout << "eigenvalue_a: " << eigenvalue_a << std::endl;
|
||||
|
@ -9,8 +9,15 @@
|
||||
|
||||
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
|
||||
std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, const double unscaled_resolution);
|
||||
std::tuple<Vec2f, Vec2f> compute_principal_components(const Polygons &polys);
|
||||
|
||||
}
|
||||
|
||||
|
@ -1201,7 +1201,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
}
|
||||
// Invalidate just the supports step.
|
||||
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));
|
||||
}
|
||||
if (supports_differ) {
|
||||
|
@ -181,7 +181,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAMaterialSpeed);
|
||||
|
||||
static inline const t_config_enum_values s_keys_map_SLASupportTreeType = {
|
||||
{"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);
|
||||
|
||||
@ -534,7 +535,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->category = L("Speed");
|
||||
def->tooltip = L("This setting enables dynamic speed control on overhangs.");
|
||||
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->full_label = L("Overhang overlap levels");
|
||||
@ -841,7 +842,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("extra_perimeters_on_overhangs", coBool);
|
||||
def->label = L("Extra perimeters on overhangs (Experimental)");
|
||||
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->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()));
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
ConfigOptionDef* def;
|
||||
@ -3613,90 +3828,12 @@ void PrintConfigDef::init_sla_params()
|
||||
def->enum_labels = ConfigOptionEnum<sla::SupportTreeType>::get_enum_names();
|
||||
def->enum_labels[0] = L("Default");
|
||||
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 = this->add("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("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));
|
||||
init_sla_support_params("");
|
||||
init_sla_support_params("branching");
|
||||
|
||||
def = this->add("support_enforcers_only", coBool);
|
||||
def->label = L("Support only in enforced regions");
|
||||
@ -3705,93 +3842,6 @@ void PrintConfigDef::init_sla_params()
|
||||
def->mode = comSimple;
|
||||
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->label = L("Support points density");
|
||||
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); }
|
||||
|
||||
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
|
||||
|
||||
#include <cereal/types/polymorphic.hpp>
|
||||
|
@ -185,6 +185,7 @@ private:
|
||||
void init_fff_params();
|
||||
void init_extruder_option_keys();
|
||||
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_retract_keys;
|
||||
@ -861,6 +862,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
// Generate only ground facing supports
|
||||
((ConfigOptionBool, support_buildplate_only))
|
||||
|
||||
((ConfigOptionFloat, support_max_weight_on_model))
|
||||
|
||||
// Generate only ground facing supports
|
||||
((ConfigOptionBool, support_enforcers_only))
|
||||
|
||||
@ -892,6 +895,62 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
// and the model object's bounding box bottom. Units in mm.
|
||||
((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:
|
||||
((ConfigOptionInt, support_points_density_relative))
|
||||
((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 SLAPrinterConfig &cfg);
|
||||
|
||||
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config);
|
||||
|
||||
// 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.
|
||||
// The counter is used for faster synchronization of the background slicing thread
|
||||
|
@ -789,10 +789,10 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
||||
|
||||
// propagate to dependent steps
|
||||
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 });
|
||||
} else if (step == posPrepareInfill) {
|
||||
invalidated |= this->invalidate_steps({ posInfill, posIroning });
|
||||
invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch });
|
||||
} else if (step == posInfill) {
|
||||
invalidated |= this->invalidate_steps({ posIroning, posSupportSpotsSearch });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
|
@ -13,20 +13,28 @@
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
inline constexpr const auto &beam_ex_policy = ex_tbb;
|
||||
|
||||
class BranchingTreeBuilder: public branchingtree::Builder {
|
||||
SupportTreeBuilder &m_builder;
|
||||
const SupportableMesh &m_sm;
|
||||
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
|
||||
// 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;
|
||||
|
||||
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;
|
||||
@ -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:
|
||||
BranchingTreeBuilder(SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
@ -98,13 +144,24 @@ public:
|
||||
bool add_mesh_bridge(const branchingtree::Node &from,
|
||||
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
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Cannot route junction at " << j.pos.x()
|
||||
double glvl = ground_level(m_sm);
|
||||
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_subtree(j.id);
|
||||
discard_subtree_rescure(j.id);
|
||||
// discard_subtree(j.id);
|
||||
}
|
||||
|
||||
const std::vector<size_t>& unroutable_pinheads() const
|
||||
@ -113,6 +170,19 @@ public:
|
||||
}
|
||||
|
||||
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,
|
||||
@ -121,7 +191,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from,
|
||||
Vec3d fromd = from.pos.cast<double>(), tod = to.pos.cast<double>();
|
||||
double fromR = get_radius(from), toR = get_radius(to);
|
||||
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);
|
||||
|
||||
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}};
|
||||
|
||||
auto sd = m_sm.cfg.safety_distance_mm ;
|
||||
auto hit1 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam1, sd);
|
||||
auto hit2 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam2, sd);
|
||||
auto hit1 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam1, sd);
|
||||
auto hit2 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam2, sd);
|
||||
|
||||
bool ret = hit1.distance() > (tod - from1d).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,
|
||||
const branchingtree::Node &to)
|
||||
{
|
||||
bool ret = search_ground_route(ex_tbb, m_builder, m_sm,
|
||||
sla::Junction{from.pos.cast<double>(),
|
||||
get_radius(from)},
|
||||
get_radius(to)).first;
|
||||
bool ret = false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -171,17 +260,20 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
|
||||
bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
|
||||
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)};
|
||||
|
||||
auto anchor = m_sm.cfg.ground_facing_only ?
|
||||
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>());
|
||||
|
||||
if (anchor) {
|
||||
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.);
|
||||
|
||||
if (hit.distance() > distance(fromj.pos, toj.pos)) {
|
||||
@ -197,6 +289,70 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
|
||||
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)
|
||||
{
|
||||
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)
|
||||
if (h && h->is_valid()) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -240,15 +396,29 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s
|
||||
props.sampling_radius());
|
||||
|
||||
auto bedpts = branchingtree::sample_bed(props.bed_shape(),
|
||||
props.ground_level(),
|
||||
float(props.ground_level()),
|
||||
props.sampling_radius());
|
||||
|
||||
for (auto &bp : bedpts)
|
||||
bp.Rmin = sm.cfg.head_back_radius_mm;
|
||||
|
||||
branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts),
|
||||
std::move(leafs), props};
|
||||
|
||||
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);
|
||||
|
||||
build_pillars(builder, vbuilder, sm);
|
||||
|
||||
for (size_t id : vbuilder.unroutable_pinheads())
|
||||
builder.head(id).invalidate();
|
||||
|
||||
|
@ -340,14 +340,17 @@ bool DefaultSupportTree::connect_to_nearpillar(const Head &head,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DefaultSupportTree::create_ground_pillar(const Vec3d &hjp,
|
||||
bool DefaultSupportTree::create_ground_pillar(const Junction &hjp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id)
|
||||
{
|
||||
auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy,
|
||||
m_builder, m_sm, hjp,
|
||||
sourcedir, radius, radius,
|
||||
m_builder,
|
||||
m_sm,
|
||||
hjp.pos,
|
||||
sourcedir,
|
||||
hjp.r,
|
||||
hjp.r,
|
||||
head_id);
|
||||
|
||||
if (pillar_id >= 0) // Save the pillar endpoint in the spatial index
|
||||
@ -369,7 +372,6 @@ void DefaultSupportTree::add_pinheads()
|
||||
PtIndices filtered_indices;
|
||||
filtered_indices.reserve(aliases.size());
|
||||
m_iheads.reserve(aliases.size());
|
||||
m_iheadless.reserve(aliases.size());
|
||||
for(auto& a : aliases) {
|
||||
// Here we keep only the front point of the cluster.
|
||||
filtered_indices.emplace_back(a.front());
|
||||
@ -438,8 +440,7 @@ void DefaultSupportTree::add_pinheads()
|
||||
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||
|
||||
// check available distance
|
||||
AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r,
|
||||
back_r, w);
|
||||
AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, back_r, w);
|
||||
|
||||
if (t.distance() < w) {
|
||||
// Let's try to optimize this angle, there might be a
|
||||
@ -587,7 +588,7 @@ void DefaultSupportTree::routing_to_ground()
|
||||
|
||||
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)
|
||||
<< "Pillar cannot be created for support point id: " << hid;
|
||||
m_iheads_onmodel.emplace_back(h.id);
|
||||
@ -599,7 +600,7 @@ void DefaultSupportTree::routing_to_ground()
|
||||
// sidepoints with the cluster centroid (which is a ground pillar)
|
||||
// or a nearby pillar if the centroid is unreachable.
|
||||
size_t ci = 0;
|
||||
for (auto cl : m_pillar_clusters) {
|
||||
for (const std::vector<unsigned> &cl : m_pillar_clusters) {
|
||||
m_thr();
|
||||
|
||||
auto cidx = cl_centroids[ci++];
|
||||
@ -615,10 +616,9 @@ void DefaultSupportTree::routing_to_ground()
|
||||
|
||||
if (!connect_to_nearpillar(sidehead, centerpillarID) &&
|
||||
!search_pillar_and_connect(sidehead)) {
|
||||
Vec3d pstart = sidehead.junction_point();
|
||||
// Vec3d pend = Vec3d{pstart.x(), pstart.y(), gndlvl};
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef LEGACYSUPPORTTREE_HPP
|
||||
#define LEGACYSUPPORTTREE_HPP
|
||||
|
||||
#include "SupportTreeUtils.hpp"
|
||||
#include "SupportTreeUtilsLegacy.hpp"
|
||||
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/Execution/ExecutionTBB.hpp>
|
||||
@ -62,7 +62,6 @@ class DefaultSupportTree {
|
||||
|
||||
PtIndices m_iheads; // support points with pinhead
|
||||
PtIndices m_iheads_onmodel;
|
||||
PtIndices m_iheadless; // headless support points
|
||||
|
||||
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.
|
||||
// sourcedir is the allowed direction of an optional bridge between the
|
||||
// jp junction and the final pillar.
|
||||
bool create_ground_pillar(const Vec3d &jp,
|
||||
bool create_ground_pillar(const Junction &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id = SupportTreeNode::ID_UNSET);
|
||||
|
||||
void add_pillar_base(long pid)
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <libslic3r/I18N.hpp>
|
||||
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#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);
|
||||
|
||||
if (sm.cfg.enabled) {
|
||||
Benchmark bench;
|
||||
bench.start();
|
||||
|
||||
switch (sm.cfg.tree_type) {
|
||||
case SupportTreeType::Default: {
|
||||
create_default_tree(*builder, sm);
|
||||
@ -39,9 +44,18 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm,
|
||||
create_branching_tree(*builder, sm);
|
||||
break;
|
||||
}
|
||||
case SupportTreeType::Organic: {
|
||||
// TODO
|
||||
}
|
||||
default:;
|
||||
}
|
||||
|
||||
bench.stop();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Support tree creation took: "
|
||||
<< bench.getElapsedSec()
|
||||
<< " seconds";
|
||||
|
||||
builder->merge_and_cleanup(); // clean metadata, leave only the meshes.
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ struct SupportTreeConfig
|
||||
// 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
|
||||
// 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.
|
||||
double base_radius_mm = 2.0;
|
||||
@ -77,11 +77,19 @@ struct SupportTreeConfig
|
||||
|
||||
unsigned max_bridges_on_pillar = 3;
|
||||
|
||||
double max_weight_on_model_support = 10.f;
|
||||
|
||||
double head_fullwidth() const {
|
||||
return 2 * head_front_radius_mm + head_width_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)
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
@ -89,13 +97,15 @@ struct SupportTreeConfig
|
||||
// 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;
|
||||
|
||||
// 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 max_solo_pillar_height_mm = 15.0;
|
||||
static const double constexpr max_dual_pillar_height_mm = 35.0;
|
||||
static const double constexpr optimizer_rel_score_diff = 1e-6;
|
||||
static const unsigned constexpr optimizer_max_iterations = 1000;
|
||||
static const double constexpr optimizer_rel_score_diff = 1e-10;
|
||||
static const unsigned constexpr optimizer_max_iterations = 2000;
|
||||
static const unsigned constexpr pillar_cascade_neighbors = 3;
|
||||
|
||||
};
|
||||
@ -116,11 +126,11 @@ struct SupportableMesh
|
||||
: emesh{trmsh}, pts{sp}, cfg{c}
|
||||
{}
|
||||
|
||||
explicit SupportableMesh(const AABBMesh &em,
|
||||
const SupportPoints &sp,
|
||||
const SupportTreeConfig &c)
|
||||
: emesh{em}, pts{sp}, cfg{c}
|
||||
{}
|
||||
// explicit SupportableMesh(const AABBMesh &em,
|
||||
// const SupportPoints &sp,
|
||||
// const SupportTreeConfig &c)
|
||||
// : emesh{em}, pts{sp}, cfg{c}
|
||||
// {}
|
||||
};
|
||||
|
||||
inline double ground_level(const SupportableMesh &sm)
|
||||
|
@ -67,6 +67,14 @@ struct SupportTreeNode
|
||||
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
|
||||
struct Head: public SupportTreeNode {
|
||||
Vec3d dir = DOWN;
|
||||
@ -77,7 +85,6 @@ struct Head: public SupportTreeNode {
|
||||
double width_mm = 2;
|
||||
double penetration_mm = 0.5;
|
||||
|
||||
|
||||
// If there is a pillar connecting to this head, then the id will be set.
|
||||
long pillar_id = ID_UNSET;
|
||||
|
||||
@ -104,20 +111,20 @@ struct Head: public SupportTreeNode {
|
||||
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
|
||||
{
|
||||
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
|
||||
// point is given, as it would allow the pillar to be angled.
|
||||
// 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)
|
||||
: 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)
|
||||
|
@ -165,7 +165,8 @@ indexed_triangle_set halfcone(double baseheight,
|
||||
{
|
||||
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;
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
enum class SupportTreeType { Default, Branching };
|
||||
enum class SupportTreeType { Default, Branching, Organic };
|
||||
enum class PillarConnectionMode { zigzag, cross, dynamic };
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
@ -6,10 +6,13 @@
|
||||
|
||||
#include <libslic3r/Execution/Execution.hpp>
|
||||
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
|
||||
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
|
||||
#include <libslic3r/MeshNormals.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
@ -23,36 +26,12 @@ using Slic3r::opt::AlgNLoptGenetic;
|
||||
using Slic3r::Geometry::dir_to_spheric;
|
||||
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
|
||||
// method based on:
|
||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
||||
template<size_t N>
|
||||
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
|
||||
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
|
||||
@ -72,7 +51,7 @@ class PointRing {
|
||||
|
||||
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
|
||||
// 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
|
||||
{
|
||||
double phi = m_phis[idx];
|
||||
if (idx == 0)
|
||||
return src;
|
||||
|
||||
double phi = m_phis[idx - 1];
|
||||
double sinphi = std::sin(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
|
||||
struct Ball { Vec3d p; double R; };
|
||||
|
||||
struct Beam { // Defines a set of rays displaced along a cone's surface
|
||||
static constexpr size_t SAMPLES = 8;
|
||||
template<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 dir;
|
||||
double r1;
|
||||
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):
|
||||
src{s}, dir{d}, r1{R1}, r2{R2} {};
|
||||
Beam_(const Vec3d &s, const Vec3d &d, double R1, double R2)
|
||||
: src{s}, dir{d}, r1{R1}, r2{R2} {};
|
||||
|
||||
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}
|
||||
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}
|
||||
{
|
||||
r2 = src_ball.R +
|
||||
(dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p);
|
||||
r2 = src_ball.R;
|
||||
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}
|
||||
{}
|
||||
};
|
||||
|
||||
template<class Ex>
|
||||
Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
|
||||
using Beam = Beam_<>;
|
||||
|
||||
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 dst = src + beam.dir;
|
||||
@ -164,15 +155,15 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
|
||||
|
||||
Vec3d D = (dst - src);
|
||||
Vec3d dir = D.normalized();
|
||||
PointRing<Beam::SAMPLES> ring{dir};
|
||||
PointRing<RayCount> ring{dir};
|
||||
|
||||
using Hit = AABBMesh::hit_result;
|
||||
|
||||
// Hit results
|
||||
std::array<Hit, Beam::SAMPLES> hits;
|
||||
std::array<Hit, RayCount> hits;
|
||||
|
||||
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) {
|
||||
Hit &hit = hits[i];
|
||||
|
||||
@ -193,7 +184,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
|
||||
}
|
||||
} else
|
||||
hit = hr;
|
||||
}, std::min(execution::max_concurrency(ex), Beam::SAMPLES));
|
||||
}, std::min(execution::max_concurrency(policy), RayCount));
|
||||
|
||||
return min_hit(hits.begin(), hits.end());
|
||||
}
|
||||
@ -208,7 +199,10 @@ Hit pinhead_mesh_hit(Ex ex,
|
||||
double width,
|
||||
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
|
||||
// inner surface of the mesh.
|
||||
@ -291,193 +285,6 @@ Hit pinhead_mesh_hit(Ex ex,
|
||||
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)
|
||||
{
|
||||
return (a.pos - b.pos).norm();
|
||||
@ -554,19 +361,17 @@ bool optimize_pinhead_placement(Ex policy,
|
||||
// Reassemble the now corrected normal
|
||||
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||
|
||||
double sd = back_r * m.cfg.safety_distance_mm /
|
||||
m.cfg.head_back_radius_mm;
|
||||
double sd = m.cfg.safety_distance(back_r);
|
||||
|
||||
// check available distance
|
||||
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w,
|
||||
sd);
|
||||
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd);
|
||||
|
||||
if (t.distance() < w) {
|
||||
// Let's try to optimize this angle, there might be a
|
||||
// viable normal that doesn't collide with the model
|
||||
// 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
|
||||
|
||||
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)) {
|
||||
head.id = suppt_idx;
|
||||
head.id = long(suppt_idx);
|
||||
|
||||
return head;
|
||||
}
|
||||
@ -635,81 +440,335 @@ std::optional<Head> calculate_pinhead_placement(Ex policy,
|
||||
return {};
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
std::pair<bool, long> connect_to_ground(Ex policy,
|
||||
SupportTreeBuilder &builder,
|
||||
struct GroundConnection {
|
||||
// Currently, a ground connection will contain at most 2 additional junctions
|
||||
// which will not require any allocations. If I come up with an algo that
|
||||
// can produce a route to ground with more junctions, this design will be
|
||||
// able to handle that.
|
||||
static constexpr size_t MaxExpectedJunctions = 3;
|
||||
|
||||
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 Junction &j,
|
||||
const Vec3d &dir,
|
||||
double end_r)
|
||||
const GroundConnection &conn)
|
||||
{
|
||||
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));
|
||||
long ret = SupportTreeNode::ID_UNSET;
|
||||
|
||||
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);
|
||||
if (!conn)
|
||||
return ret;
|
||||
|
||||
while (d < t &&
|
||||
!std::isinf(tdown = beam_mesh_hit(policy, sm.emesh,
|
||||
Beam{hjp + d * dir, DOWN, r, r2}, sd)
|
||||
.distance())) {
|
||||
d += r;
|
||||
auto it = conn.path.begin();
|
||||
auto itnx = std::next(it);
|
||||
|
||||
while (itnx != conn.path.end()) {
|
||||
builder.add_diffbridge(*it, *itnx);
|
||||
builder.add_junction(*itnx);
|
||||
++it; ++itnx;
|
||||
}
|
||||
|
||||
if(!std::isinf(tdown))
|
||||
return {false, SupportTreeNode::ID_UNSET};
|
||||
auto gp = conn.path.back().pos;
|
||||
gp.z() = ground_level(sm);
|
||||
double h = conn.path.back().pos.z() - gp.z();
|
||||
|
||||
Vec3d endp = hjp + d * dir;
|
||||
auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r);
|
||||
if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) {
|
||||
h += sm.pad_cfg.wall_thickness_mm;
|
||||
gp.z() -= sm.pad_cfg.wall_thickness_mm;
|
||||
}
|
||||
|
||||
if (ret.second >= 0) {
|
||||
builder.add_bridge(hjp, endp, r);
|
||||
builder.add_junction(endp, r);
|
||||
// TODO: does not work yet
|
||||
// if (conn.path.back().id < 0) {
|
||||
// // 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;
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
std::pair<bool, long> search_ground_route(Ex policy,
|
||||
SupportTreeBuilder &builder,
|
||||
// Searching a ground connection from an arbitrary source point.
|
||||
// Currently, the result will contain one avoidance bridge (at most) and a
|
||||
// pillar to the ground, if it's feasible
|
||||
template<class Ex, class WideningFn,
|
||||
class = std::enable_if_t<IsWideningFn<WideningFn>> >
|
||||
GroundConnection deepsearch_ground_connection(
|
||||
Ex policy,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &j,
|
||||
const Junction &source,
|
||||
WideningFn &&wideningfn,
|
||||
const Vec3d &init_dir = DOWN)
|
||||
{
|
||||
constexpr unsigned MaxIterationsGlobal = 5000;
|
||||
constexpr unsigned MaxIterationsLocal = 100;
|
||||
constexpr double RelScoreDiff = 0.05;
|
||||
|
||||
const auto gndlvl = ground_level(sm);
|
||||
|
||||
// The used solver (AlgNLoptMLSL_Subplx search method) is composed of a global (MLSL)
|
||||
// and a local (Subplex) search method. Criteria can be set in a way that
|
||||
// local searches are quick and less accurate. The global method will only
|
||||
// consider the max iteration number and the stop score (Z level <= ground)
|
||||
|
||||
auto criteria = get_criteria(sm.cfg); // get defaults from cfg
|
||||
criteria.max_iterations(MaxIterationsGlobal);
|
||||
criteria.abs_score_diff(NaNd);
|
||||
criteria.rel_score_diff(NaNd);
|
||||
criteria.stop_score(gndlvl);
|
||||
|
||||
auto criteria_loc = criteria;
|
||||
criteria_loc.max_iterations(MaxIterationsLocal);
|
||||
criteria_loc.abs_score_diff(EPSILON);
|
||||
criteria_loc.rel_score_diff(RelScoreDiff);
|
||||
|
||||
Optimizer<opt::AlgNLoptMLSL_Subplx> solver(criteria);
|
||||
solver.set_loc_criteria(criteria_loc);
|
||||
solver.seed(0); // require repeatability
|
||||
|
||||
// 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 downdst = j.pos.z() - ground_level(sm);
|
||||
double gndlvl = ground_level(sm);
|
||||
auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) {
|
||||
if (len < EPSILON)
|
||||
return src.R;
|
||||
|
||||
auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius);
|
||||
if (res.first)
|
||||
return res;
|
||||
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;
|
||||
|
||||
// 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);
|
||||
return r;
|
||||
};
|
||||
|
||||
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg).stop_score(1e6));
|
||||
solver.seed(0); // we want deterministic behavior
|
||||
static_assert(IsWideningFn<decltype(wfn)>, "Not a widening function");
|
||||
|
||||
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} })
|
||||
);
|
||||
return deepsearch_ground_connection(policy, sm, source, wfn, init_dir);
|
||||
}
|
||||
|
||||
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
||||
struct DefaultWideningModel {
|
||||
static constexpr double WIDENING_SCALE = 0.02;
|
||||
const SupportableMesh &sm;
|
||||
|
||||
return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius);
|
||||
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>
|
||||
@ -729,8 +788,7 @@ bool optimize_anchor_placement(Ex policy,
|
||||
double lmax = std::min(sm.cfg.head_width_mm,
|
||||
distance(from.pos, anchor.pos) - 2 * from.r);
|
||||
|
||||
double sd = anchor.r_back_mm * sm.cfg.safety_distance_mm /
|
||||
sm.cfg.head_back_radius_mm;
|
||||
double sd = sm.cfg.safety_distance(anchor.r_back_mm);
|
||||
|
||||
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg)
|
||||
.stop_score(anchor.fullwidth())
|
||||
@ -796,6 +854,92 @@ std::optional<Anchor> calculate_anchor_placement(Ex policy,
|
||||
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
|
||||
|
||||
#endif // SLASUPPORTTREEUTILS_H
|
||||
|
300
src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp
Normal file
300
src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp
Normal 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
|
@ -40,6 +40,9 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c)
|
||||
|
||||
scfg.enabled = c.supports_enable.getBool();
|
||||
scfg.tree_type = c.support_tree_type.value;
|
||||
|
||||
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;
|
||||
@ -62,6 +65,38 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c)
|
||||
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;
|
||||
}
|
||||
@ -822,6 +857,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||
|| opt_key == "supports_enable"
|
||||
|| opt_key == "support_tree_type"
|
||||
|| opt_key == "support_object_elevation"
|
||||
|| opt_key == "branchingsupport_object_elevation"
|
||||
|| opt_key == "pad_around_object"
|
||||
|| opt_key == "pad_around_object_everywhere"
|
||||
|| 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_widening_factor"
|
||||
|| opt_key == "support_small_pillar_diameter_percent"
|
||||
|| opt_key == "support_max_weight_on_model"
|
||||
|| opt_key == "support_max_bridges_on_pillar"
|
||||
|| opt_key == "support_pillar_connection_mode"
|
||||
|| 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_pillar_link_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"
|
||||
) {
|
||||
steps.emplace_back(slaposSupportTree);
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "SupportSpotsGenerator.hpp"
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
@ -7,6 +8,7 @@
|
||||
#include "Line.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "PrincipalComponents2D.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "PrintBase.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
@ -117,13 +119,14 @@ public:
|
||||
|
||||
size_t to_cell_index(const Vec3i &cell_coords) const
|
||||
{
|
||||
#ifdef DETAILED_DEBUG_LOGS
|
||||
assert(cell_coords.x() >= 0);
|
||||
assert(cell_coords.x() < cell_count.x());
|
||||
assert(cell_coords.y() >= 0);
|
||||
assert(cell_coords.y() < cell_count.y());
|
||||
assert(cell_coords.z() >= 0);
|
||||
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();
|
||||
}
|
||||
|
||||
@ -244,6 +247,7 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
||||
|
||||
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,
|
||||
prev_layer_lines, flow_width,
|
||||
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) :
|
||||
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;
|
||||
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(b−a)+v(c−a) ,
|
||||
// which in coordinates appears as
|
||||
// g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(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)=∫S′f(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]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) .
|
||||
// Therefore we can write
|
||||
// ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(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 connection;
|
||||
|
||||
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;
|
||||
|
||||
ExPolygons below_polys{};
|
||||
for (const auto &link : slice.overlaps_below) { below_polys.push_back(lower_layer->lslices[link.slice_idx]); }
|
||||
ExPolygons overlap = intersection_ex({slice_poly}, below_polys);
|
||||
ExPolygons below{};
|
||||
for (const auto &link : slice.overlaps_below) { below.push_back(lower_layer->lslices[link.slice_idx]); }
|
||||
Polygons below_polys = to_polygons(below);
|
||||
|
||||
BoundingBox below_bb = get_extents(below_polys);
|
||||
|
||||
Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb),
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb));
|
||||
|
||||
for (const Polygon &poly : overlap) {
|
||||
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;
|
||||
|
||||
std::vector<Vec2f> triangles = triangulate_expolygons_2f(overlap);
|
||||
for (size_t idx = 0; idx < triangles.size(); idx += 3) {
|
||||
auto [area, first_moment_of_area, second_moment_area,
|
||||
second_moment_of_area_covariance] = compute_triangle_moments_of_area(triangles[idx], triangles[idx + 1], triangles[idx + 2]);
|
||||
connection.area += area;
|
||||
connection.centroid_accumulator += Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area);
|
||||
connection.second_moment_of_area_accumulator += second_moment_area;
|
||||
connection.second_moment_of_area_covariance_accumulator += second_moment_of_area_covariance;
|
||||
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;
|
||||
@ -973,6 +933,9 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms)
|
||||
std::vector<ExtrusionLine> current_layer_lines;
|
||||
for (const LayerRegion *layer_region : l->regions()) {
|
||||
for (const ExtrusionEntity *extrusion : layer_region->perimeters().flatten().entities) {
|
||||
|
||||
if (!extrusion->role().is_external_perimeter()) continue;
|
||||
|
||||
Points extrusion_pts;
|
||||
extrusion->collect_points(extrusion_pts);
|
||||
float flow_width = get_flow_width(layer_region, extrusion->role());
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
@ -26,6 +28,8 @@
|
||||
namespace Slic3r::FFFTreeSupport
|
||||
{
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
// or warning
|
||||
// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL()
|
||||
#define error_level_not_in_cache error
|
||||
@ -336,7 +340,7 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI
|
||||
return (*result).get();
|
||||
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!";
|
||||
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);
|
||||
return getCollision(orig_radius, layer_idx, min_xy_dist);
|
||||
@ -351,7 +355,7 @@ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerInde
|
||||
return (*result).get();
|
||||
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!";
|
||||
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 });
|
||||
return getCollisionHolefree(radius, layer_idx);
|
||||
@ -375,10 +379,10 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI
|
||||
if (m_precalculated) {
|
||||
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!";
|
||||
tree_supports_show_error("Not precalculated Avoidance(to model) requested.", false);
|
||||
tree_supports_show_error("Not precalculated Avoidance(to model) requested."sv, false);
|
||||
} 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!";
|
||||
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);
|
||||
@ -396,7 +400,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L
|
||||
return (*result).get();
|
||||
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!";
|
||||
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);
|
||||
return getPlaceableAreas(orig_radius, layer_idx);
|
||||
@ -422,7 +426,7 @@ const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius,
|
||||
tree_supports_show_error(
|
||||
min_xy_dist ?
|
||||
"Not precalculated Wall restriction of minimum xy distance requested )." :
|
||||
"Not precalculated Wall restriction requested )."
|
||||
"Not precalculated Wall restriction requested )."sv
|
||||
, false);
|
||||
}
|
||||
const_cast<TreeModelVolumes*>(this)->calculateWallRestrictions({ radius, layer_idx });
|
||||
|
@ -58,6 +58,7 @@ enum class LineStatus
|
||||
|
||||
using LineInformation = std::vector<std::pair<Point, LineStatus>>;
|
||||
using LineInformations = std::vector<LineInformation>;
|
||||
using namespace std::literals;
|
||||
|
||||
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
|
||||
|
||||
//todo Remove! Only relevant for public BETA!
|
||||
static bool inline g_showed_critical_error = 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!!
|
||||
|
||||
#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);
|
||||
(critical ? g_showed_critical_error : g_showed_performance_warning) = true;
|
||||
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);
|
||||
#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...
|
||||
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";
|
||||
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)
|
||||
// This case should never happen, but better safe than sorry.
|
||||
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);
|
||||
if (safe_step_size < 0 || last_step_offset_without_check < 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
if (!mesh_config.support_rests_on_model && !to_bp) {
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
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 <<
|
||||
" 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;
|
||||
tree_supports_show_error("Potentially lost branch!", true);
|
||||
tree_supports_show_error("Potentially lost branch!"sv, true);
|
||||
} else
|
||||
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);
|
||||
@ -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 (area(elem.areas.influence_areas) < tiny_area_threshold) {
|
||||
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.
|
||||
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);
|
||||
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;
|
||||
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));
|
||||
}
|
||||
@ -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.
|
||||
if (! elem.state.result_on_layer_is_set()) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
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;
|
||||
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;
|
||||
set_to_model_contact_simple(first_elem);
|
||||
} else {
|
||||
@ -2494,7 +2494,7 @@ static void create_nodes_from_area(
|
||||
if (elem.state.to_buildplate) {
|
||||
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;
|
||||
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
|
||||
elem.state.deleted = true;
|
||||
|
@ -17,7 +17,7 @@
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#define TREE_SUPPORT_SHOW_ERRORS
|
||||
// #define TREE_SUPPORT_SHOW_ERRORS
|
||||
|
||||
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
|
||||
// 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 message, bool critical);
|
||||
void tree_supports_show_error(std::string_view message, bool critical);
|
||||
|
||||
} // namespace FFFTreeSupport
|
||||
|
||||
|
@ -385,6 +385,8 @@ inline IntegerOnly<I, I> fast_round_up(double a)
|
||||
return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5));
|
||||
}
|
||||
|
||||
template<class T> using SamePair = std::pair<T, T>;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // _libslic3r_h_
|
||||
|
@ -374,23 +374,43 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
|
||||
bool is_default_tree = treetype == sla::SupportTreeType::Default;
|
||||
bool is_branching_tree = treetype == sla::SupportTreeType::Branching;
|
||||
|
||||
toggle_field("support_head_front_diameter", supports_en);
|
||||
toggle_field("support_head_penetration", supports_en);
|
||||
toggle_field("support_head_width", supports_en);
|
||||
toggle_field("support_pillar_diameter", supports_en);
|
||||
toggle_field("support_small_pillar_diameter_percent", supports_en);
|
||||
toggle_field("support_tree_type", supports_en);
|
||||
|
||||
toggle_field("support_head_front_diameter", supports_en && is_default_tree);
|
||||
toggle_field("support_head_penetration", supports_en && is_default_tree);
|
||||
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_pillar_connection_mode", supports_en && is_default_tree);
|
||||
toggle_field("support_tree_type", supports_en);
|
||||
toggle_field("support_buildplate_only", supports_en);
|
||||
toggle_field("support_buildplate_only", supports_en && is_default_tree);
|
||||
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_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_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_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;
|
||||
|
||||
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_around_object_everywhere", zero_elev);
|
||||
toggle_field("pad_object_connector_stride", zero_elev);
|
||||
|
@ -73,10 +73,10 @@ using Config::SnapshotDB;
|
||||
|
||||
// Configuration data structures extensions needed for the wizard
|
||||
|
||||
bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle)
|
||||
bool Bundle::load(fs::path source_path, BundleLocation location, bool ais_prusa_bundle)
|
||||
{
|
||||
this->preset_bundle = std::make_unique<PresetBundle>();
|
||||
this->is_in_resources = ais_in_resources;
|
||||
this->location = location;
|
||||
this->is_prusa_bundle = ais_prusa_bundle;
|
||||
|
||||
std::string path_string = source_path.string();
|
||||
@ -104,7 +104,7 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
|
||||
Bundle::Bundle(Bundle &&other)
|
||||
: preset_bundle(std::move(other.preset_bundle))
|
||||
, vendor_profile(other.vendor_profile)
|
||||
, is_in_resources(other.is_in_resources)
|
||||
, location(other.location)
|
||||
, is_prusa_bundle(other.is_prusa_bundle)
|
||||
{
|
||||
other.vendor_profile = nullptr;
|
||||
@ -115,25 +115,35 @@ BundleMap BundleMap::load()
|
||||
BundleMap res;
|
||||
|
||||
const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred();
|
||||
const auto archive_dir = (boost::filesystem::path(Slic3r::data_dir()) / "cache" / "vendor").make_preferred();
|
||||
const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
||||
|
||||
// Load Prusa bundle from the datadir/vendor directory or from datadir/cache/vendor (archive) or from resources/profiles.
|
||||
auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
|
||||
auto prusa_bundle_rsrc = false;
|
||||
BundleLocation prusa_bundle_loc = BundleLocation::IN_VENDOR;
|
||||
if (! boost::filesystem::exists(prusa_bundle_path)) {
|
||||
prusa_bundle_path = (archive_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
|
||||
prusa_bundle_loc = BundleLocation::IN_ARCHIVE;
|
||||
}
|
||||
if (!boost::filesystem::exists(prusa_bundle_path)) {
|
||||
prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
|
||||
prusa_bundle_rsrc = true;
|
||||
prusa_bundle_loc = BundleLocation::IN_RESOURCES;
|
||||
}
|
||||
{
|
||||
Bundle prusa_bundle;
|
||||
if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true))
|
||||
if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_loc, true))
|
||||
res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle));
|
||||
}
|
||||
|
||||
// Load the other bundles in the datadir/vendor directory
|
||||
// and then additionally from resources/profiles.
|
||||
bool is_in_resources = false;
|
||||
for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
|
||||
for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
|
||||
// and then additionally from datadir/cache/vendor (archive) and resources/profiles.
|
||||
// Should we concider case where archive has older profiles than resources (shouldnt happen)?
|
||||
typedef std::pair<const fs::path&, BundleLocation> DirData;
|
||||
std::vector<DirData> dir_list { {vendor_dir, BundleLocation::IN_VENDOR}, {archive_dir, BundleLocation::IN_ARCHIVE}, {rsrc_vendor_dir, BundleLocation::IN_RESOURCES} };
|
||||
for ( auto dir : dir_list) {
|
||||
if (!fs::exists(dir.first))
|
||||
continue;
|
||||
for (const auto &dir_entry : boost::filesystem::directory_iterator(dir.first)) {
|
||||
if (Slic3r::is_ini_file(dir_entry)) {
|
||||
std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part
|
||||
|
||||
@ -141,12 +151,10 @@ BundleMap BundleMap::load()
|
||||
if (res.find(id) != res.end()) { continue; }
|
||||
|
||||
Bundle bundle;
|
||||
if (bundle.load(dir_entry.path(), is_in_resources))
|
||||
if (bundle.load(dir_entry.path(), dir.second))
|
||||
res.emplace(std::move(id), std::move(bundle));
|
||||
}
|
||||
}
|
||||
|
||||
is_in_resources = true;
|
||||
}
|
||||
|
||||
return res;
|
||||
@ -220,29 +228,41 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt
|
||||
|
||||
bool is_variants = false;
|
||||
|
||||
const fs::path vendor_dir_path = (fs::path(Slic3r::data_dir()) / "vendor").make_preferred();
|
||||
const fs::path cache_dir_path = (fs::path(Slic3r::data_dir()) / "cache").make_preferred();
|
||||
const fs::path rsrc_dir_path = (fs::path(resources_dir()) / "profiles").make_preferred();
|
||||
|
||||
for (const auto &model : models) {
|
||||
if (! filter(model)) { continue; }
|
||||
|
||||
wxBitmap bitmap;
|
||||
int bitmap_width = 0;
|
||||
auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool {
|
||||
if (wxFileExists(bitmap_file)) {
|
||||
auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width) {
|
||||
bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG);
|
||||
bitmap_width = bitmap.GetWidth();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
|
||||
if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
|
||||
|
||||
bool found = false;
|
||||
for (const fs::path& res : { rsrc_dir_path / vendor.id / model.thumbnail
|
||||
, vendor_dir_path / vendor.id / model.thumbnail
|
||||
, cache_dir_path / vendor.id / model.thumbnail })
|
||||
{
|
||||
if (!fs::exists(res))
|
||||
continue;
|
||||
load_bitmap(GUI::from_u8(res.string()), bitmap, bitmap_width);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead")
|
||||
% (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png")
|
||||
% model.thumbnail
|
||||
% vendor.id
|
||||
% model.id;
|
||||
load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width);
|
||||
}
|
||||
}
|
||||
auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
|
||||
wxStaticText* title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
title->SetFont(font_name);
|
||||
const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width);
|
||||
title->Wrap(wrap_width);
|
||||
@ -255,7 +275,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt
|
||||
|
||||
titles.push_back(title);
|
||||
|
||||
auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
|
||||
wxStaticBitmap* bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
|
||||
bitmaps.push_back(bitmap_widget);
|
||||
|
||||
auto *variants_panel = new wxPanel(this);
|
||||
@ -278,7 +298,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt
|
||||
is_variants = true;
|
||||
}
|
||||
|
||||
auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
|
||||
Checkbox* cbox = new Checkbox(variants_panel, label, model_id, variant.name);
|
||||
i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox);
|
||||
|
||||
const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name);
|
||||
@ -620,6 +640,7 @@ void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
|
||||
|
||||
|
||||
const std::string PageMaterials::EMPTY;
|
||||
const std::string PageMaterials::TEMPLATES = "templates";
|
||||
|
||||
PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name)
|
||||
: ConfigWizardPage(parent, std::move(title), std::move(shortname))
|
||||
@ -727,6 +748,11 @@ void PageMaterials::reload_presets()
|
||||
clear();
|
||||
|
||||
list_printer->append(_L("(All)"), &EMPTY);
|
||||
|
||||
const AppConfig* app_config = wxGetApp().app_config;
|
||||
if (materials->technology == T_FFF && app_config->get("no_templates") == "0")
|
||||
list_printer->append(_L("(Templates)"), &TEMPLATES);
|
||||
|
||||
//list_printer->SetLabelMarkup("<b>bald</b>");
|
||||
for (const Preset* printer : materials->printers) {
|
||||
list_printer->append(printer->name, &printer->name);
|
||||
@ -758,8 +784,12 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
|
||||
const auto text_clr = wxGetApp().get_label_clr_default();
|
||||
const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
|
||||
const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
|
||||
wxString first_line = format_wxstr(_L("%1% marked with <b>*</b> are <b>not</b> compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
|
||||
wxString text;
|
||||
if (materials->technology == T_FFF && template_shown) {
|
||||
text = format_wxstr(_L("%1% visible for <b>(\"Template\")</b> printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
|
||||
} else {
|
||||
wxString first_line = format_wxstr(_L("%1% marked with <b>*</b> are <b>not</b> compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
|
||||
|
||||
if (all_printers) {
|
||||
wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
|
||||
text = wxString::Format(
|
||||
@ -780,7 +810,8 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
|
||||
, first_line
|
||||
, second_line
|
||||
);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
wxString second_line;
|
||||
if (!printer_names.empty())
|
||||
second_line = (materials->technology == T_FFF ?
|
||||
@ -819,6 +850,8 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
|
||||
"</html>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this));
|
||||
const int fs = font.GetPointSize();
|
||||
@ -863,6 +896,8 @@ void PageMaterials::on_material_highlighted(int sel_material)
|
||||
std::vector<std::string> names;
|
||||
for (const Preset* printer : materials->printers) {
|
||||
for (const Preset* material : matching_materials) {
|
||||
if (material->vendor && material->vendor->templates_profile)
|
||||
continue;
|
||||
if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) {
|
||||
names.push_back(printer->name);
|
||||
break;
|
||||
@ -880,6 +915,8 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
wxArrayInt sel_printers;
|
||||
int sel_printers_count = list_printer->GetSelections(sel_printers);
|
||||
|
||||
bool templates_available = list_printer->size() > 1 && list_printer->get_data(1) == TEMPLATES;
|
||||
|
||||
// Does our wxWidgets version support operator== for wxArrayInt ?
|
||||
// https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614
|
||||
#if wxCHECK_VERSION(3, 1, 1)
|
||||
@ -895,13 +932,14 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
};
|
||||
if (!are_equal(sel_printers, sel_printers_prev)) {
|
||||
#endif
|
||||
|
||||
template_shown = false;
|
||||
// Refresh type list
|
||||
list_type->Clear();
|
||||
list_type->append(_L("(All)"), &EMPTY);
|
||||
if (sel_printers_count > 0) {
|
||||
if (sel_printers_count > 1) {
|
||||
// If all is selected with other printers
|
||||
// unselect "all" or all printers depending on last value
|
||||
// same with "templates"
|
||||
if (sel_printers[0] == 0 && sel_printers_count > 1) {
|
||||
if (last_selected_printer == 0) {
|
||||
list_printer->SetSelection(wxNOT_FOUND);
|
||||
@ -911,7 +949,18 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
sel_printers_count = list_printer->GetSelections(sel_printers);
|
||||
}
|
||||
}
|
||||
if (sel_printers[0] != 0) {
|
||||
if (materials->technology == T_FFF && templates_available && (sel_printers[0] == 1 || sel_printers[1] == 1) && sel_printers_count > 1) {
|
||||
if (last_selected_printer == 1) {
|
||||
list_printer->SetSelection(wxNOT_FOUND);
|
||||
list_printer->SetSelection(1);
|
||||
}
|
||||
else if (last_selected_printer != 0) {
|
||||
list_printer->SetSelection(1, false);
|
||||
sel_printers_count = list_printer->GetSelections(sel_printers);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sel_printers_count > 0 && sel_printers[0] != 0 && ((materials->technology == T_FFF && templates_available && sel_printers[0] != 1) || materials->technology != T_FFF || !templates_available)) {
|
||||
for (int i = 0; i < sel_printers_count; i++) {
|
||||
const std::string& printer_name = list_printer->get_data(sel_printers[i]);
|
||||
const Preset* printer = nullptr;
|
||||
@ -921,20 +970,35 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
break;
|
||||
}
|
||||
}
|
||||
materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) {
|
||||
materials->filter_presets(printer, printer_name, EMPTY, EMPTY, [this](const Preset* p) {
|
||||
const std::string& type = this->materials->get_type(p);
|
||||
if (list_type->find(type) == wxNOT_FOUND) {
|
||||
list_type->append(type, &type);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else if (sel_printers_count > 0 && last_selected_printer == 0) {
|
||||
//clear selection except "ALL"
|
||||
list_printer->SetSelection(wxNOT_FOUND);
|
||||
list_printer->SetSelection(0);
|
||||
sel_printers_count = list_printer->GetSelections(sel_printers);
|
||||
|
||||
materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) {
|
||||
materials->filter_presets(nullptr, EMPTY, EMPTY, EMPTY, [this](const Preset* p) {
|
||||
const std::string& type = this->materials->get_type(p);
|
||||
if (list_type->find(type) == wxNOT_FOUND) {
|
||||
list_type->append(type, &type);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (materials->technology == T_FFF && templates_available && sel_printers_count > 0 && last_selected_printer == 1) {
|
||||
//clear selection except "TEMPLATES"
|
||||
list_printer->SetSelection(wxNOT_FOUND);
|
||||
list_printer->SetSelection(1);
|
||||
sel_printers_count = list_printer->GetSelections(sel_printers);
|
||||
template_shown = true;
|
||||
materials->filter_presets(nullptr, TEMPLATES, EMPTY, EMPTY,
|
||||
[this](const Preset* p) {
|
||||
const std::string& type = this->materials->get_type(p);
|
||||
if (list_type->find(type) == wxNOT_FOUND) {
|
||||
list_type->append(type, &type);
|
||||
@ -942,7 +1006,6 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
});
|
||||
}
|
||||
sort_list_data(list_type, true, true);
|
||||
}
|
||||
|
||||
sel_printers_prev = sel_printers;
|
||||
sel_type = 0;
|
||||
@ -971,7 +1034,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
break;
|
||||
}
|
||||
}
|
||||
materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) {
|
||||
materials->filter_presets(printer, printer_name, type, EMPTY, [this](const Preset* p) {
|
||||
const std::string& vendor = this->materials->get_vendor(p);
|
||||
if (list_vendor->find(vendor) == wxNOT_FOUND) {
|
||||
list_vendor->append(vendor, &vendor);
|
||||
@ -996,7 +1059,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) {
|
||||
const std::string& type = list_type->get_data(sel_type);
|
||||
const std::string& vendor = list_vendor->get_data(sel_vendor);
|
||||
// finst printer preset
|
||||
// first printer preset
|
||||
std::vector<ProfilePrintData> to_list;
|
||||
for (int i = 0; i < sel_printers_count; i++) {
|
||||
const std::string& printer_name = list_printer->get_data(sel_printers[i]);
|
||||
@ -1007,15 +1070,14 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
|
||||
materials->filter_presets(printer, printer_name, type, vendor, [this, &to_list](const Preset* p) {
|
||||
const std::string& section = materials->appconfig_section();
|
||||
bool checked = wizard_p()->appconfig_new.has(section, p->name);
|
||||
bool was_checked = false;
|
||||
|
||||
int cur_i = list_profile->find(p->alias);
|
||||
if (cur_i == wxNOT_FOUND) {
|
||||
cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
|
||||
cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) || template_shown ? "" : " *"), &p->alias);
|
||||
to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked);
|
||||
}
|
||||
else {
|
||||
@ -1053,10 +1115,15 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat
|
||||
|
||||
std::vector<std::reference_wrapper<const std::string>> prusa_profiles;
|
||||
std::vector<std::reference_wrapper<const std::string>> other_profiles;
|
||||
bool add_TEMPLATES_item = false;
|
||||
for (int i = 0 ; i < list->size(); ++i) {
|
||||
const std::string& data = list->get_data(i);
|
||||
if (data == EMPTY) // do not sort <all> item
|
||||
continue;
|
||||
if (data == TEMPLATES) {// do not sort <templates> item
|
||||
add_TEMPLATES_item = true;
|
||||
continue;
|
||||
}
|
||||
if (!material_type_ordering && data.find("Prusa") != std::string::npos)
|
||||
prusa_profiles.push_back(data);
|
||||
else
|
||||
@ -1095,10 +1162,13 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat
|
||||
list->Clear();
|
||||
if (add_All_item)
|
||||
list->append(_L("(All)"), &EMPTY);
|
||||
if (materials->technology == T_FFF && add_TEMPLATES_item)
|
||||
list->append(_L("(Templates)"), &TEMPLATES);
|
||||
for (const auto& item : prusa_profiles)
|
||||
list->append(item, &const_cast<std::string&>(item.get()));
|
||||
for (const auto& item : other_profiles)
|
||||
list->append(item, &const_cast<std::string&>(item.get()));
|
||||
|
||||
}
|
||||
|
||||
void PageMaterials::sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data)
|
||||
@ -1125,11 +1195,11 @@ void PageMaterials::sort_list_data(PresetList* list, const std::vector<ProfilePr
|
||||
});
|
||||
list->Clear();
|
||||
for (size_t i = 0; i < prusa_profiles.size(); ++i) {
|
||||
list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(prusa_profiles[i].name.get()));
|
||||
list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast<std::string&>(prusa_profiles[i].name.get()));
|
||||
list->Check(i, prusa_profiles[i].checked);
|
||||
}
|
||||
for (size_t i = 0; i < other_profiles.size(); ++i) {
|
||||
list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(other_profiles[i].name.get()));
|
||||
list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast<std::string&>(other_profiles[i].name.get()));
|
||||
list->Check(i + prusa_profiles.size(), other_profiles[i].checked);
|
||||
}
|
||||
}
|
||||
@ -1139,6 +1209,15 @@ void PageMaterials::select_material(int i)
|
||||
const bool checked = list_profile->IsChecked(i);
|
||||
|
||||
const std::string& alias_key = list_profile->get_data(i);
|
||||
if (checked && template_shown && !notification_shown) {
|
||||
notification_shown = true;
|
||||
wxString message = _L("You have selected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)");
|
||||
MessageDialog msg(this, message, _L("Notice"), wxYES_NO);
|
||||
if (msg.ShowModal() == wxID_NO) {
|
||||
list_profile->Check(i, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
|
||||
}
|
||||
|
||||
@ -1543,6 +1622,8 @@ PageVendors::PageVendors(ConfigWizard *parent)
|
||||
for (const auto &pair : wizard_p()->bundles) {
|
||||
const VendorProfile *vendor = pair.second.vendor_profile;
|
||||
if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
|
||||
if (vendor && vendor->templates_profile)
|
||||
continue;
|
||||
|
||||
auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name);
|
||||
cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) {
|
||||
@ -2199,11 +2280,17 @@ void ConfigWizard::priv::load_pages()
|
||||
|
||||
// Filaments & Materials
|
||||
if (any_fff_selected) { index->add_page(page_filaments); }
|
||||
// Filaments page if only custom printer is selected
|
||||
const AppConfig* app_config = wxGetApp().app_config;
|
||||
if (!any_fff_selected && (custom_printer_selected || custom_printer_in_bundle) && (app_config->get("no_templates") == "0")) {
|
||||
update_materials(T_ANY);
|
||||
index->add_page(page_filaments);
|
||||
}
|
||||
}
|
||||
if (any_sla_selected) { index->add_page(page_sla_materials); }
|
||||
|
||||
// there should to be selected at least one printer
|
||||
btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected);
|
||||
btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected || custom_printer_in_bundle);
|
||||
|
||||
index->add_page(page_update);
|
||||
index->add_page(page_downloader);
|
||||
@ -2265,6 +2352,13 @@ void ConfigWizard::priv::load_vendors()
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& printer : wxGetApp().preset_bundle->printers) {
|
||||
if (!printer.is_default && !printer.is_system && printer.is_visible) {
|
||||
custom_printer_in_bundle = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the is_visible flag in printer Presets
|
||||
for (auto &pair : bundles) {
|
||||
pair.second.preset_bundle->load_installed_printers(appconfig_new);
|
||||
@ -2386,7 +2480,7 @@ void ConfigWizard::priv::set_run_reason(RunReason run_reason)
|
||||
|
||||
void ConfigWizard::priv::update_materials(Technology technology)
|
||||
{
|
||||
if (any_fff_selected && (technology & T_FFF)) {
|
||||
if ((any_fff_selected || custom_printer_in_bundle || custom_printer_selected) && (technology & T_FFF)) {
|
||||
filaments.clear();
|
||||
aliases_fff.clear();
|
||||
// Iterate filaments in all bundles
|
||||
@ -2409,11 +2503,22 @@ void ConfigWizard::priv::update_materials(Technology technology)
|
||||
filaments.add_printer(&printer);
|
||||
}
|
||||
}
|
||||
|
||||
// template filament bundle has no printers - filament would be never added
|
||||
if(pair.second.vendor_profile&& pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end())
|
||||
{
|
||||
if (!filaments.containts(&filament)) {
|
||||
filaments.push(&filament);
|
||||
if (!filament.alias.empty())
|
||||
aliases_fff[filament.alias].insert(filament.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// count compatible printers
|
||||
for (const auto& preset : filaments.presets) {
|
||||
// skip template filaments
|
||||
if (preset->vendor && preset->vendor->templates_profile)
|
||||
continue;
|
||||
|
||||
const auto filter = [preset](const std::pair<std::string, size_t> element) {
|
||||
return preset->alias == element.first;
|
||||
@ -2421,17 +2526,19 @@ void ConfigWizard::priv::update_materials(Technology technology)
|
||||
if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) {
|
||||
continue;
|
||||
}
|
||||
// find all aliases (except templates)
|
||||
std::vector<size_t> idx_with_same_alias;
|
||||
for (size_t i = 0; i < filaments.presets.size(); ++i) {
|
||||
if (preset->alias == filaments.presets[i]->alias)
|
||||
if (preset->alias == filaments.presets[i]->alias && ((filaments.presets[i]->vendor && !filaments.presets[i]->vendor->templates_profile) || !filaments.presets[i]->vendor))
|
||||
idx_with_same_alias.push_back(i);
|
||||
}
|
||||
// check compatibility with each printer
|
||||
size_t counter = 0;
|
||||
for (const auto& printer : filaments.printers) {
|
||||
if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF)
|
||||
continue;
|
||||
bool compatible = false;
|
||||
// Test otrher materials with same alias
|
||||
// Test other materials with same alias
|
||||
for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
|
||||
const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]);
|
||||
const Preset& prntr = *printer;
|
||||
@ -2694,6 +2801,21 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
|
||||
has_material = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// find if preset.first is part of the templates profile (up is searching if preset.first is part of printer vendor preset)
|
||||
for (const auto& bp : bundles) {
|
||||
if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) {
|
||||
const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology);
|
||||
const Preset* template_material = template_materials.find_preset(preset.first, false);
|
||||
if (template_material && is_compatible_with_printer(PresetWithVendorProfile(*template_material, &bp.second.preset_bundle->vendors.begin()->second), PresetWithVendorProfile(printer, nullptr))) {
|
||||
has_material = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has_material)
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
if (! has_material)
|
||||
@ -2702,6 +2824,27 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
|
||||
}
|
||||
}
|
||||
}
|
||||
// todo: just workaround so template_profile_selected wont get to false after this function is called for SLA
|
||||
// this will work unltil there are no SLA template filaments
|
||||
if (technology == ptFFF) {
|
||||
// template_profile_selected check
|
||||
template_profile_selected = false;
|
||||
for (const auto& bp : bundles) {
|
||||
if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) {
|
||||
for (const auto& preset : appconfig_presets) {
|
||||
const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology);
|
||||
const Preset* template_material = template_materials.find_preset(preset.first, false);
|
||||
if (template_material){
|
||||
template_profile_selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (template_profile_selected) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id);
|
||||
return printer_models_without_material;
|
||||
};
|
||||
@ -2852,10 +2995,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
if (!check_unsaved_preset_changes)
|
||||
act_btns |= ActionButtons::SAVE;
|
||||
|
||||
// Install bundles from resources if needed:
|
||||
// Install bundles from resources or cache / vendor if needed:
|
||||
std::vector<std::string> install_bundles;
|
||||
for (const auto &pair : bundles) {
|
||||
if (! pair.second.is_in_resources) { continue; }
|
||||
if (pair.second.location == BundleLocation::IN_VENDOR) { continue; }
|
||||
|
||||
if (pair.second.is_prusa_bundle) {
|
||||
// Always install Prusa bundle, because it has a lot of filaments/materials
|
||||
@ -2865,7 +3008,14 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
}
|
||||
|
||||
const auto vendor = enabled_vendors.find(pair.first);
|
||||
if (vendor == enabled_vendors.end()) { continue; }
|
||||
if (vendor == enabled_vendors.end()) {
|
||||
// vendor not found
|
||||
// if templates vendor and needs to be installed, add it
|
||||
// then continue
|
||||
if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile)
|
||||
install_bundles.emplace_back(pair.first);
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t size_sum = 0;
|
||||
for (const auto &model : vendor->second) { size_sum += model.second.size(); }
|
||||
@ -2915,12 +3065,14 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
return false;
|
||||
|
||||
if (install_bundles.size() > 0) {
|
||||
// Install bundles from resources.
|
||||
// Install bundles from resources or cache / vendor.
|
||||
// Don't create snapshot - we've already done that above if applicable.
|
||||
if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
|
||||
|
||||
bool install_result = updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false);
|
||||
if (!install_result)
|
||||
return false;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
|
||||
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources or cache / vendor";
|
||||
}
|
||||
|
||||
if (page_welcome->reset_user_profile()) {
|
||||
|
@ -60,18 +60,25 @@ enum Technology {
|
||||
T_ANY = ~0,
|
||||
};
|
||||
|
||||
enum BundleLocation{
|
||||
IN_VENDOR,
|
||||
IN_ARCHIVE,
|
||||
IN_RESOURCES
|
||||
};
|
||||
|
||||
struct Bundle
|
||||
{
|
||||
std::unique_ptr<PresetBundle> preset_bundle;
|
||||
VendorProfile* vendor_profile{ nullptr };
|
||||
bool is_in_resources{ false };
|
||||
//bool is_in_resources{ false };
|
||||
BundleLocation location;
|
||||
bool is_prusa_bundle{ false };
|
||||
|
||||
Bundle() = default;
|
||||
Bundle(Bundle&& other);
|
||||
|
||||
// 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; }
|
||||
};
|
||||
@ -84,74 +91,8 @@ struct BundleMap : std::map<std::string /* = vendor ID */, Bundle>
|
||||
const Bundle& prusa_bundle() const;
|
||||
};
|
||||
|
||||
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;
|
||||
struct Materials;
|
||||
|
||||
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;
|
||||
@ -344,6 +285,10 @@ struct PageMaterials: ConfigWizardPage
|
||||
std::string empty_printers_label;
|
||||
bool first_paint = { false };
|
||||
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 } ;
|
||||
|
||||
PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name);
|
||||
@ -368,6 +313,82 @@ struct PageMaterials: ConfigWizardPage
|
||||
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
|
||||
{
|
||||
PageCustom(ConfigWizard *parent);
|
||||
@ -608,9 +629,11 @@ struct ConfigWizard::priv
|
||||
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_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)
|
||||
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;
|
||||
wxBoxSizer *hscroll_sizer = nullptr;
|
||||
|
@ -163,6 +163,8 @@ void FileGet::priv::get_perform()
|
||||
m_stopped = true;
|
||||
fclose(file);
|
||||
cancel = true;
|
||||
if (m_written == 0)
|
||||
std::remove(m_tmp_path.string().c_str());
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED);
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
|
@ -808,6 +808,7 @@ void GUI_App::post_init()
|
||||
// Configuration is not compatible and reconfigure was refused by the user. Application is closing.
|
||||
return;
|
||||
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();
|
||||
this->preset_updater->sync(preset_bundle);
|
||||
this->app_version_check(false);
|
||||
@ -1248,7 +1249,7 @@ bool GUI_App::on_init_inner()
|
||||
std::string evt_string = into_u8(evt.GetString());
|
||||
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) {
|
||||
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
|
||||
, Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string)
|
||||
, _u8L("See Releases page.")
|
||||
@ -1282,6 +1283,7 @@ bool GUI_App::on_init_inner()
|
||||
#endif // __WXMSW__
|
||||
}
|
||||
|
||||
std::string delayed_error_load_presets;
|
||||
// Suppress the '- default -' presets.
|
||||
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
|
||||
try {
|
||||
@ -1290,7 +1292,7 @@ bool GUI_App::on_init_inner()
|
||||
// installation of a compatible system preset, thus nullifying the system preset substitutions.
|
||||
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(nullptr, ex.what());
|
||||
delayed_error_load_presets = ex.what();
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
@ -1307,6 +1309,9 @@ bool GUI_App::on_init_inner()
|
||||
if (scrn && is_editor())
|
||||
scrn->SetText(_L("Preparing settings tabs") + dots);
|
||||
|
||||
if (!delayed_error_load_presets.empty())
|
||||
show_error(nullptr, delayed_error_load_presets);
|
||||
|
||||
mainframe = new MainFrame();
|
||||
// hide settings tabs after first Layout
|
||||
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");
|
||||
|
||||
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)
|
||||
return false;
|
||||
}
|
||||
@ -3189,6 +3197,7 @@ bool GUI_App::check_updates(const bool verbose)
|
||||
{
|
||||
PresetUpdater::UpdateResult updater_result;
|
||||
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);
|
||||
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
|
||||
mainframe->Close();
|
||||
@ -3300,6 +3309,19 @@ void GUI_App::on_version_read(wxCommandEvent& evt)
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// notification
|
||||
|
@ -558,8 +558,8 @@ void MenuFactory::append_menu_items_add_volume(MenuType menu_type)
|
||||
menu->Destroy(range_id);
|
||||
|
||||
if (wxGetApp().get_mode() == comSimple) {
|
||||
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::MODEL_PART, 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), "",
|
||||
[](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); },
|
||||
|
@ -2480,6 +2480,9 @@ bool ObjectList::is_splittable(bool to_objects)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wxGetApp().get_mode() == comSimple)
|
||||
return false; // suppress to split to parts for simple mode
|
||||
|
||||
ModelVolume* volume;
|
||||
if (!get_volume_by_item(item, volume) || !volume)
|
||||
return false;
|
||||
|
@ -47,7 +47,6 @@
|
||||
#define ALLOW_ADD_FONT_BY_OS_SELECTOR
|
||||
#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_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_OFFSET_DURING_DRAGGING // when drag with text over surface visualize used center
|
||||
#define SHOW_IMGUI_ATLAS
|
||||
@ -403,7 +402,7 @@ Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model)
|
||||
if (!fix.has_value())
|
||||
return res;
|
||||
|
||||
return res * (*fix);
|
||||
return res * fix->inverse();
|
||||
}
|
||||
|
||||
Transform3d priv::world_matrix(const Selection &selection)
|
||||
@ -861,6 +860,9 @@ void GLGizmoEmboss::on_set_state()
|
||||
if (m_volume == nullptr) {
|
||||
// reopen gizmo when new object is created
|
||||
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
|
||||
create_volume(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
@ -1830,6 +1832,9 @@ void GLGizmoEmboss::init_font_name_texture() {
|
||||
face.cancel = 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)
|
||||
@ -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);
|
||||
if (ImGui::BeginCombo("##font_selector", selected)) {
|
||||
if (!m_face_names.is_init) init_face_names();
|
||||
if (m_face_names.texture_id == 0) init_font_name_texture();
|
||||
ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_CharsUppercase;
|
||||
|
||||
// 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) {
|
||||
const wxString &wx_face_name = face.wx_name;
|
||||
size_t index = &face - &m_face_names.faces.front();
|
||||
|
||||
// Filter for face names
|
||||
if (m_face_names.hide[index])
|
||||
continue;
|
||||
|
||||
ImGui::PushID(index);
|
||||
ScopeGuard sg([]() { ImGui::PopID(); });
|
||||
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));
|
||||
}
|
||||
}
|
||||
// tooltip as full name of font face
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", wx_face_name.ToUTF8().data());
|
||||
if (is_selected) ImGui::SetItemDefaultFocus();
|
||||
|
||||
// on first draw set focus on selected font
|
||||
if (set_selection_focus && is_selected)
|
||||
ImGui::SetScrollHereY();
|
||||
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();
|
||||
|
||||
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.search.clear();
|
||||
m_face_names.hide.clear();
|
||||
// cancel all process for generation of texture
|
||||
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));
|
||||
m_face_names.texture_id = 0;
|
||||
}
|
||||
|
@ -270,6 +270,10 @@ private:
|
||||
// hash created from enumerated font from OS
|
||||
// check when new font was installed
|
||||
size_t hash = 0;
|
||||
|
||||
// filtration pattern
|
||||
std::string search = "";
|
||||
std::vector<bool> hide; // result of filtration
|
||||
} m_face_names;
|
||||
static bool store(const Facenames &facenames);
|
||||
static bool load(Facenames &facenames);
|
||||
|
@ -517,14 +517,6 @@ bool GLGizmoFdmSupports::has_backend_supports()
|
||||
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()
|
||||
{
|
||||
ModelObject *mo = m_c->selection_info()->model_object();
|
||||
@ -549,8 +541,10 @@ void GLGizmoFdmSupports::auto_generate()
|
||||
}
|
||||
}
|
||||
|
||||
wxGetApp().CallAfter([this]() {
|
||||
wxGetApp().plater()->reslice_FFF_until_step(posSupportSpotsSearch, *m_c->selection_info()->model_object(), false);
|
||||
this->waiting_for_autogenerated_supports = true;
|
||||
wxGetApp().CallAfter([this]() { reslice_FDM_supports(); });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,6 @@ private:
|
||||
|
||||
bool waiting_for_autogenerated_supports = false;
|
||||
bool has_backend_supports();
|
||||
void reslice_FDM_supports(bool postpone_error_messages = false) const;
|
||||
void auto_generate();
|
||||
void apply_data_from_backend();
|
||||
};
|
||||
|
@ -2246,6 +2246,29 @@ void NotificationManager::push_simplify_suggestion_notification(const std::stri
|
||||
notification->object_id = object_id;
|
||||
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)
|
||||
{
|
||||
for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) {
|
||||
|
@ -56,6 +56,7 @@ enum class NotificationType
|
||||
// Like NewAppAvailable but with text and link for alpha / bet release
|
||||
NewAlphaAvailable,
|
||||
NewBetaAvailable,
|
||||
NoNewReleaseAvailable,
|
||||
// Notification on the start of PrusaSlicer, when updates of system profiles are detected.
|
||||
// Contains a hyperlink to execute installation of the new system profiles.
|
||||
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.
|
||||
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*)>());
|
||||
// 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.
|
||||
// living_oids is expected to be sorted.
|
||||
void remove_simplify_suggestion_of_released_objects(const std::vector<ObjectID>& living_oids);
|
||||
|
@ -553,12 +553,12 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
|
||||
const bool supports_enable = selection == _("None") ? false : true;
|
||||
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"))
|
||||
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"))
|
||||
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")) {
|
||||
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()) {
|
||||
//wxMessageDialog msg_dlg(q, _L(
|
||||
MessageDialog msg_dlg(q, _L(
|
||||
"This file contains several objects positioned at multiple heights.\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)) {
|
||||
//wxMessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n",
|
||||
if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
|
||||
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) {
|
||||
Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
|
||||
view3D->set_as_dirty();
|
||||
@ -6147,7 +6145,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
|
||||
break;
|
||||
}
|
||||
case ProjectDropDialog::LoadType::LoadGeometry: {
|
||||
Plater::TakeSnapshot snapshot(this, _L("Import Object"));
|
||||
// Plater::TakeSnapshot snapshot(this, _L("Import Object"));
|
||||
load_files({ *it }, true, false);
|
||||
break;
|
||||
}
|
||||
|
@ -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."),
|
||||
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",
|
||||
L("Show incompatible print and filament presets"),
|
||||
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);
|
||||
#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" };
|
||||
|
||||
for (const std::string& option : options_to_recreate_GUI) {
|
||||
@ -761,6 +768,9 @@ void PreferencesDialog::accept(wxEvent&)
|
||||
|
||||
wxGetApp().update_ui_from_settings();
|
||||
clear_cache();
|
||||
|
||||
if (update_filament_sidebar)
|
||||
wxGetApp().plater()->sidebar().update_presets(Preset::Type::TYPE_FILAMENT);
|
||||
}
|
||||
|
||||
void PreferencesDialog::revert(wxEvent&)
|
||||
|
@ -798,7 +798,7 @@ void PlaterPresetComboBox::show_edit_menu()
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
std::map<wxString, wxBitmapBundle*> nonsys_presets;
|
||||
std::map<wxString, wxBitmapBundle*> template_presets;
|
||||
|
||||
wxString selected_user_preset;
|
||||
wxString tooltip;
|
||||
@ -883,11 +884,19 @@ void PlaterPresetComboBox::update()
|
||||
|
||||
const std::string name = preset.alias.empty() ? preset.name : preset.alias;
|
||||
if (preset.is_default || preset.is_system) {
|
||||
if (preset.vendor && preset.vendor->templates_profile) {
|
||||
template_presets.emplace(get_preset_name(preset), bmp);
|
||||
if (is_selected) {
|
||||
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
|
||||
{
|
||||
nonsys_presets.emplace(get_preset_name(preset), bmp);
|
||||
@ -899,6 +908,8 @@ void PlaterPresetComboBox::update()
|
||||
if (i + 1 == m_collection->num_default_presets())
|
||||
set_label_marker(Append(separator(L("System presets")), NullBitmapBndl()));
|
||||
}
|
||||
|
||||
|
||||
if (!nonsys_presets.empty())
|
||||
{
|
||||
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)
|
||||
{
|
||||
// add Physical printers, if any exists
|
||||
@ -1046,6 +1066,8 @@ void TabPresetComboBox::update()
|
||||
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>> template_presets;
|
||||
|
||||
wxString selected = "";
|
||||
if (!presets.front().is_visible)
|
||||
set_label_marker(Append(separator(L("System presets")), NullBitmapBndl()));
|
||||
@ -1079,11 +1101,19 @@ void TabPresetComboBox::update()
|
||||
assert(bmp);
|
||||
|
||||
if (preset.is_default || preset.is_system) {
|
||||
if (preset.vendor && preset.vendor->templates_profile) {
|
||||
template_presets.emplace(get_preset_name(preset), std::pair<wxBitmapBundle*, bool>(bmp, is_enabled));
|
||||
if (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
|
||||
{
|
||||
std::pair<wxBitmapBundle*, bool> pair(bmp, is_enabled);
|
||||
@ -1094,6 +1124,7 @@ void TabPresetComboBox::update()
|
||||
if (i + 1 == m_collection->num_default_presets())
|
||||
set_label_marker(Append(separator(L("System presets")), NullBitmapBndl()));
|
||||
}
|
||||
|
||||
if (!nonsys_presets.empty())
|
||||
{
|
||||
set_label_marker(Append(separator(L("User presets")), NullBitmapBndl()));
|
||||
@ -1106,6 +1137,18 @@ void TabPresetComboBox::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, 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)
|
||||
{
|
||||
// add Physical printers, if any exists
|
||||
|
@ -285,17 +285,17 @@ void SavePresetDialog::Item::Enable(bool enable /*= true*/)
|
||||
// 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)
|
||||
{
|
||||
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),
|
||||
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)
|
||||
@ -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__)
|
||||
// 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()); });
|
||||
|
||||
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);
|
||||
|
||||
SetSizer(topSizer);
|
||||
@ -371,6 +380,15 @@ std::string SavePresetDialog::get_name(Preset::Type type)
|
||||
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
|
||||
{
|
||||
for (const Item* item : m_items)
|
||||
|
@ -76,6 +76,7 @@ private:
|
||||
wxStaticText* m_label {nullptr};
|
||||
wxBoxSizer* m_radio_sizer {nullptr};
|
||||
ActionType m_action {UndefAction};
|
||||
wxCheckBox* m_template_filament_checkbox {nullptr};
|
||||
|
||||
std::string m_ph_printer_name;
|
||||
std::string m_old_preset_name;
|
||||
@ -88,8 +89,8 @@ public:
|
||||
|
||||
const wxString& get_info_line_extention() { return m_info_line_extention; }
|
||||
|
||||
SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = "");
|
||||
SavePresetDialog(wxWindow* parent, std::vector<Preset::Type> types, std::string suffix = "", PresetBundle* preset_bundle = nullptr);
|
||||
SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = "", bool template_filament = false);
|
||||
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() override;
|
||||
|
||||
@ -105,12 +106,13 @@ public:
|
||||
bool Layout() override;
|
||||
bool is_for_rename() { return m_use_for_rename; }
|
||||
|
||||
bool get_template_filament_checkbox();
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
void on_sys_color_changed() override {}
|
||||
|
||||
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 accept();
|
||||
};
|
||||
|
@ -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)
|
||||
{
|
||||
std::string slatree = is_fff ? "" : get_sla_suptree_prefix(config);
|
||||
|
||||
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
|
||||
! config.opt_bool(support) ?
|
||||
_("None") :
|
||||
@ -1082,7 +1085,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
|
||||
if (is_fff ?
|
||||
(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));
|
||||
|
||||
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 ?
|
||||
//! 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()) {
|
||||
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)
|
||||
return;
|
||||
name = dlg.get_name();
|
||||
if (from_template)
|
||||
from_template = dlg.get_template_filament_checkbox();
|
||||
}
|
||||
|
||||
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)
|
||||
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.
|
||||
// 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);
|
||||
@ -4821,6 +4851,60 @@ void TabSLAMaterial::update()
|
||||
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()
|
||||
{
|
||||
m_presets = &m_preset_bundle->sla_prints;
|
||||
@ -4833,42 +4917,14 @@ void TabSLAPrint::build()
|
||||
optgroup->append_single_option_line("faded_layers");
|
||||
|
||||
page = add_options_page(L("Supports"), "support"/*"sla_supports"*/);
|
||||
|
||||
optgroup = page->new_optgroup(L("Supports"));
|
||||
optgroup->append_single_option_line("supports_enable");
|
||||
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_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
|
||||
optgroup->append_single_option_line("support_object_elevation");
|
||||
build_sla_support_params({{"", "Default"}, {"branching", "Branching"}}, page);
|
||||
|
||||
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->append_single_option_line("support_points_density_relative");
|
||||
|
@ -526,6 +526,12 @@ public:
|
||||
|
||||
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:
|
||||
TabSLAPrint(wxBookCtrlBase* parent) :
|
||||
Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {}
|
||||
|
@ -323,6 +323,11 @@ void AppUpdater::priv::parse_version_string(const std::string& body)
|
||||
return;
|
||||
#endif // 0
|
||||
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;
|
||||
}
|
||||
std::string tree_string = body.substr(start);
|
||||
|
@ -12,14 +12,17 @@
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/UpdateDialogs.hpp"
|
||||
@ -49,7 +52,7 @@ namespace Slic3r {
|
||||
static const char *INDEX_FILENAME = "index.idx";
|
||||
static const char *TMP_EXTENSION = ".download";
|
||||
|
||||
|
||||
namespace {
|
||||
void copy_file_fix(const fs::path &source, const fs::path &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;
|
||||
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
|
||||
{
|
||||
fs::path source;
|
||||
@ -144,6 +161,7 @@ struct PresetUpdater::priv
|
||||
std::string version_check_url;
|
||||
|
||||
fs::path cache_path;
|
||||
fs::path cache_vendor_path;
|
||||
fs::path rsrc_path;
|
||||
fs::path vendor_path;
|
||||
|
||||
@ -155,19 +173,25 @@ struct PresetUpdater::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;
|
||||
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;
|
||||
Updates get_config_updates(const Semver& old_slic3r_version) const;
|
||||
bool perform_updates(Updates &&updates, bool snapshot = true) const;
|
||||
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()
|
||||
: cache_path(fs::path(Slic3r::data_dir()) / "cache")
|
||||
, cache_vendor_path(cache_path / "vendor")
|
||||
, rsrc_path(fs::path(resources_dir()) / "profiles")
|
||||
, vendor_path(fs::path(Slic3r::data_dir()) / "vendor")
|
||||
, cancel(false)
|
||||
@ -179,8 +203,13 @@ PresetUpdater::priv::priv()
|
||||
index_db = Index::load_db();
|
||||
}
|
||||
|
||||
void PresetUpdater::priv::update_index_db()
|
||||
{
|
||||
index_db = Index::load_db();
|
||||
}
|
||||
|
||||
// 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";
|
||||
version_check_url = app_config->version_check_url();
|
||||
@ -232,45 +261,207 @@ 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.
|
||||
// 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";
|
||||
|
||||
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:
|
||||
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());
|
||||
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;
|
||||
}
|
||||
|
||||
if (archive_it != vendors_with_status.end())
|
||||
archive_it->second = VendorStatus::INSTALLED;
|
||||
|
||||
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_temp = idx_path + "-update";
|
||||
//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; }
|
||||
const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string();
|
||||
|
||||
// Load the fresh index up
|
||||
{
|
||||
@ -282,10 +473,11 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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
|
||||
//index = std::move(new_index);
|
||||
try {
|
||||
@ -315,12 +507,265 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
|
||||
|
||||
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;
|
||||
const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string());
|
||||
const auto bundle_path = cache_path / (vendor.id + ".ini");
|
||||
if (! get_file(bundle_url, bundle_path)) { continue; }
|
||||
if (cancel) { return; }
|
||||
if (!get_file(bundle_url, bundle_path))
|
||||
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.
|
||||
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
|
||||
// from the internet, or installed / updated from the installation resources.
|
||||
auto recommended = idx.recommended();
|
||||
@ -443,10 +894,13 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
|
||||
fs::path bundle_path_idx_to_install;
|
||||
if (fs::exists(path_in_cache)) {
|
||||
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) {
|
||||
// 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.
|
||||
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);
|
||||
// and install the config index from the cache into vendor's directory.
|
||||
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());
|
||||
|
||||
wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles") , 100, nullptr, wxPD_AUTO_HIDE);
|
||||
progress_dialog.Pulse();
|
||||
|
||||
for (const auto &update : updates.updates) {
|
||||
BOOST_LOG_TRIVIAL(info) << '\t' << update;
|
||||
|
||||
@ -601,7 +1058,35 @@ 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_materials/*filaments*/) { obsolete_remover("sla_material", 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;
|
||||
@ -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);
|
||||
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
|
||||
// into the closure (but perhaps the compiler can elide this).
|
||||
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->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()
|
||||
{
|
||||
if (! p->enabled_version_check)
|
||||
@ -810,7 +1306,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
||||
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;
|
||||
|
||||
@ -818,9 +1314,67 @@ bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
|
||||
|
||||
for (const auto &bundle : bundles) {
|
||||
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");
|
||||
|
||||
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);
|
||||
}
|
||||
@ -857,4 +1411,9 @@ bool PresetUpdater::version_check_enabled() const
|
||||
return p->enabled_version_check;
|
||||
}
|
||||
|
||||
void PresetUpdater::update_index_db()
|
||||
{
|
||||
p->update_index_db();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ public:
|
||||
~PresetUpdater();
|
||||
|
||||
// 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.
|
||||
void slic3r_update_notify();
|
||||
@ -53,8 +54,10 @@ public:
|
||||
// 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;
|
||||
|
||||
// "Update" a list of bundles from resources (behaves like an online update).
|
||||
bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
void update_index_db();
|
||||
|
||||
// "Update" a list of bundles from resources or cache/vendor (behaves like an online update).
|
||||
bool install_bundles_rsrc_or_cache_vendor(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
|
||||
void on_update_notification_confirm();
|
||||
|
||||
|
76
tests/data/U_overhang.obj
Normal file
76
tests/data/U_overhang.obj
Normal 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
|
@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
|
||||
sla_test_utils.hpp sla_test_utils.cpp
|
||||
sla_supptgen_tests.cpp
|
||||
sla_raycast_tests.cpp
|
||||
sla_supptreeutils_tests.cpp
|
||||
sla_archive_readwrite_tests.cpp)
|
||||
|
||||
# mold linker for successful linking needs also to link TBB library and link it before libslic3r.
|
||||
|
@ -1,4 +1,3 @@
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <random>
|
||||
#include <numeric>
|
||||
@ -32,13 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = {
|
||||
|
||||
} // 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",
|
||||
"[SLASupportGeneration], [SLAPointGen]") {
|
||||
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);
|
||||
//}
|
||||
|
||||
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]") {
|
||||
|
||||
|
377
tests/sla_print/sla_supptreeutils_tests.cpp
Normal file
377
tests/sla_print/sla_supptreeutils_tests.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ void test_supports(const std::string &obj_filename,
|
||||
|
||||
// Create the special index-triangle mesh with spatial indexing which
|
||||
// is the input of the support point and support mesh generators
|
||||
AABBMesh emesh{mesh};
|
||||
sla::SupportableMesh sm{mesh.its, {}, supportcfg};
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
if (hollowingcfg.enabled)
|
||||
@ -130,23 +130,23 @@ void test_supports(const std::string &obj_filename,
|
||||
// Create the support point generator
|
||||
sla::SupportPointGenerator::Config autogencfg;
|
||||
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.execute(out.model_slices, out.slicegrid);
|
||||
|
||||
// Get the calculated support points.
|
||||
std::vector<sla::SupportPoint> support_points = point_gen.output();
|
||||
sm.pts = point_gen.output();
|
||||
|
||||
int validityflags = ASSUME_NO_REPAIR;
|
||||
|
||||
// If there is no elevation, support points shall be removed from the
|
||||
// bottom of the object.
|
||||
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 {
|
||||
// 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.
|
||||
validityflags |= ASSUME_NO_EMPTY;
|
||||
@ -154,7 +154,6 @@ void test_supports(const std::string &obj_filename,
|
||||
|
||||
// Generate the actual support tree
|
||||
sla::SupportTreeBuilder treebuilder;
|
||||
sla::SupportableMesh sm{emesh, support_points, supportcfg};
|
||||
|
||||
switch (sm.cfg.tree_type) {
|
||||
case sla::SupportTreeType::Default: {
|
||||
@ -182,6 +181,18 @@ void test_supports(const std::string &obj_filename,
|
||||
if (std::abs(supportcfg.object_elevation_mm) < EPSILON)
|
||||
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.max.z() <= Approx(zmax));
|
||||
|
||||
|
@ -14,14 +14,12 @@
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/SLA/Pad.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||
#include "libslic3r/SLA/SupportPointGenerator.hpp"
|
||||
#include "libslic3r/SLA/AGGRaster.hpp"
|
||||
#include "libslic3r/SLA/ConcaveHull.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
|
||||
#include "libslic3r/SVG.hpp"
|
||||
#include "libslic3r/Format/OBJ.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
@ -111,58 +109,6 @@ inline void test_support_model_collision(
|
||||
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:
|
||||
|
||||
using TPixel = uint8_t;
|
||||
|
Loading…
x
Reference in New Issue
Block a user