
Fixed fatal bug with anchors for mini supports Make the optimization cleaner in support generatior Much better widening behaviour Add an optimizer interface and the NLopt implementation into libslic3r New optimizer based only on nlopt C interfase Fix build and tests
377 lines
14 KiB
C++
377 lines
14 KiB
C++
#ifndef SLASUPPORTTREEALGORITHM_H
|
|
#define SLASUPPORTTREEALGORITHM_H
|
|
|
|
#include <cstdint>
|
|
|
|
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
|
#include <libslic3r/SLA/Clustering.hpp>
|
|
#include <libslic3r/SLA/SpatIndex.hpp>
|
|
|
|
namespace Slic3r {
|
|
namespace sla {
|
|
|
|
// The minimum distance for two support points to remain valid.
|
|
const double /*constexpr*/ D_SP = 0.1;
|
|
|
|
enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
|
|
X, Y, Z
|
|
};
|
|
|
|
inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; }
|
|
|
|
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
|
|
{
|
|
double z = n.z();
|
|
double r = norm;
|
|
double polar = std::acos(z / r);
|
|
double azimuth = std::atan2(n(1), n(0));
|
|
return {polar, azimuth};
|
|
}
|
|
|
|
inline Vec3d spheric_to_dir(double polar, double azimuth)
|
|
{
|
|
return {std::cos(azimuth) * std::sin(polar),
|
|
std::sin(azimuth) * std::sin(polar), std::cos(polar)};
|
|
}
|
|
|
|
inline Vec3d spheric_to_dir(const std::tuple<double, double> &v)
|
|
{
|
|
auto [plr, azm] = v;
|
|
return spheric_to_dir(plr, azm);
|
|
}
|
|
|
|
inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
|
|
{
|
|
return spheric_to_dir(v.first, v.second);
|
|
}
|
|
|
|
inline Vec3d spheric_to_dir(const std::array<double, 2> &v)
|
|
{
|
|
return spheric_to_dir(v[0], v[1]);
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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
|
|
// placeholder.
|
|
// a and b vectors are perpendicular to the ring direction and to each other.
|
|
// Together they define the plane where we have to iterate with the
|
|
// given angles in the 'm_phis' vector
|
|
Vec3d a = {0, 1, 0}, b;
|
|
double m_radius = 0.;
|
|
|
|
static inline bool constexpr is_one(double val)
|
|
{
|
|
return std::abs(std::abs(val) - 1) < 1e-20;
|
|
}
|
|
|
|
public:
|
|
|
|
PointRing(const Vec3d &n)
|
|
{
|
|
m_phis = linspace_array<N>(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
|
|
// its components will be completely zero and one is 1.0. Our method
|
|
// becomes dangerous here due to division with zero. Instead, vector
|
|
// 'a' can be an element-wise rotated version of 'v'
|
|
if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
|
|
a = {n(Z), n(X), n(Y)};
|
|
b = {n(Y), n(Z), n(X)};
|
|
}
|
|
else {
|
|
a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
|
|
b = a.cross(n);
|
|
}
|
|
}
|
|
|
|
Vec3d get(size_t idx, const Vec3d src, double r) const
|
|
{
|
|
double phi = m_phis[idx];
|
|
double sinphi = std::sin(phi);
|
|
double cosphi = std::cos(phi);
|
|
|
|
double rpscos = r * cosphi;
|
|
double rpssin = r * sinphi;
|
|
|
|
// Point on the sphere
|
|
return {src(X) + rpscos * a(X) + rpssin * b(X),
|
|
src(Y) + rpscos * a(Y) + rpssin * b(Y),
|
|
src(Z) + rpscos * a(Z) + rpssin * b(Z)};
|
|
}
|
|
};
|
|
|
|
//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
|
|
//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
|
|
|
|
inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) {
|
|
return (endp - startp).normalized();
|
|
}
|
|
|
|
class PillarIndex {
|
|
PointIndex m_index;
|
|
using Mutex = ccr::BlockingMutex;
|
|
mutable Mutex m_mutex;
|
|
|
|
public:
|
|
|
|
template<class...Args> inline void guarded_insert(Args&&...args)
|
|
{
|
|
std::lock_guard<Mutex> lck(m_mutex);
|
|
m_index.insert(std::forward<Args>(args)...);
|
|
}
|
|
|
|
template<class...Args>
|
|
inline std::vector<PointIndexEl> guarded_query(Args&&...args) const
|
|
{
|
|
std::lock_guard<Mutex> lck(m_mutex);
|
|
return m_index.query(std::forward<Args>(args)...);
|
|
}
|
|
|
|
template<class...Args> inline void insert(Args&&...args)
|
|
{
|
|
m_index.insert(std::forward<Args>(args)...);
|
|
}
|
|
|
|
template<class...Args>
|
|
inline std::vector<PointIndexEl> query(Args&&...args) const
|
|
{
|
|
return m_index.query(std::forward<Args>(args)...);
|
|
}
|
|
|
|
template<class Fn> inline void foreach(Fn fn) { m_index.foreach(fn); }
|
|
template<class Fn> inline void guarded_foreach(Fn fn)
|
|
{
|
|
std::lock_guard<Mutex> lck(m_mutex);
|
|
m_index.foreach(fn);
|
|
}
|
|
|
|
PointIndex guarded_clone()
|
|
{
|
|
std::lock_guard<Mutex> lck(m_mutex);
|
|
return m_index;
|
|
}
|
|
};
|
|
|
|
// 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;
|
|
}
|
|
|
|
class SupportTreeBuildsteps {
|
|
const SupportTreeConfig& m_cfg;
|
|
const IndexedMesh& m_mesh;
|
|
const std::vector<SupportPoint>& m_support_pts;
|
|
|
|
using PtIndices = std::vector<unsigned>;
|
|
|
|
PtIndices m_iheads; // support points with pinhead
|
|
PtIndices m_iheads_onmodel;
|
|
PtIndices m_iheadless; // headless support points
|
|
|
|
std::map<unsigned, IndexedMesh::hit_result> m_head_to_ground_scans;
|
|
|
|
// normals for support points from model faces.
|
|
PointSet m_support_nmls;
|
|
|
|
// Clusters of points which can reach the ground directly and can be
|
|
// bridged to one central pillar
|
|
std::vector<PtIndices> m_pillar_clusters;
|
|
|
|
// This algorithm uses the SupportTreeBuilder class to fill gradually
|
|
// the support elements (heads, pillars, bridges, ...)
|
|
SupportTreeBuilder& m_builder;
|
|
|
|
// support points in Eigen/IGL format
|
|
PointSet m_points;
|
|
|
|
// throw if canceled: It will be called many times so a shorthand will
|
|
// come in handy.
|
|
ThrowOnCancel m_thr;
|
|
|
|
// A spatial index to easily find strong pillars to connect to.
|
|
PillarIndex m_pillar_index;
|
|
|
|
// When bridging heads to pillars... TODO: find a cleaner solution
|
|
ccr::BlockingMutex m_bridge_mutex;
|
|
|
|
inline IndexedMesh::hit_result ray_mesh_intersect(const Vec3d& s,
|
|
const Vec3d& dir)
|
|
{
|
|
return m_mesh.query_ray_hit(s, dir);
|
|
}
|
|
|
|
// This function will test if a future pinhead would not collide with the
|
|
// model geometry. It does not take a 'Head' object because those are
|
|
// created after this test. Parameters: s: The touching point on the model
|
|
// surface. dir: This is the direction of the head from the pin to the back
|
|
// r_pin, r_back: the radiuses of the pin and the back sphere width: This
|
|
// is the full width from the pin center to the back center m: The object
|
|
// mesh.
|
|
// The return value is the hit result from the ray casting. If the starting
|
|
// point was inside the model, an "invalid" hit_result will be returned
|
|
// with a zero distance value instead of a NAN. This way the result can
|
|
// be used safely for comparison with other distances.
|
|
IndexedMesh::hit_result pinhead_mesh_intersect(
|
|
const Vec3d& s,
|
|
const Vec3d& dir,
|
|
double r_pin,
|
|
double r_back,
|
|
double width,
|
|
double safety_d);
|
|
|
|
IndexedMesh::hit_result pinhead_mesh_intersect(
|
|
const Vec3d& s,
|
|
const Vec3d& dir,
|
|
double r_pin,
|
|
double r_back,
|
|
double width)
|
|
{
|
|
return pinhead_mesh_intersect(s, dir, r_pin, r_back, width,
|
|
r_back * m_cfg.safety_distance_mm /
|
|
m_cfg.head_back_radius_mm);
|
|
}
|
|
|
|
// Checking bridge (pillar and stick as well) intersection with the model.
|
|
// If the function is used for headless sticks, the ins_check parameter
|
|
// have to be true as the beginning of the stick might be inside the model
|
|
// geometry.
|
|
// The return value is the hit result from the ray casting. If the starting
|
|
// point was inside the model, an "invalid" hit_result will be returned
|
|
// with a zero distance value instead of a NAN. This way the result can
|
|
// be used safely for comparison with other distances.
|
|
IndexedMesh::hit_result bridge_mesh_intersect(
|
|
const Vec3d& s,
|
|
const Vec3d& dir,
|
|
double r,
|
|
double safety_d);
|
|
|
|
IndexedMesh::hit_result bridge_mesh_intersect(
|
|
const Vec3d& s,
|
|
const Vec3d& dir,
|
|
double r)
|
|
{
|
|
return bridge_mesh_intersect(s, dir, r,
|
|
r * m_cfg.safety_distance_mm /
|
|
m_cfg.head_back_radius_mm);
|
|
}
|
|
|
|
template<class...Args>
|
|
inline double bridge_mesh_distance(Args&&...args) {
|
|
return bridge_mesh_intersect(std::forward<Args>(args)...).distance();
|
|
}
|
|
|
|
// Helper function for interconnecting two pillars with zig-zag bridges.
|
|
bool interconnect(const Pillar& pillar, const Pillar& nextpillar);
|
|
|
|
// For connecting a head to a nearby pillar.
|
|
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
|
|
|
|
// Find route for a head to the ground. Inserts additional bridge from the
|
|
// head to the pillar if cannot create pillar directly.
|
|
// The optional dir parameter is the direction of the bridge which is the
|
|
// direction of the pinhead if omitted.
|
|
bool connect_to_ground(Head& head, const Vec3d &dir);
|
|
inline bool connect_to_ground(Head& head);
|
|
|
|
bool connect_to_model_body(Head &head);
|
|
|
|
bool search_pillar_and_connect(const Head& source);
|
|
|
|
// This is a proxy function for pillar creation which will mind the gap
|
|
// between the pad and the model bottom in zero elevation mode.
|
|
// 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,
|
|
const Vec3d &sourcedir,
|
|
double radius,
|
|
long head_id = SupportTreeNode::ID_UNSET);
|
|
|
|
void add_pillar_base(long pid)
|
|
{
|
|
m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
|
}
|
|
|
|
std::optional<DiffBridge> search_widening_path(const Vec3d &jp,
|
|
const Vec3d &dir,
|
|
double radius,
|
|
double new_radius);
|
|
|
|
public:
|
|
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
|
|
|
// Now let's define the individual steps of the support generation algorithm
|
|
|
|
// Filtering step: here we will discard inappropriate support points
|
|
// and decide the future of the appropriate ones. We will check if a
|
|
// pinhead is applicable and adjust its angle at each support point. We
|
|
// will also merge the support points that are just too close and can
|
|
// be considered as one.
|
|
void filter();
|
|
|
|
// Pinhead creation: based on the filtering results, the Head objects
|
|
// will be constructed (together with their triangle meshes).
|
|
void add_pinheads();
|
|
|
|
// Further classification of the support points with pinheads. If the
|
|
// ground is directly reachable through a vertical line parallel to the
|
|
// Z axis we consider a support point as pillar candidate. If touches
|
|
// the model geometry, it will be marked as non-ground facing and
|
|
// further steps will process it. Also, the pillars will be grouped
|
|
// into clusters that can be interconnected with bridges. Elements of
|
|
// these groups may or may not be interconnected. Here we only run the
|
|
// clustering algorithm.
|
|
void classify();
|
|
|
|
// Step: Routing the ground connected pinheads, and interconnecting
|
|
// them with additional (angled) bridges. Not all of these pinheads
|
|
// will be a full pillar (ground connected). Some will connect to a
|
|
// nearby pillar using a bridge. The max number of such side-heads for
|
|
// a central pillar is limited to avoid bad weight distribution.
|
|
void routing_to_ground();
|
|
|
|
// Step: routing the pinheads that would connect to the model surface
|
|
// along the Z axis downwards. For now these will actually be connected with
|
|
// the model surface with a flipped pinhead. In the future here we could use
|
|
// some smart algorithms to search for a safe path to the ground or to a
|
|
// nearby pillar that can hold the supported weight.
|
|
void routing_to_model();
|
|
|
|
void interconnect_pillars();
|
|
|
|
inline void merge_result() { m_builder.merged_mesh(); }
|
|
|
|
static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
#endif // SLASUPPORTTREEALGORITHM_H
|