OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::sla::SupportTreeBuildsteps Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/libslic3r/SLA/SupportTreeBuildsteps.hpp>

Collaboration diagram for Slic3r::sla::SupportTreeBuildsteps:

Public Member Functions

 SupportTreeBuildsteps (SupportTreeBuilder &builder, const SupportableMesh &sm)
void filter ()
void add_pinheads ()
void classify ()
void routing_to_ground ()
void routing_to_model ()
void interconnect_pillars ()
void merge_result ()

Static Public Member Functions

static bool execute (SupportTreeBuilder &builder, const SupportableMesh &sm)

Private Types

using PtIndices = std::vector<unsigned>

Private Member Functions

IndexedMesh::hit_result ray_mesh_intersect (const Vec3d &s, const Vec3d &dir)
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)
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)
template<class... Args>
double bridge_mesh_distance (Args &&...args)
bool interconnect (const Pillar &pillar, const Pillar &nextpillar)
bool connect_to_nearpillar (const Head &head, long nearpillar_id)
bool connect_to_ground (Head &head, const Vec3d &dir)
bool connect_to_ground (Head &head)
bool connect_to_model_body (Head &head)
bool search_pillar_and_connect (const Head &source)
bool create_ground_pillar (const Vec3d &jp, const Vec3d &sourcedir, double radius, long head_id=SupportTreeNode::ID_UNSET)
void add_pillar_base (long pid)
std::optional< DiffBridge > search_widening_path (const Vec3d &jp, const Vec3d &dir, double radius, double new_radius)

Private Attributes

const SupportTreeConfig & m_cfg
const IndexedMesh & m_mesh
const std::vector< SupportPoint > & m_support_pts
PtIndices m_iheads
PtIndices m_iheads_onmodel
PtIndices m_iheadless
std::map< unsigned, IndexedMesh::hit_result > m_head_to_ground_scans
PointSet m_support_nmls
std::vector< PtIndices > m_pillar_clusters
SupportTreeBuilder & m_builder
PointSet m_points
ThrowOnCancel m_thr
PillarIndex m_pillar_index
ccr::BlockingMutex m_bridge_mutex

Detailed Description

Member Typedef Documentation

◆ PtIndices

using Slic3r::sla::SupportTreeBuildsteps::PtIndices = std::vector<unsigned>
private

Constructor & Destructor Documentation

◆ SupportTreeBuildsteps()

Slic3r::sla::SupportTreeBuildsteps::SupportTreeBuildsteps ( SupportTreeBuilder & builder,
const SupportableMesh & sm )
60 : m_cfg(sm.cfg)
61 , m_mesh(sm.emesh)
62 , m_support_pts(sm.pts)
63 , m_support_nmls(sm.pts.size(), 3)
64 , m_builder(builder)
65 , m_points(sm.pts.size(), 3)
66 , m_thr(builder.ctl().cancelfn)
67{
68 // Prepare the support points in Eigen/IGL format as well, we will use
69 // it mostly in this form.
70
71 long i = 0;
72 for (const SupportPoint &sp : m_support_pts) {
73 m_points.row(i)(X) = double(sp.pos(X));
74 m_points.row(i)(Y) = double(sp.pos(Y));
75 m_points.row(i)(Z) = double(sp.pos(Z));
76 ++i;
77 }
78}
SupportTreeBuilder & m_builder
Definition SupportTreeBuildsteps.hpp:229
const SupportTreeConfig & m_cfg
Definition SupportTreeBuildsteps.hpp:208
const IndexedMesh & m_mesh
Definition SupportTreeBuildsteps.hpp:209
ThrowOnCancel m_thr
Definition SupportTreeBuildsteps.hpp:236
PointSet m_points
Definition SupportTreeBuildsteps.hpp:232
const std::vector< SupportPoint > & m_support_pts
Definition SupportTreeBuildsteps.hpp:210
PointSet m_support_nmls
Definition SupportTreeBuildsteps.hpp:221
@ Y
Definition SupportTreeBuildsteps.hpp:37
@ X
Definition SupportTreeBuildsteps.hpp:37
@ Z
Definition SupportTreeBuildsteps.hpp:37

References m_builder, m_cfg, m_mesh, m_points, m_support_nmls, m_support_pts, m_thr, Slic3r::sla::X, Slic3r::sla::Y, and Slic3r::sla::Z.

Referenced by execute().

Here is the caller graph for this function:

Member Function Documentation

◆ add_pillar_base()

void Slic3r::sla::SupportTreeBuildsteps::add_pillar_base ( long pid)
inlineprivate
338 {
339 m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm);
340 }

References m_builder, and m_cfg.

Referenced by create_ground_pillar(), and interconnect_pillars().

Here is the caller graph for this function:

◆ add_pinheads()

void Slic3r::sla::SupportTreeBuildsteps::add_pinheads ( )
789{
790}

References add_pinheads().

Referenced by add_pinheads(), and execute().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ bridge_mesh_distance()

template<class... Args>
double Slic3r::sla::SupportTreeBuildsteps::bridge_mesh_distance ( Args &&... args)
inlineprivate
306 {
307 return bridge_mesh_intersect(std::forward<Args>(args)...).distance();
308 }
double distance() const
Definition IndexedMesh.hpp:97
IndexedMesh::hit_result bridge_mesh_intersect(const Vec3d &s, const Vec3d &dir, double r, double safety_d)
Definition SupportTreeBuildsteps.cpp:282

References bridge_mesh_intersect(), and Slic3r::sla::IndexedMesh::hit_result::distance().

Referenced by connect_to_ground(), connect_to_ground(), connect_to_nearpillar(), create_ground_pillar(), interconnect(), interconnect_pillars(), and search_widening_path().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ bridge_mesh_intersect() [1/2]

IndexedMesh::hit_result Slic3r::sla::SupportTreeBuildsteps::bridge_mesh_intersect ( const Vec3d & s,
const Vec3d & dir,
double r )
inlineprivate
299 {
300 return bridge_mesh_intersect(s, dir, r,
301 r * m_cfg.safety_distance_mm /
302 m_cfg.head_back_radius_mm);
303 }

References bridge_mesh_intersect(), and m_cfg.

Here is the call graph for this function:

◆ bridge_mesh_intersect() [2/2]

IndexedMesh::hit_result Slic3r::sla::SupportTreeBuildsteps::bridge_mesh_intersect ( const Vec3d & s,
const Vec3d & dir,
double r,
double safety_d )
private
284{
285 static const size_t SAMPLES = 8;
286 PointRing<SAMPLES> ring{dir};
287
288 using Hit = IndexedMesh::hit_result;
289
290 // Hit results
291 std::array<Hit, SAMPLES> hits;
292
293 ccr::for_each(size_t(0), hits.size(),
294 [this, r, src, /*ins_check,*/ &ring, dir, sd, &hits] (size_t i)
295 {
296 Hit &hit = hits[i];
297
298 // Point on the circle on the pin sphere
299 Vec3d p = ring.get(i, src, r + sd);
300
301 auto hr = m_mesh.query_ray_hit(p + r * dir, dir);
302
303 if(/*ins_check && */hr.is_inside()) {
304 if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
305 else {
306 // re-cast the ray from the outside of the object
307 hit = m_mesh.query_ray_hit(p + (hr.distance() + EPSILON) * dir, dir);
308 }
309 } else hit = hr;
310 });
311
312 return min_hit(hits);
313}
static Hit min_hit(const C &hits)
Definition SupportTreeBuildsteps.cpp:48

References bridge_mesh_intersect().

Referenced by bridge_mesh_distance(), bridge_mesh_intersect(), bridge_mesh_intersect(), classify(), and interconnect_pillars().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ classify()

void Slic3r::sla::SupportTreeBuildsteps::classify ( )
793{
794 // We should first get the heads that reach the ground directly
795 PtIndices ground_head_indices;
796 ground_head_indices.reserve(m_iheads.size());
797 m_iheads_onmodel.reserve(m_iheads.size());
798
799 // First we decide which heads reach the ground and can be full
800 // pillars and which shall be connected to the model surface (or
801 // search a suitable path around the surface that leads to the
802 // ground -- TODO)
803 for(unsigned i : m_iheads) {
804 m_thr();
805
806 Head &head = m_builder.head(i);
807 double r = head.r_back_mm;
808 Vec3d headjp = head.junction_point();
809
810 // collision check
811 auto hit = bridge_mesh_intersect(headjp, DOWN, r);
812
813 if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
814 else if(m_cfg.ground_facing_only) head.invalidate();
815 else m_iheads_onmodel.emplace_back(i);
816
817 m_head_to_ground_scans[i] = hit;
818 }
819
820 // We want to search for clusters of points that are far enough
821 // from each other in the XY plane to not cross their pillar bases
822 // These clusters of support points will join in one pillar,
823 // possibly in their centroid support point.
824
825 auto pointfn = [this](unsigned i) {
826 return m_builder.head(i).junction_point();
827 };
828
829 auto predicate = [this](const PointIndexEl &e1,
830 const PointIndexEl &e2) {
831 double d2d = distance(to_2d(e1.first), to_2d(e2.first));
832 double d3d = distance(e1.first, e2.first);
833 return d2d < 2 * m_cfg.base_radius_mm
834 && d3d < m_cfg.max_bridge_length_mm;
835 };
836
837 m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate,
838 m_cfg.max_bridges_on_pillar);
839}
std::vector< unsigned > PtIndices
Definition SupportTreeBuildsteps.hpp:212
std::vector< PtIndices > m_pillar_clusters
Definition SupportTreeBuildsteps.hpp:225
std::map< unsigned, IndexedMesh::hit_result > m_head_to_ground_scans
Definition SupportTreeBuildsteps.hpp:218
PtIndices m_iheads
Definition SupportTreeBuildsteps.hpp:214
PtIndices m_iheads_onmodel
Definition SupportTreeBuildsteps.hpp:215
ClusteredPoints cluster(const std::vector< unsigned > &indices, std::function< Vec3d(unsigned)> pointfn, double dist, unsigned max_points)
Definition Clustering.cpp:101
Eigen::Matrix< double, 3, 1, Eigen::DontAlign > Vec3d
Definition SpatIndex.hpp:18
const Vec3d DOWN
Definition SupportTreeBuilder.hpp:68
double distance(const Vec &p)
Definition SupportTreeBuilder.hpp:59
std::pair< Vec3d, unsigned > PointIndexEl
Definition SpatIndex.hpp:19
Eigen::Matrix< typename Derived::Scalar, 2, 1, Eigen::DontAlign > to_2d(const Eigen::MatrixBase< Derived > &ptN)
Definition Point.hpp:147

References bridge_mesh_intersect(), classify(), Slic3r::sla::cluster(), Slic3r::sla::distance(), Slic3r::sla::DOWN, Slic3r::sla::Head::invalidate(), Slic3r::sla::Head::junction_point(), m_builder, m_cfg, m_head_to_ground_scans, m_iheads, m_iheads_onmodel, m_pillar_clusters, m_thr, Slic3r::sla::Head::r_back_mm, and Slic3r::to_2d().

Referenced by classify(), and execute().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ connect_to_ground() [1/2]

bool Slic3r::sla::SupportTreeBuildsteps::connect_to_ground ( Head & head)
inlineprivate
940{
941 if (connect_to_ground(head, head.dir)) return true;
942
943 // Optimize bridge direction:
944 // Straight path failed so we will try to search for a suitable
945 // direction out of the cavity.
946 auto [polar, azimuth] = dir_to_spheric(head.dir);
947
948 Optimizer<AlgNLoptGenetic> solver(get_criteria(m_cfg).stop_score(1e6));
949 solver.seed(0); // we want deterministic behavior
950
951 double r_back = head.r_back_mm;
952 Vec3d hjp = head.junction_point();
953 auto oresult = solver.to_max().optimize(
954 [this, hjp, r_back](const opt::Input<2> &input) {
955 auto &[plr, azm] = input;
956 Vec3d n = spheric_to_dir(plr, azm).normalized();
957 return bridge_mesh_distance(hjp, n, r_back);
958 },
959 initvals({polar, azimuth}), // let's start with what we have
960 bounds({ {PI - m_cfg.bridge_slope, PI}, {-PI, PI} })
961 );
962
963 Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
964 return connect_to_ground(head, bridgedir);
965}
double bridge_mesh_distance(Args &&...args)
Definition SupportTreeBuildsteps.hpp:306
bool connect_to_ground(Head &head, const Vec3d &dir)
Definition SupportTreeBuildsteps.cpp:915
static constexpr double PI
Definition libslic3r.h:81
std::array< double, N > Input
Definition Optimizer.hpp:38
Vec3d spheric_to_dir(double polar, double azimuth)
Definition SupportTreeBuildsteps.hpp:51
StopCriteria get_criteria(const SupportTreeConfig &cfg)
Definition SupportTreeBuildsteps.cpp:40
std::pair< double, double > dir_to_spheric(const Vec3d &n, double norm=1.)
Definition SupportTreeBuildsteps.hpp:42
Bounds< N > bounds(const Bound(&b)[N])
Definition Optimizer.hpp:175
Input< N > initvals(const double(&a)[N])
Definition Optimizer.hpp:176

References Slic3r::sla::bounds(), bridge_mesh_distance(), connect_to_ground(), Slic3r::sla::Head::dir, Slic3r::sla::dir_to_spheric(), Slic3r::sla::get_criteria(), Slic3r::sla::initvals(), Slic3r::sla::Head::junction_point(), m_cfg, Slic3r::opt::Optimizer< Method, Enable >::optimize(), PI, Slic3r::sla::Head::r_back_mm, Slic3r::opt::Optimizer< Method, Enable >::seed(), Slic3r::sla::spheric_to_dir(), and Slic3r::opt::Optimizer< Method, Enable >::to_max().

Here is the call graph for this function:

◆ connect_to_ground() [2/2]

bool Slic3r::sla::SupportTreeBuildsteps::connect_to_ground ( Head & head,
const Vec3d & dir )
private
916{
917 auto hjp = head.junction_point();
918 double r = head.r_back_mm;
919 double t = bridge_mesh_distance(hjp, dir, head.r_back_mm);
920 double d = 0, tdown = 0;
921 t = std::min(t, m_cfg.max_bridge_length_mm * r / m_cfg.head_back_radius_mm);
922
923 while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r)))
924 d += r;
925
926 if(!std::isinf(tdown)) return false;
927
928 Vec3d endp = hjp + d * dir;
929 bool ret = false;
930
931 if ((ret = create_ground_pillar(endp, dir, head.r_back_mm))) {
932 m_builder.add_bridge(head.id, endp);
933 m_builder.add_junction(endp, head.r_back_mm);
934 }
935
936 return ret;
937}
bool create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, long head_id=SupportTreeNode::ID_UNSET)
Definition SupportTreeBuildsteps.cpp:491

References bridge_mesh_distance(), connect_to_ground(), create_ground_pillar(), Slic3r::sla::DOWN, Slic3r::sla::SupportTreeNode::id, Slic3r::sla::Head::junction_point(), m_builder, m_cfg, and Slic3r::sla::Head::r_back_mm.

Referenced by connect_to_ground(), connect_to_ground(), and routing_to_model().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ connect_to_model_body()

bool Slic3r::sla::SupportTreeBuildsteps::connect_to_model_body ( Head & head)
private
968{
969 if (head.id <= SupportTreeNode::ID_UNSET) return false;
970
971 auto it = m_head_to_ground_scans.find(unsigned(head.id));
972 if (it == m_head_to_ground_scans.end()) return false;
973
974 auto &hit = it->second;
975
976 if (!hit.is_hit()) {
977 // TODO scan for potential anchor points on model surface
978 return false;
979 }
980
981 Vec3d hjp = head.junction_point();
982 double zangle = std::asin(hit.direction()(Z));
983 zangle = std::max(zangle, PI/4);
984 double h = std::sin(zangle) * head.fullwidth();
985
986 // The width of the tail head that we would like to have...
987 h = std::min(hit.distance() - head.r_back_mm, h);
988
989 // If this is a mini pillar dont bother with the tail width, can be 0.
990 if (head.r_back_mm < m_cfg.head_back_radius_mm) h = std::max(h, 0.);
991 else if (h <= 0.) return false;
992
993 Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
994 auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
995
996 double hitdiff = center_hit.distance() - hit.distance();
997 Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
998 center_hit.position() : hit.position();
999
1000 long pillar_id = m_builder.add_pillar(head.id, hjp.z() - endp.z());
1001 Pillar &pill = m_builder.pillar(pillar_id);
1002
1003 Vec3d taildir = endp - hitp;
1004 double dist = (hitp - endp).norm() + m_cfg.head_penetration_mm;
1005 double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
1006
1007 if (w < 0.) {
1008 BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
1009 w = 0.;
1010 }
1011
1012 m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w,
1013 m_cfg.head_penetration_mm, taildir, hitp);
1014
1015 m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
1016
1017 return true;
1018}
PillarIndex m_pillar_index
Definition SupportTreeBuildsteps.hpp:239
T dist(const boost::polygon::point_data< T > &p1, const boost::polygon::point_data< T > &p2)
Definition Geometry.cpp:298
static const constexpr long ID_UNSET
Definition SupportTreeBuilder.hpp:72

References connect_to_model_body(), Slic3r::Geometry::dist(), Slic3r::sla::DOWN, Slic3r::sla::Pillar::endpoint(), Slic3r::sla::Head::fullwidth(), Slic3r::sla::SupportTreeNode::id, Slic3r::sla::SupportTreeNode::ID_UNSET, Slic3r::sla::Head::junction_point(), m_builder, m_cfg, m_head_to_ground_scans, m_mesh, m_pillar_index, PI, Slic3r::sla::Head::r_back_mm, Slic3r::sla::Head::r_pin_mm, Slic3r::sla::X, Slic3r::sla::Y, and Slic3r::sla::Z.

Referenced by connect_to_model_body(), and routing_to_model().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ connect_to_nearpillar()

bool Slic3r::sla::SupportTreeBuildsteps::connect_to_nearpillar ( const Head & head,
long nearpillar_id )
private
410{
411 auto nearpillar = [this, nearpillar_id]() -> const Pillar& {
412 return m_builder.pillar(nearpillar_id);
413 };
414
415 if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar)
416 return false;
417
418 Vec3d headjp = head.junction_point();
419 Vec3d nearjp_u = nearpillar().startpoint();
420 Vec3d nearjp_l = nearpillar().endpoint();
421
422 double r = head.r_back_mm;
423 double d2d = distance(to_2d(headjp), to_2d(nearjp_u));
424 double d3d = distance(headjp, nearjp_u);
425
426 double hdiff = nearjp_u(Z) - headjp(Z);
427 double slope = std::atan2(hdiff, d2d);
428
429 Vec3d bridgestart = headjp;
430 Vec3d bridgeend = nearjp_u;
431 double max_len = r * m_cfg.max_bridge_length_mm / m_cfg.head_back_radius_mm;
432 double max_slope = m_cfg.bridge_slope;
433 double zdiff = 0.0;
434
435 // check the default situation if feasible for a bridge
436 if(d3d > max_len || slope > -max_slope) {
437 // not feasible to connect the two head junctions. We have to search
438 // for a suitable touch point.
439
440 double Zdown = headjp(Z) + d2d * std::tan(-max_slope);
441 Vec3d touchjp = bridgeend; touchjp(Z) = Zdown;
442 double D = distance(headjp, touchjp);
443 zdiff = Zdown - nearjp_u(Z);
444
445 if(zdiff > 0) {
446 Zdown -= zdiff;
447 bridgestart(Z) -= zdiff;
448 touchjp(Z) = Zdown;
449
450 double t = bridge_mesh_distance(headjp, DOWN, r);
451
452 // We can't insert a pillar under the source head to connect
453 // with the nearby pillar's starting junction
454 if(t < zdiff) return false;
455 }
456
457 if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len)
458 bridgeend(Z) = Zdown;
459 else
460 return false;
461 }
462
463 // There will be a minimum distance from the ground where the
464 // bridge is allowed to connect. This is an empiric value.
465 double minz = m_builder.ground_level + 4 * head.r_back_mm;
466 if(bridgeend(Z) < minz) return false;
467
468 double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r);
469
470 // Cannot insert the bridge. (further search might not worth the hassle)
471 if(t < distance(bridgestart, bridgeend)) return false;
472
473 std::lock_guard<ccr::BlockingMutex> lk(m_bridge_mutex);
474
475 if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) {
476 // A partial pillar is needed under the starting head.
477 if(zdiff > 0) {
478 m_builder.add_pillar(head.id, headjp.z() - bridgestart.z());
479 m_builder.add_junction(bridgestart, r);
480 m_builder.add_bridge(bridgestart, bridgeend, r);
481 } else {
482 m_builder.add_bridge(head.id, bridgeend);
483 }
484
485 m_builder.increment_bridges(nearpillar());
486 } else return false;
487
488 return true;
489}
ccr::BlockingMutex m_bridge_mutex
Definition SupportTreeBuildsteps.hpp:242
Vec3d dirv(const Vec3d &startp, const Vec3d &endp)
Definition SupportTreeBuildsteps.hpp:134

References bridge_mesh_distance(), connect_to_nearpillar(), Slic3r::sla::dirv(), Slic3r::sla::distance(), Slic3r::sla::DOWN, Slic3r::sla::SupportTreeNode::id, Slic3r::sla::Head::junction_point(), m_bridge_mutex, m_builder, m_cfg, Slic3r::sla::Head::r_back_mm, Slic3r::to_2d(), and Slic3r::sla::Z.

Referenced by connect_to_nearpillar(), routing_to_ground(), and search_pillar_and_connect().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ create_ground_pillar()

bool Slic3r::sla::SupportTreeBuildsteps::create_ground_pillar ( const Vec3d & jp,
const Vec3d & sourcedir,
double radius,
long head_id = SupportTreeNode::ID_UNSET )
private
495{
496 Vec3d jp = hjp, endp = jp, dir = sourcedir;
497 long pillar_id = SupportTreeNode::ID_UNSET;
498 bool can_add_base = false, non_head = false;
499
500 double gndlvl = 0.; // The Z level where pedestals should be
501 double jp_gnd = 0.; // The lowest Z where a junction center can be
502 double gap_dist = 0.; // The gap distance between the model and the pad
503
504 auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
505
506 auto eval_limits = [this, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
507 (bool base_en = true)
508 {
509 can_add_base = base_en && radius >= m_cfg.head_back_radius_mm;
510 double base_r = can_add_base ? m_cfg.base_radius_mm : 0.;
511 gndlvl = m_builder.ground_level;
512 if (!can_add_base) gndlvl -= m_mesh.ground_level_offset();
513 jp_gnd = gndlvl + (can_add_base ? 0. : m_cfg.head_back_radius_mm);
514 gap_dist = m_cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
515 };
516
517 eval_limits();
518
519 // We are dealing with a mini pillar that's potentially too long
520 if (radius < m_cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
521 {
522 std::optional<DiffBridge> diffbr =
523 search_widening_path(jp, dir, radius, m_cfg.head_back_radius_mm);
524
525 if (diffbr && diffbr->endp.z() > jp_gnd) {
526 auto &br = m_builder.add_diffbridge(*diffbr);
527 if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id;
528 endp = diffbr->endp;
529 radius = diffbr->end_r;
530 m_builder.add_junction(endp, radius);
531 non_head = true;
532 dir = diffbr->get_dir();
533 eval_limits();
534 } else return false;
535 }
536
537 if (m_cfg.object_elevation_mm < EPSILON)
538 {
539 // get a suitable direction for the corrector bridge. It is the
540 // original sourcedir's azimuth but the polar angle is saturated to the
541 // configured bridge slope.
542 auto [polar, azimuth] = dir_to_spheric(dir);
543 polar = PI - m_cfg.bridge_slope;
544 Vec3d d = spheric_to_dir(polar, azimuth).normalized();
545 double t = bridge_mesh_distance(endp, d, radius);
546 double tmax = std::min(m_cfg.max_bridge_length_mm, t);
547 t = 0.;
548
549 double zd = endp.z() - jp_gnd;
550 double tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope);
551 tmax = std::min(tmax, tmax2);
552
553 Vec3d nexp = endp;
554 double dlast = 0.;
555 while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist ||
556 !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) {
557 t += radius;
558 nexp = endp + t * d;
559 }
560
561 if (dlast < gap_dist && can_add_base) {
562 nexp = endp;
563 t = 0.;
564 can_add_base = false;
565 eval_limits(can_add_base);
566
567 zd = endp.z() - jp_gnd;
568 tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope);
569 tmax = std::min(tmax, tmax2);
570
571 while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist ||
572 !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) {
573 t += radius;
574 nexp = endp + t * d;
575 }
576 }
577
578 // Could not find a path to avoid the pad gap
579 if (dlast < gap_dist) return false;
580
581 if (t > 0.) { // Need to make additional bridge
582 const Bridge& br = m_builder.add_bridge(endp, nexp, radius);
583 if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id;
584
585 m_builder.add_junction(nexp, radius);
586 endp = nexp;
587 non_head = true;
588 }
589 }
590
591 Vec3d gp = to_floor(endp);
592 double h = endp.z() - gp.z();
593
594 pillar_id = head_id >= 0 && !non_head ? m_builder.add_pillar(head_id, h) :
595 m_builder.add_pillar(gp, h, radius);
596
597 if (can_add_base)
598 add_pillar_base(pillar_id);
599
600 if(pillar_id >= 0) // Save the pillar endpoint in the spatial index
601 m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt,
602 unsigned(pillar_id));
603
604 return true;
605}
#define EPSILON
Definition ColorUtils.cpp:64
void add_pillar_base(long pid)
Definition SupportTreeBuildsteps.hpp:337
std::optional< DiffBridge > search_widening_path(const Vec3d &jp, const Vec3d &dir, double radius, double new_radius)
Definition SupportTreeBuildsteps.cpp:607

References add_pillar_base(), bridge_mesh_distance(), create_ground_pillar(), Slic3r::sla::dir_to_spheric(), Slic3r::sla::DOWN, EPSILON, Slic3r::sla::SupportTreeNode::id, Slic3r::sla::SupportTreeNode::ID_UNSET, m_builder, m_cfg, m_mesh, m_pillar_index, PI, search_widening_path(), and Slic3r::sla::spheric_to_dir().

Referenced by connect_to_ground(), create_ground_pillar(), and routing_to_ground().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ execute()

bool Slic3r::sla::SupportTreeBuildsteps::execute ( SupportTreeBuilder & builder,
const SupportableMesh & sm )
static
82{
83 if(sm.pts.empty()) return false;
84
85 builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
86
87 SupportTreeBuildsteps alg(builder, sm);
88
89 // Let's define the individual steps of the processing. We can experiment
90 // later with the ordering and the dependencies between them.
91 enum Steps {
92 BEGIN,
93 FILTER,
94 PINHEADS,
95 CLASSIFY,
96 ROUTING_GROUND,
97 ROUTING_NONGROUND,
98 CASCADE_PILLARS,
99 MERGE_RESULT,
100 DONE,
101 ABORT,
102 NUM_STEPS
103 //...
104 };
105
106 // Collect the algorithm steps into a nice sequence
107 std::array<std::function<void()>, NUM_STEPS> program = {
108 [] () {
109 // Begin...
110 // Potentially clear up the shared data (not needed for now)
111 },
112
113 std::bind(&SupportTreeBuildsteps::filter, &alg),
114
115 std::bind(&SupportTreeBuildsteps::add_pinheads, &alg),
116
117 std::bind(&SupportTreeBuildsteps::classify, &alg),
118
120
122
124
125 std::bind(&SupportTreeBuildsteps::merge_result, &alg),
126
127 [] () {
128 // Done
129 },
130
131 [] () {
132 // Abort
133 }
134 };
135
136 Steps pc = BEGIN;
137
138 if(sm.cfg.ground_facing_only) {
139 program[ROUTING_NONGROUND] = []() {
140 BOOST_LOG_TRIVIAL(info)
141 << "Skipping model-facing supports as requested.";
142 };
143 }
144
145 // Let's define a simple automaton that will run our program.
146 auto progress = [&builder, &pc] () {
147 static const std::array<std::string, NUM_STEPS> stepstr {
148 "Starting",
149 "Filtering",
150 "Generate pinheads",
151 "Classification",
152 "Routing to ground",
153 "Routing supports to model surface",
154 "Interconnecting pillars",
155 "Merging support mesh",
156 "Done",
157 "Abort"
158 };
159
160 static const std::array<unsigned, NUM_STEPS> stepstate {
161 0,
162 10,
163 30,
164 50,
165 60,
166 70,
167 80,
168 99,
169 100,
170 0
171 };
172
173 if(builder.ctl().stopcondition()) pc = ABORT;
174
175 switch(pc) {
176 case BEGIN: pc = FILTER; break;
177 case FILTER: pc = PINHEADS; break;
178 case PINHEADS: pc = CLASSIFY; break;
179 case CLASSIFY: pc = ROUTING_GROUND; break;
180 case ROUTING_GROUND: pc = ROUTING_NONGROUND; break;
181 case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break;
182 case CASCADE_PILLARS: pc = MERGE_RESULT; break;
183 case MERGE_RESULT: pc = DONE; break;
184 case DONE:
185 case ABORT: break;
186 default: ;
187 }
188
189 builder.ctl().statuscb(stepstate[pc], stepstr[pc]);
190 };
191
192 // Just here we run the computation...
193 while(pc < DONE) {
194 progress();
195 program[pc]();
196 }
197
198 return pc == ABORT;
199}
void routing_to_model()
Definition SupportTreeBuildsteps.cpp:1057
void classify()
Definition SupportTreeBuildsteps.cpp:792
void merge_result()
Definition SupportTreeBuildsteps.hpp:389
SupportTreeBuildsteps(SupportTreeBuilder &builder, const SupportableMesh &sm)
Definition SupportTreeBuildsteps.cpp:58
void interconnect_pillars()
Definition SupportTreeBuildsteps.cpp:1087
void filter()
Definition SupportTreeBuildsteps.cpp:652
void add_pinheads()
Definition SupportTreeBuildsteps.cpp:788
void routing_to_ground()
Definition SupportTreeBuildsteps.cpp:841

References SupportTreeBuildsteps(), add_pinheads(), Slic3r::sla::SupportableMesh::cfg, classify(), Slic3r::sla::SupportTree::ctl(), Slic3r::sla::SupportableMesh::emesh, filter(), Slic3r::sla::SupportTreeConfig::ground_facing_only, Slic3r::sla::IndexedMesh::ground_level(), Slic3r::sla::SupportTreeBuilder::ground_level, interconnect_pillars(), merge_result(), Slic3r::sla::SupportTreeConfig::object_elevation_mm, Slic3r::sla::SupportableMesh::pts, routing_to_ground(), routing_to_model(), Slic3r::sla::JobController::statuscb, and Slic3r::sla::JobController::stopcondition.

Referenced by Slic3r::sla::SupportTree::create().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ filter()

void Slic3r::sla::SupportTreeBuildsteps::filter ( )
653{
654 // Get the points that are too close to each other and keep only the
655 // first one
656 auto aliases = cluster(m_points, D_SP, 2);
657
658 PtIndices filtered_indices;
659 filtered_indices.reserve(aliases.size());
660 m_iheads.reserve(aliases.size());
661 m_iheadless.reserve(aliases.size());
662 for(auto& a : aliases) {
663 // Here we keep only the front point of the cluster.
664 filtered_indices.emplace_back(a.front());
665 }
666
667 // calculate the normals to the triangles for filtered points
668 auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm,
669 m_thr, filtered_indices);
670
671 // Not all of the support points have to be a valid position for
672 // support creation. The angle may be inappropriate or there may
673 // not be enough space for the pinhead. Filtering is applied for
674 // these reasons.
675
676 std::vector<Head> heads; heads.reserve(m_support_pts.size());
677 for (const SupportPoint &sp : m_support_pts) {
678 m_thr();
679 heads.emplace_back(
680 std::nan(""),
681 sp.head_front_radius,
682 0.,
683 m_cfg.head_penetration_mm,
684 Vec3d::Zero(), // dir
685 sp.pos.cast<double>() // displacement
686 );
687 }
688
689 std::function<void(unsigned, size_t, double)> filterfn;
690 filterfn = [this, &nmls, &heads, &filterfn](unsigned fidx, size_t i, double back_r) {
691 m_thr();
692
693 auto n = nmls.row(Eigen::Index(i));
694
695 // for all normals we generate the spherical coordinates and
696 // saturate the polar angle to 45 degrees from the bottom then
697 // convert back to standard coordinates to get the new normal.
698 // Then we just create a quaternion from the two normals
699 // (Quaternion::FromTwoVectors) and apply the rotation to the
700 // arrow head.
701
702 auto [polar, azimuth] = dir_to_spheric(n);
703
704 // skip if the tilt is not sane
705 if (polar < PI - m_cfg.normal_cutoff_angle) return;
706
707 // We saturate the polar angle to 3pi/4
708 polar = std::max(polar, PI - m_cfg.bridge_slope);
709
710 // save the head (pinpoint) position
711 Vec3d hp = m_points.row(fidx);
712
713 double lmin = m_cfg.head_width_mm, lmax = lmin;
714
715 if (back_r < m_cfg.head_back_radius_mm) {
716 lmin = 0., lmax = m_cfg.head_penetration_mm;
717 }
718
719 // The distance needed for a pinhead to not collide with model.
720 double w = lmin + 2 * back_r + 2 * m_cfg.head_front_radius_mm -
721 m_cfg.head_penetration_mm;
722
723 double pin_r = double(m_support_pts[fidx].head_front_radius);
724
725 // Reassemble the now corrected normal
726 auto nn = spheric_to_dir(polar, azimuth).normalized();
727
728 // check available distance
729 IndexedMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r,
730 back_r, w);
731
732 if (t.distance() < w) {
733 // Let's try to optimize this angle, there might be a
734 // viable normal that doesn't collide with the model
735 // geometry and its very close to the default.
736
737 Optimizer<AlgNLoptGenetic> solver(get_criteria(m_cfg));
738 solver.seed(0); // we want deterministic behavior
739
740 auto oresult = solver.to_max().optimize(
741 [this, pin_r, back_r, hp](const opt::Input<3> &input)
742 {
743 auto &[plr, azm, l] = input;
744
745 auto dir = spheric_to_dir(plr, azm).normalized();
746
748 hp, dir, pin_r, back_r, l).distance();
749 },
750 initvals({polar, azimuth, (lmin + lmax) / 2.}), // start with what we have
751 bounds({
752 {PI - m_cfg.bridge_slope, PI}, // Must not exceed the slope limit
753 {-PI, PI}, // azimuth can be a full search
754 {lmin, lmax}
755 }));
756
757 if(oresult.score > w) {
758 polar = std::get<0>(oresult.optimum);
759 azimuth = std::get<1>(oresult.optimum);
760 nn = spheric_to_dir(polar, azimuth).normalized();
761 lmin = std::get<2>(oresult.optimum);
762 t = IndexedMesh::hit_result(oresult.score);
763 }
764 }
765
766 if (t.distance() > w && hp(Z) + w * nn(Z) >= m_builder.ground_level) {
767 Head &h = heads[fidx];
768 h.id = fidx; h.dir = nn; h.width_mm = lmin; h.r_back_mm = back_r;
769 } else if (back_r > m_cfg.head_fallback_radius_mm) {
770 filterfn(fidx, i, m_cfg.head_fallback_radius_mm);
771 }
772 };
773
774 ccr::for_each(size_t(0), filtered_indices.size(),
775 [this, &filterfn, &filtered_indices] (size_t i) {
776 filterfn(filtered_indices[i], i, m_cfg.head_back_radius_mm);
777 });
778
779 for (size_t i = 0; i < heads.size(); ++i)
780 if (heads[i].is_valid()) {
781 m_builder.add_head(i, heads[i]);
782 m_iheads.emplace_back(i);
783 }
784
785 m_thr();
786}
IndexedMesh::hit_result pinhead_mesh_intersect(const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width, double safety_d)
Definition SupportTreeBuildsteps.cpp:201
PtIndices m_iheadless
Definition SupportTreeBuildsteps.hpp:216
const double D_SP
Definition SupportTreeBuildsteps.hpp:34
PointSet normals(const PointSet &points, const IndexedMesh &mesh, double eps, std::function< void()> thr, const std::vector< unsigned > &pt_indices)
Definition IndexedMesh.cpp:342
bool is_valid(const FontFile &font, unsigned int index)
Definition Emboss.cpp:284

References Slic3r::sla::bounds(), Slic3r::sla::cluster(), Slic3r::sla::D_SP, Slic3r::sla::Head::dir, Slic3r::sla::dir_to_spheric(), Slic3r::sla::IndexedMesh::hit_result::distance(), filter(), Slic3r::sla::get_criteria(), Slic3r::sla::SupportTreeNode::id, Slic3r::sla::initvals(), m_builder, m_cfg, m_iheadless, m_iheads, m_mesh, m_points, m_support_pts, m_thr, Slic3r::sla::normals(), Slic3r::opt::Optimizer< Method, Enable >::optimize(), PI, pinhead_mesh_intersect(), Slic3r::sla::Head::r_back_mm, Slic3r::opt::Optimizer< Method, Enable >::seed(), Slic3r::sla::spheric_to_dir(), Slic3r::opt::Optimizer< Method, Enable >::to_max(), Slic3r::sla::Head::width_mm, and Slic3r::sla::Z.

Referenced by execute(), and filter().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ interconnect()

bool Slic3r::sla::SupportTreeBuildsteps::interconnect ( const Pillar & pillar,
const Pillar & nextpillar )
private
317{
318 // We need to get the starting point of the zig-zag pattern. We have to
319 // be aware that the two head junctions are at different heights. We
320 // may start from the lowest junction and call it a day but this
321 // strategy would leave unconnected a lot of pillar duos where the
322 // shorter pillar is too short to start a new bridge but the taller
323 // pillar could still be bridged with the shorter one.
324 bool was_connected = false;
325
326 Vec3d supper = pillar.startpoint();
327 Vec3d slower = nextpillar.startpoint();
328 Vec3d eupper = pillar.endpoint();
329 Vec3d elower = nextpillar.endpoint();
330
331 double zmin = m_builder.ground_level + m_cfg.base_height_mm;
332 eupper(Z) = std::max(eupper(Z), zmin);
333 elower(Z) = std::max(elower(Z), zmin);
334
335 // The usable length of both pillars should be positive
336 if(slower(Z) - elower(Z) < 0) return false;
337 if(supper(Z) - eupper(Z) < 0) return false;
338
339 double pillar_dist = distance(Vec2d{slower(X), slower(Y)},
340 Vec2d{supper(X), supper(Y)});
341 double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope);
342 double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope);
343
344 if(pillar_dist < 2 * m_cfg.head_back_radius_mm ||
345 pillar_dist > m_cfg.max_pillar_link_distance_mm) return false;
346
347 if(supper(Z) < slower(Z)) supper.swap(slower);
348 if(eupper(Z) < elower(Z)) eupper.swap(elower);
349
350 double startz = 0, endz = 0;
351
352 startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z);
353 endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z);
354
355 if(slower(Z) - eupper(Z) < std::abs(zstep)) {
356 // no space for even one cross
357
358 // Get max available space
359 startz = std::min(supper(Z), slower(Z) - zstep);
360 endz = std::max(eupper(Z) + zstep, elower(Z));
361
362 // Align to center
363 double available_dist = (startz - endz);
364 double rounds = std::floor(available_dist / std::abs(zstep));
365 startz -= 0.5 * (available_dist - rounds * std::abs(zstep));
366 }
367
368 auto pcm = m_cfg.pillar_connection_mode;
369 bool docrosses =
372 pillar_dist > 2*m_cfg.base_radius_mm);
373
374 // 'sj' means starting junction, 'ej' is the end junction of a bridge.
375 // They will be swapped in every iteration thus the zig-zag pattern.
376 // According to a config parameter, a second bridge may be added which
377 // results in a cross connection between the pillars.
378 Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep;
379
380 // TODO: This is a workaround to not have a faulty last bridge
381 while(ej(Z) >= eupper(Z) /*endz*/) {
382 if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
383 {
384 m_builder.add_crossbridge(sj, ej, pillar.r);
385 was_connected = true;
386 }
387
388 // double bridging: (crosses)
389 if(docrosses) {
390 Vec3d sjback(ej(X), ej(Y), sj(Z));
391 Vec3d ejback(sj(X), sj(Y), ej(Z));
392 if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) &&
393 bridge_mesh_distance(sjback, dirv(sjback, ejback),
394 pillar.r) >= bridge_distance) {
395 // need to check collision for the cross stick
396 m_builder.add_crossbridge(sjback, ejback, pillar.r);
397 was_connected = true;
398 }
399 }
400
401 sj.swap(ej);
402 ej(Z) = sj(Z) + zstep;
403 }
404
405 return was_connected;
406}
@ cross
Definition SupportTree.hpp:29
@ dynamic
Definition SupportTree.hpp:30
Eigen::Matrix< double, 2, 1, Eigen::DontAlign > Vec2d
Definition Point.hpp:67

References bridge_mesh_distance(), Slic3r::sla::cross, Slic3r::sla::dirv(), Slic3r::sla::distance(), Slic3r::sla::dynamic, Slic3r::sla::Pillar::endpoint(), interconnect(), m_builder, m_cfg, Slic3r::sla::Pillar::r, Slic3r::sla::Pillar::startpoint(), Slic3r::sla::X, Slic3r::sla::Y, and Slic3r::sla::Z.

Referenced by interconnect(), and interconnect_pillars().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ interconnect_pillars()

void Slic3r::sla::SupportTreeBuildsteps::interconnect_pillars ( )
1088{
1089 // Now comes the algorithm that connects pillars with each other.
1090 // Ideally every pillar should be connected with at least one of its
1091 // neighbors if that neighbor is within max_pillar_link_distance
1092
1093 // Pillars with height exceeding H1 will require at least one neighbor
1094 // to connect with. Height exceeding H2 require two neighbors.
1095 double H1 = m_cfg.max_solo_pillar_height_mm;
1096 double H2 = m_cfg.max_dual_pillar_height_mm;
1097 double d = m_cfg.max_pillar_link_distance_mm;
1098
1099 //A connection between two pillars only counts if the height ratio is
1100 // bigger than 50%
1101 double min_height_ratio = 0.5;
1102
1103 std::set<unsigned long> pairs;
1104
1105 // A function to connect one pillar with its neighbors. THe number of
1106 // neighbors is given in the configuration. This function if called
1107 // for every pillar in the pillar index. A pair of pillar will not
1108 // be connected multiple times this is ensured by the 'pairs' set which
1109 // remembers the processed pillar pairs
1110 auto cascadefn =
1111 [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el)
1112 {
1113 Vec3d qp = el.first; // endpoint of the pillar
1114
1115 const Pillar& pillar = m_builder.pillar(el.second); // actual pillar
1116
1117 // Get the max number of neighbors a pillar should connect to
1118 unsigned neighbors = m_cfg.pillar_cascade_neighbors;
1119
1120 // connections are already enough for the pillar
1121 if(pillar.links >= neighbors) return;
1122
1123 double max_d = d * pillar.r / m_cfg.head_back_radius_mm;
1124 // Query all remaining points within reach
1125 auto qres = m_pillar_index.query([qp, max_d](const PointIndexEl& e){
1126 return distance(e.first, qp) < max_d;
1127 });
1128
1129 // sort the result by distance (have to check if this is needed)
1130 std::sort(qres.begin(), qres.end(),
1131 [qp](const PointIndexEl& e1, const PointIndexEl& e2){
1132 return distance(e1.first, qp) < distance(e2.first, qp);
1133 });
1134
1135 for(auto& re : qres) { // process the queried neighbors
1136
1137 if(re.second == el.second) continue; // Skip self
1138
1139 auto a = el.second, b = re.second;
1140
1141 // Get unique hash for the given pair (order doesn't matter)
1142 auto hashval = pairhash(a, b);
1143
1144 // Search for the pair amongst the remembered pairs
1145 if(pairs.find(hashval) != pairs.end()) continue;
1146
1147 const Pillar& neighborpillar = m_builder.pillar(re.second);
1148
1149 // this neighbor is occupied, skip
1150 if (neighborpillar.links >= neighbors) continue;
1151 if (neighborpillar.r < pillar.r) continue;
1152
1153 if(interconnect(pillar, neighborpillar)) {
1154 pairs.insert(hashval);
1155
1156 // If the interconnection length between the two pillars is
1157 // less than 50% of the longer pillar's height, don't count
1158 if(pillar.height < H1 ||
1159 neighborpillar.height / pillar.height > min_height_ratio)
1160 m_builder.increment_links(pillar);
1161
1162 if(neighborpillar.height < H1 ||
1163 pillar.height / neighborpillar.height > min_height_ratio)
1164 m_builder.increment_links(neighborpillar);
1165
1166 }
1167
1168 // connections are enough for one pillar
1169 if(pillar.links >= neighbors) break;
1170 }
1171 };
1172
1173 // Run the cascade for the pillars in the index
1174 m_pillar_index.foreach(cascadefn);
1175
1176 // We would be done here if we could allow some pillars to not be
1177 // connected with any neighbors. But this might leave the support tree
1178 // unprintable.
1179 //
1180 // The current solution is to insert additional pillars next to these
1181 // lonely pillars. One or even two additional pillar might get inserted
1182 // depending on the length of the lonely pillar.
1183
1184 size_t pillarcount = m_builder.pillarcount();
1185
1186 // Again, go through all pillars, this time in the whole support tree
1187 // not just the index.
1188 for(size_t pid = 0; pid < pillarcount; pid++) {
1189 auto pillar = [this, pid]() { return m_builder.pillar(pid); };
1190
1191 // Decide how many additional pillars will be needed:
1192
1193 unsigned needpillars = 0;
1194 if (pillar().bridges > m_cfg.max_bridges_on_pillar)
1195 needpillars = 3;
1196 else if (pillar().links < 2 && pillar().height > H2) {
1197 // Not enough neighbors to support this pillar
1198 needpillars = 2;
1199 } else if (pillar().links < 1 && pillar().height > H1) {
1200 // No neighbors could be found and the pillar is too long.
1201 needpillars = 1;
1202 }
1203
1204 needpillars = std::max(pillar().links, needpillars) - pillar().links;
1205 if (needpillars == 0) continue;
1206
1207 // Search for new pillar locations:
1208
1209 bool found = false;
1210 double alpha = 0; // goes to 2Pi
1211 double r = 2 * m_cfg.base_radius_mm;
1212 Vec3d pillarsp = pillar().startpoint();
1213
1214 // temp value for starting point detection
1215 Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r);
1216
1217 // A vector of bool for placement feasbility
1218 std::vector<bool> canplace(needpillars, false);
1219 std::vector<Vec3d> spts(needpillars); // vector of starting points
1220
1221 double gnd = m_builder.ground_level;
1222 double min_dist = m_cfg.pillar_base_safety_distance_mm +
1223 m_cfg.base_radius_mm + EPSILON;
1224
1225 while(!found && alpha < 2*PI) {
1226 for (unsigned n = 0;
1227 n < needpillars && (!n || canplace[n - 1]);
1228 n++)
1229 {
1230 double a = alpha + n * PI / 3;
1231 Vec3d s = sp;
1232 s(X) += std::cos(a) * r;
1233 s(Y) += std::sin(a) * r;
1234 spts[n] = s;
1235
1236 // Check the path vertically down
1237 Vec3d check_from = s + Vec3d{0., 0., pillar().r};
1238 auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r);
1239 Vec3d gndsp{s(X), s(Y), gnd};
1240
1241 // If the path is clear, check for pillar base collisions
1242 canplace[n] = std::isinf(hr.distance()) &&
1243 std::sqrt(m_mesh.squared_distance(gndsp)) >
1244 min_dist;
1245 }
1246
1247 found = std::all_of(canplace.begin(), canplace.end(),
1248 [](bool v) { return v; });
1249
1250 // 20 angles will be tried...
1251 alpha += 0.1 * PI;
1252 }
1253
1254 std::vector<long> newpills;
1255 newpills.reserve(needpillars);
1256
1257 if (found)
1258 for (unsigned n = 0; n < needpillars; n++) {
1259 Vec3d s = spts[n];
1260 Pillar p(Vec3d{s.x(), s.y(), gnd}, s.z() - gnd, pillar().r);
1261
1262 if (interconnect(pillar(), p)) {
1263 Pillar &pp = m_builder.pillar(m_builder.add_pillar(p));
1264
1265 add_pillar_base(pp.id);
1266
1267 m_pillar_index.insert(pp.endpoint(), unsigned(pp.id));
1268
1269 m_builder.add_junction(s, pillar().r);
1270 double t = bridge_mesh_distance(pillarsp, dirv(pillarsp, s),
1271 pillar().r);
1272 if (distance(pillarsp, s) < t)
1273 m_builder.add_bridge(pillarsp, s, pillar().r);
1274
1275 if (pillar().endpoint()(Z) > m_builder.ground_level + pillar().r)
1276 m_builder.add_junction(pillar().endpoint(), pillar().r);
1277
1278 newpills.emplace_back(pp.id);
1279 m_builder.increment_links(pillar());
1280 m_builder.increment_links(pp);
1281 }
1282 }
1283
1284 if(!newpills.empty()) {
1285 for(auto it = newpills.begin(), nx = std::next(it);
1286 nx != newpills.end(); ++it, ++nx) {
1287 const Pillar& itpll = m_builder.pillar(*it);
1288 const Pillar& nxpll = m_builder.pillar(*nx);
1289 if(interconnect(itpll, nxpll)) {
1290 m_builder.increment_links(itpll);
1291 m_builder.increment_links(nxpll);
1292 }
1293 }
1294
1295 m_pillar_index.foreach(cascadefn);
1296 }
1297 }
1298}
bool interconnect(const Pillar &pillar, const Pillar &nextpillar)
Definition SupportTreeBuildsteps.cpp:315
IntegerOnly< DoubleI > pairhash(I a, I b)
Definition SupportTreeBuildsteps.hpp:191

References add_pillar_base(), bridge_mesh_distance(), bridge_mesh_intersect(), Slic3r::sla::dirv(), Slic3r::sla::distance(), Slic3r::sla::DOWN, Slic3r::sla::Pillar::endpoint(), EPSILON, Slic3r::sla::Pillar::height, Slic3r::sla::SupportTreeNode::id, interconnect(), interconnect_pillars(), Slic3r::sla::Pillar::links, m_builder, m_cfg, m_mesh, m_pillar_index, Slic3r::sla::pairhash(), PI, Slic3r::sla::Pillar::r, Slic3r::sla::X, Slic3r::sla::Y, and Slic3r::sla::Z.

Referenced by execute(), and interconnect_pillars().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ merge_result()

void Slic3r::sla::SupportTreeBuildsteps::merge_result ( )
inline
389{ m_builder.merged_mesh(); }

References m_builder.

Referenced by execute().

Here is the caller graph for this function:

◆ pinhead_mesh_intersect() [1/2]

IndexedMesh::hit_result Slic3r::sla::SupportTreeBuildsteps::pinhead_mesh_intersect ( const Vec3d & s,
const Vec3d & dir,
double r_pin,
double r_back,
double width )
inlineprivate
275 {
276 return pinhead_mesh_intersect(s, dir, r_pin, r_back, width,
277 r_back * m_cfg.safety_distance_mm /
278 m_cfg.head_back_radius_mm);
279 }

References m_cfg, and pinhead_mesh_intersect().

Here is the call graph for this function:

◆ pinhead_mesh_intersect() [2/2]

IndexedMesh::hit_result Slic3r::sla::SupportTreeBuildsteps::pinhead_mesh_intersect ( const Vec3d & s,
const Vec3d & dir,
double r_pin,
double r_back,
double width,
double safety_d )
private
208{
209 static const size_t SAMPLES = 8;
210
211 // Move away slightly from the touching point to avoid raycasting on the
212 // inner surface of the mesh.
213
214 auto& m = m_mesh;
215 using HitResult = IndexedMesh::hit_result;
216
217 // Hit results
218 std::array<HitResult, SAMPLES> hits;
219
220 struct Rings {
221 double rpin;
222 double rback;
223 Vec3d spin;
224 Vec3d sback;
225 PointRing<SAMPLES> ring;
226
227 Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); }
228 Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); }
229 } rings {r_pin + sd, r_back + sd, s, s + width * dir, dir};
230
231 // We will shoot multiple rays from the head pinpoint in the direction
232 // of the pinhead robe (side) surface. The result will be the smallest
233 // hit distance.
234
235 ccr::for_each(size_t(0), hits.size(),
236 [&m, &rings, sd, &hits](size_t i) {
237
238 // Point on the circle on the pin sphere
239 Vec3d ps = rings.pinring(i);
240 // This is the point on the circle on the back sphere
241 Vec3d p = rings.backring(i);
242
243 auto &hit = hits[i];
244
245 // Point ps is not on mesh but can be inside or
246 // outside as well. This would cause many problems
247 // with ray-casting. To detect the position we will
248 // use the ray-casting result (which has an is_inside
249 // predicate).
250
251 Vec3d n = (p - ps).normalized();
252 auto q = m.query_ray_hit(ps + sd * n, n);
253
254 if (q.is_inside()) { // the hit is inside the model
255 if (q.distance() > rings.rpin) {
256 // If we are inside the model and the hit
257 // distance is bigger than our pin circle
258 // diameter, it probably indicates that the
259 // support point was already inside the
260 // model, or there is really no space
261 // around the point. We will assign a zero
262 // hit distance to these cases which will
263 // enforce the function return value to be
264 // an invalid ray with zero hit distance.
265 // (see min_element at the end)
266 hit = HitResult(0.0);
267 } else {
268 // re-cast the ray from the outside of the
269 // object. The starting point has an offset
270 // of 2*safety_distance because the
271 // original ray has also had an offset
272 auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n);
273 hit = q2;
274 }
275 } else
276 hit = q;
277 });
278
279 return min_hit(hits);
280}
coord_t width(const BoundingBox &box)
Definition Arrange.cpp:1184
Eigen::Matrix< double, 3, 1, Eigen::DontAlign > Vec3d
Definition Point.hpp:68

References Slic3r::sla::PointRing< N >::get(), and m_mesh.

Referenced by filter(), pinhead_mesh_intersect(), and search_widening_path().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ray_mesh_intersect()

IndexedMesh::hit_result Slic3r::sla::SupportTreeBuildsteps::ray_mesh_intersect ( const Vec3d & s,
const Vec3d & dir )
inlineprivate
246 {
247 return m_mesh.query_ray_hit(s, dir);
248 }

References m_mesh.

◆ routing_to_ground()

void Slic3r::sla::SupportTreeBuildsteps::routing_to_ground ( )
842{
843 ClusterEl cl_centroids;
844 cl_centroids.reserve(m_pillar_clusters.size());
845
846 for (auto &cl : m_pillar_clusters) {
847 m_thr();
848
849 // place all the centroid head positions into the index. We
850 // will query for alternative pillar positions. If a sidehead
851 // cannot connect to the cluster centroid, we have to search
852 // for another head with a full pillar. Also when there are two
853 // elements in the cluster, the centroid is arbitrary and the
854 // sidehead is allowed to connect to a nearby pillar to
855 // increase structural stability.
856
857 if (cl.empty()) continue;
858
859 // get the current cluster centroid
860 auto & thr = m_thr;
861 const auto &points = m_points;
862
863 long lcid = cluster_centroid(
864 cl, [&points](size_t idx) { return points.row(long(idx)); },
865 [thr](const Vec3d &p1, const Vec3d &p2) {
866 thr();
867 return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y)));
868 });
869
870 assert(lcid >= 0);
871 unsigned hid = cl[size_t(lcid)]; // Head ID
872
873 cl_centroids.emplace_back(hid);
874
875 Head &h = m_builder.head(hid);
876
877 if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) {
878 BOOST_LOG_TRIVIAL(warning)
879 << "Pillar cannot be created for support point id: " << hid;
880 m_iheads_onmodel.emplace_back(h.id);
881 continue;
882 }
883 }
884
885 // now we will go through the clusters ones again and connect the
886 // sidepoints with the cluster centroid (which is a ground pillar)
887 // or a nearby pillar if the centroid is unreachable.
888 size_t ci = 0;
889 for (auto cl : m_pillar_clusters) {
890 m_thr();
891
892 auto cidx = cl_centroids[ci++];
893
894 auto q = m_pillar_index.query(m_builder.head(cidx).junction_point(), 1);
895 if (!q.empty()) {
896 long centerpillarID = q.front().second;
897 for (auto c : cl) {
898 m_thr();
899 if (c == cidx) continue;
900
901 auto &sidehead = m_builder.head(c);
902
903 if (!connect_to_nearpillar(sidehead, centerpillarID) &&
904 !search_pillar_and_connect(sidehead)) {
905 Vec3d pstart = sidehead.junction_point();
906 // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl};
907 // Could not find a pillar, create one
908 create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id);
909 }
910 }
911 }
912 }
913}
bool search_pillar_and_connect(const Head &source)
Definition SupportTreeBuildsteps.cpp:1020
bool connect_to_nearpillar(const Head &head, long nearpillar_id)
Definition SupportTreeBuildsteps.cpp:408
std::vector< unsigned > ClusterEl
Definition Clustering.hpp:15
long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df)
Definition Clustering.hpp:37

References Slic3r::sla::cluster_centroid(), connect_to_nearpillar(), create_ground_pillar(), Slic3r::sla::Head::dir, Slic3r::sla::distance(), Slic3r::sla::SupportTreeNode::id, Slic3r::sla::Head::junction_point(), m_builder, m_iheads_onmodel, m_pillar_clusters, m_pillar_index, m_points, m_thr, Slic3r::sla::Head::r_back_mm, routing_to_ground(), search_pillar_and_connect(), Slic3r::sla::X, and Slic3r::sla::Y.

Referenced by execute(), and routing_to_ground().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ routing_to_model()

void Slic3r::sla::SupportTreeBuildsteps::routing_to_model ( )
1058{
1059 // We need to check if there is an easy way out to the bed surface.
1060 // If it can be routed there with a bridge shorter than
1061 // min_bridge_distance.
1062
1063 ccr::for_each(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
1064 [this] (const unsigned idx) {
1065 m_thr();
1066
1067 auto& head = m_builder.head(idx);
1068
1069 // Search nearby pillar
1070 if (search_pillar_and_connect(head)) { return; }
1071
1072 // Cannot connect to nearby pillar. We will try to search for
1073 // a route to the ground.
1074 if (connect_to_ground(head)) { return; }
1075
1076 // No route to the ground, so connect to the model body as a last resort
1077 if (connect_to_model_body(head)) { return; }
1078
1079 // We have failed to route this head.
1080 BOOST_LOG_TRIVIAL(warning)
1081 << "Failed to route model facing support point. ID: " << idx;
1082
1083 head.invalidate();
1084 });
1085}
bool connect_to_model_body(Head &head)
Definition SupportTreeBuildsteps.cpp:967

References connect_to_ground(), connect_to_model_body(), m_iheads_onmodel, and routing_to_model().

Referenced by execute(), and routing_to_model().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ search_pillar_and_connect()

bool Slic3r::sla::SupportTreeBuildsteps::search_pillar_and_connect ( const Head & source)
private
1021{
1022 // Hope that a local copy takes less time than the whole search loop.
1023 // We also need to remove elements progressively from the copied index.
1024 PointIndex spindex = m_pillar_index.guarded_clone();
1025
1026 long nearest_id = SupportTreeNode::ID_UNSET;
1027
1028 Vec3d querypt = source.junction_point();
1029
1030 while(nearest_id < 0 && !spindex.empty()) { m_thr();
1031 // loop until a suitable head was not found
1032 // if there is a pillar closer than the cluster center
1033 // (this may happen as the clustering is not perfect)
1034 // than we will bridge to this closer pillar
1035
1036 Vec3d qp(querypt(X), querypt(Y), m_builder.ground_level);
1037 auto qres = spindex.nearest(qp, 1);
1038 if(qres.empty()) break;
1039
1040 auto ne = qres.front();
1041 nearest_id = ne.second;
1042
1043 if(nearest_id >= 0) {
1044 if (size_t(nearest_id) < m_builder.pillarcount()) {
1045 if(!connect_to_nearpillar(source, nearest_id) ||
1046 m_builder.pillar(nearest_id).r < source.r_back_mm) {
1047 nearest_id = SupportTreeNode::ID_UNSET; // continue searching
1048 spindex.remove(ne); // without the current pillar
1049 }
1050 }
1051 }
1052 }
1053
1054 return nearest_id >= 0;
1055}

References connect_to_nearpillar(), Slic3r::sla::PointIndex::empty(), Slic3r::sla::SupportTreeNode::ID_UNSET, Slic3r::sla::Head::junction_point(), m_builder, m_pillar_index, m_thr, Slic3r::sla::PointIndex::nearest(), Slic3r::sla::Head::r_back_mm, Slic3r::sla::PointIndex::remove(), search_pillar_and_connect(), Slic3r::sla::X, and Slic3r::sla::Y.

Referenced by routing_to_ground(), and search_pillar_and_connect().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ search_widening_path()

std::optional< DiffBridge > Slic3r::sla::SupportTreeBuildsteps::search_widening_path ( const Vec3d & jp,
const Vec3d & dir,
double radius,
double new_radius )
private
609{
610 double w = radius + 2 * m_cfg.head_back_radius_mm;
611 double stopval = w + jp.z() - m_builder.ground_level;
612 Optimizer<AlgNLoptSubplex> solver(get_criteria(m_cfg).stop_score(stopval));
613
614 auto [polar, azimuth] = dir_to_spheric(dir);
615
616 double fallback_ratio = radius / m_cfg.head_back_radius_mm;
617
618 auto oresult = solver.to_max().optimize(
619 [this, jp, radius, new_radius](const opt::Input<3> &input) {
620 auto &[plr, azm, t] = input;
621
622 auto d = spheric_to_dir(plr, azm).normalized();
623 double ret = pinhead_mesh_intersect(jp, d, radius, new_radius, t)
624 .distance();
625 double down = bridge_mesh_distance(jp + t * d, d, new_radius);
626
627 if (ret > t && std::isinf(down))
628 ret += jp.z() - m_builder.ground_level;
629
630 return ret;
631 },
632 initvals({polar, azimuth, w}), // start with what we have
633 bounds({
634 {PI - m_cfg.bridge_slope, PI}, // Must not exceed the slope limit
635 {-PI, PI}, // azimuth can be a full search
636 {radius + m_cfg.head_back_radius_mm,
637 fallback_ratio * m_cfg.max_bridge_length_mm}
638 }));
639
640 if (oresult.score >= stopval) {
641 polar = std::get<0>(oresult.optimum);
642 azimuth = std::get<1>(oresult.optimum);
643 double t = std::get<2>(oresult.optimum);
644 Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
645
646 return DiffBridge(jp, endp, radius, m_cfg.head_back_radius_mm);
647 }
648
649 return {};
650}
@ down
Definition MarchingSquares.hpp:189

References Slic3r::sla::bounds(), bridge_mesh_distance(), Slic3r::sla::dir_to_spheric(), Slic3r::sla::get_criteria(), Slic3r::sla::initvals(), m_builder, m_cfg, Slic3r::opt::Optimizer< Method, Enable >::optimize(), PI, pinhead_mesh_intersect(), search_widening_path(), Slic3r::sla::spheric_to_dir(), and Slic3r::opt::Optimizer< Method, Enable >::to_max().

Referenced by create_ground_pillar(), and search_widening_path().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ m_bridge_mutex

ccr::BlockingMutex Slic3r::sla::SupportTreeBuildsteps::m_bridge_mutex
private

Referenced by connect_to_nearpillar().

◆ m_builder

◆ m_cfg

◆ m_head_to_ground_scans

std::map<unsigned, IndexedMesh::hit_result> Slic3r::sla::SupportTreeBuildsteps::m_head_to_ground_scans
private

Referenced by classify(), and connect_to_model_body().

◆ m_iheadless

PtIndices Slic3r::sla::SupportTreeBuildsteps::m_iheadless
private

Referenced by filter().

◆ m_iheads

PtIndices Slic3r::sla::SupportTreeBuildsteps::m_iheads
private

Referenced by classify(), and filter().

◆ m_iheads_onmodel

PtIndices Slic3r::sla::SupportTreeBuildsteps::m_iheads_onmodel
private

◆ m_mesh

◆ m_pillar_clusters

std::vector<PtIndices> Slic3r::sla::SupportTreeBuildsteps::m_pillar_clusters
private

Referenced by classify(), and routing_to_ground().

◆ m_pillar_index

PillarIndex Slic3r::sla::SupportTreeBuildsteps::m_pillar_index
private

◆ m_points

PointSet Slic3r::sla::SupportTreeBuildsteps::m_points
private

◆ m_support_nmls

PointSet Slic3r::sla::SupportTreeBuildsteps::m_support_nmls
private

Referenced by SupportTreeBuildsteps().

◆ m_support_pts

const std::vector<SupportPoint>& Slic3r::sla::SupportTreeBuildsteps::m_support_pts
private

Referenced by SupportTreeBuildsteps(), and filter().

◆ m_thr

ThrowOnCancel Slic3r::sla::SupportTreeBuildsteps::m_thr
private

The documentation for this class was generated from the following files: