Replacing old model routing with more advanced algorithm.

Interconnection still missing.
This commit is contained in:
tamasmeszaros 2019-02-28 19:05:11 +01:00
parent 93c57612bf
commit 450f817c09
3 changed files with 374 additions and 255 deletions

View file

@ -70,24 +70,32 @@ public:
// Result of a raycast // Result of a raycast
class hit_result { class hit_result {
double m_t = std::numeric_limits<double>::infinity(); double m_t = std::nan("");
int m_face_id = -1; int m_face_id = -1;
const EigenMesh3D& m_mesh; std::reference_wrapper<const EigenMesh3D> m_mesh;
Vec3d m_dir; Vec3d m_dir;
inline hit_result(const EigenMesh3D& em): m_mesh(em) {} Vec3d m_source;
friend class EigenMesh3D; friend class EigenMesh3D;
public: public:
// A valid object of this class can only be obtained from
// EigenMesh3D::query_ray_hit method.
explicit inline hit_result(const EigenMesh3D& em): m_mesh(em) {}
inline double distance() const { return m_t; } inline double distance() const { return m_t; }
inline const Vec3d& direction() const { return m_dir; } inline const Vec3d& direction() const { return m_dir; }
inline Vec3d position() const { return m_source + m_dir * m_t; }
inline int face() const { return m_face_id; } inline int face() const { return m_face_id; }
// Hit_result can decay into a double as the hit distance.
inline operator double() const { return distance(); }
inline Vec3d normal() const { inline Vec3d normal() const {
if(m_face_id < 0) return {}; if(m_face_id < 0) return {};
auto trindex = m_mesh.m_F.row(m_face_id); auto trindex = m_mesh.get().m_F.row(m_face_id);
const Vec3d& p1 = m_mesh.V().row(trindex(0)); const Vec3d& p1 = m_mesh.get().V().row(trindex(0));
const Vec3d& p2 = m_mesh.V().row(trindex(1)); const Vec3d& p2 = m_mesh.get().V().row(trindex(1));
const Vec3d& p3 = m_mesh.V().row(trindex(2)); const Vec3d& p3 = m_mesh.get().V().row(trindex(2));
Eigen::Vector3d U = p2 - p1; Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1; Eigen::Vector3d V = p3 - p1;
return U.cross(V).normalized(); return U.cross(V).normalized();

View file

@ -15,6 +15,11 @@
#include <libnest2d/optimizers/nlopt/simplex.hpp> #include <libnest2d/optimizers/nlopt/simplex.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
#include <libslic3r/I18N.hpp>
//! macro used to mark string used at localization,
//! return same string
#define L(s) Slic3r::I18N::translate(s)
/** /**
* Terminology: * Terminology:
@ -335,6 +340,11 @@ struct Head {
return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm;
} }
static double fullwidth(const SupportConfig& cfg) {
return 2 * cfg.head_front_radius_mm + cfg.head_width_mm +
2 * cfg.head_back_radius_mm - cfg.head_penetration_mm;
}
Vec3d junction_point() const { Vec3d junction_point() const {
return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir;
} }
@ -381,7 +391,7 @@ struct Pillar {
assert(steps > 0); assert(steps > 0);
double h = jp(Z) - endp(Z); double h = jp(Z) - endp(Z);
assert(h > 0); // Endpoint is below the starting point if(h > 0) { // Endpoint is below the starting point
// We just create a bridge geometry with the pillar parameters and // We just create a bridge geometry with the pillar parameters and
// move the data. // move the data.
@ -389,6 +399,7 @@ struct Pillar {
mesh.points.swap(body.points); mesh.points.swap(body.points);
mesh.indices.swap(body.indices); mesh.indices.swap(body.indices);
} }
}
Pillar(const Junction& junc, const Vec3d& endp): Pillar(const Junction& junc, const Vec3d& endp):
Pillar(junc.pos, endp, junc.r, junc.steps){} Pillar(junc.pos, endp, junc.r, junc.steps){}
@ -569,9 +580,20 @@ bool operator==(const SpatElement& e1, const SpatElement& e2) {
// Clustering a set of points by the given criteria. // Clustering a set of points by the given criteria.
ClusteredPoints cluster( ClusteredPoints cluster(
const PointSet& points, const std::vector<unsigned>& indices,
std::function<bool(const SpatElement&, const SpatElement&)> pred,
unsigned max_points = 0);
inline ClusteredPoints cluster(
const PointSet& points, const PointSet& points,
std::function<bool(const SpatElement&, const SpatElement&)> pred, std::function<bool(const SpatElement&, const SpatElement&)> pred,
unsigned max_points = 0, const std::vector<unsigned>& indices = {}); unsigned max_points = 0)
{
std::vector<unsigned> indices(size_t(points.rows()), 0);
std::iota(indices.begin(), indices.end(), 0);
return cluster(points, indices, pred, max_points);
}
// This class will hold the support tree meshes with some additional bookkeeping // This class will hold the support tree meshes with some additional bookkeeping
// as well. Various parts of the support geometry are stored separately and are // as well. Various parts of the support geometry are stored separately and are
@ -628,8 +650,20 @@ public:
return m_pillars.back(); return m_pillars.back();
} }
template<class...Args> Pillar& add_pillar(const Vec3d& startp,
const Vec3d& endp,
double r)
{
m_pillars.emplace_back(startp, endp, r);
Pillar& pillar = m_pillars.back();
pillar.id = long(m_pillars.size() - 1);
pillar.starts_from_head = false;
meshcache_valid = false;
return m_pillars.back();
}
const Head& pillar_head(long pillar_id) const { const Head& pillar_head(long pillar_id) const {
assert(pillar_id >= 0 && pillar_id < m_pillars.size()); assert(pillar_id >= 0 && pillar_id < long(m_pillars.size()));
const Pillar& p = m_pillars[size_t(pillar_id)]; const Pillar& p = m_pillars[size_t(pillar_id)];
assert(p.starts_from_head && p.start_junction_id >= 0); assert(p.starts_from_head && p.start_junction_id >= 0);
auto it = m_heads.find(unsigned(p.start_junction_id)); auto it = m_heads.find(unsigned(p.start_junction_id));
@ -641,7 +675,7 @@ public:
auto it = m_heads.find(headid); auto it = m_heads.find(headid);
assert(it != m_heads.end()); assert(it != m_heads.end());
const Head& h = it->second; const Head& h = it->second;
assert(h.pillar_id >= 0 && h.pillar_id < m_pillars.size()); assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size()));
return m_pillars[size_t(h.pillar_id)]; return m_pillars[size_t(h.pillar_id)];
} }
@ -938,15 +972,13 @@ class SLASupportTree::Algorithm {
PtIndices m_iheads; // support points with pinhead PtIndices m_iheads; // support points with pinhead
PtIndices m_iheadless; // headless support points PtIndices m_iheadless; // headless support points
PtIndices m_iheads_onmodel; // supp. pts. connecting to model
// supp. pts. connecting to model: point index and the ray hit data
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
// normals for support points from model faces. // normals for support points from model faces.
PointSet m_support_nmls; PointSet m_support_nmls;
// Captures the height of the processed support points from the ground
// or the model surface
std::vector<double> m_ptheights;
// Clusters of points which can reach the ground directly and can be // Clusters of points which can reach the ground directly and can be
// bridged to one central pillar // bridged to one central pillar
std::vector<PtIndices> m_pillar_clusters; std::vector<PtIndices> m_pillar_clusters;
@ -964,6 +996,9 @@ class SLASupportTree::Algorithm {
// come in handy. // come in handy.
ThrowOnCancel m_thr; ThrowOnCancel m_thr;
// A spatial index to easily find strong pillars to connect to.
SpatIndex m_pillar_index;
inline double ray_mesh_intersect(const Vec3d& s, inline double ray_mesh_intersect(const Vec3d& s,
const Vec3d& dir) const Vec3d& dir)
{ {
@ -982,7 +1017,8 @@ class SLASupportTree::Algorithm {
// samples: how many rays will be shot // samples: how many rays will be shot
// safety distance: This will be added to the radiuses to have a safety // safety distance: This will be added to the radiuses to have a safety
// distance from the mesh. // distance from the mesh.
double pinhead_mesh_intersect(const Vec3d& s, EigenMesh3D::hit_result pinhead_mesh_intersect(
const Vec3d& s,
const Vec3d& dir, const Vec3d& dir,
double r_pin, double r_pin,
double r_back, double r_back,
@ -1012,6 +1048,12 @@ class SLASupportTree::Algorithm {
std::vector<double> phis(samples); std::vector<double> phis(samples);
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
auto& m = m_mesh;
using HitResult = EigenMesh3D::hit_result;
// Hit results
std::vector<HitResult> hits(samples, HitResult(m));
// We have to address the case when the direction vector v (same as // We have to address the case when the direction vector v (same as
// dir) is coincident with one of the world axes. In this case two of // dir) is coincident with one of the world axes. In this case two of
// its components will be completely zero and one is 1.0. Our method // its components will be completely zero and one is 1.0. Our method
@ -1033,10 +1075,10 @@ class SLASupportTree::Algorithm {
// Now a and b vectors are perpendicular to v and to each other. // Now a and b vectors are perpendicular to v and to each other.
// Together they define the plane where we have to iterate with the // Together they define the plane where we have to iterate with the
// given angles in the 'phis' vector // given angles in the 'phis' vector
auto& m = m_mesh;
tbb::parallel_for(size_t(0), phis.size(), tbb::parallel_for(size_t(0), phis.size(),
[&phis, &m, sd, r_pin, r_back, s, a, b, c](size_t i) [&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c]
(size_t i)
{ {
double& phi = phis[i]; double& phi = phis[i];
double sinphi = std::sin(phi); double sinphi = std::sin(phi);
@ -1071,12 +1113,17 @@ class SLASupportTree::Algorithm {
else { else {
// re-cast the ray from the outside of the object // re-cast the ray from the outside of the object
auto q2 = m.query_ray_hit(ps + (q.distance() + 2*sd)*n, n); auto q2 = m.query_ray_hit(ps + (q.distance() + 2*sd)*n, n);
phi = q2.distance(); hits[i] = q2;
} }
} else phi = q.distance(); } else hits[i] = q;
}); });
auto mit = std::min_element(phis.begin(), phis.end()); auto mit = std::min_element(hits.begin(), hits.end(),
[](const HitResult& hr1,
const HitResult& hr2)
{
return hr1.distance() < hr2.distance();
});
return *mit; return *mit;
} }
@ -1085,7 +1132,8 @@ class SLASupportTree::Algorithm {
// If the function is used for headless sticks, the ins_check parameter // 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 // have to be true as the beginning of the stick might be inside the model
// geometry. // geometry.
double bridge_mesh_intersect(const Vec3d& s, EigenMesh3D::hit_result bridge_mesh_intersect(
const Vec3d& s,
const Vec3d& dir, const Vec3d& dir,
double r, double r,
bool ins_check = false, bool ins_check = false,
@ -1116,9 +1164,14 @@ class SLASupportTree::Algorithm {
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
auto& m = m_mesh; auto& m = m_mesh;
using HitResult = EigenMesh3D::hit_result;
// Hit results
std::vector<HitResult> hits(samples, HitResult(m));
tbb::parallel_for(size_t(0), phis.size(), tbb::parallel_for(size_t(0), phis.size(),
[&m, &phis, a, b, sd, dir, r, s, ins_check](size_t i) [&m, &phis, a, b, sd, dir, r, s, ins_check, &hits]
(size_t i)
{ {
double& phi = phis[i]; double& phi = phis[i];
double sinphi = std::sin(phi); double sinphi = std::sin(phi);
@ -1142,12 +1195,17 @@ class SLASupportTree::Algorithm {
auto hr2 = auto hr2 =
m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir);
phi = hr2.distance(); hits[i] = hr2;
} }
} else phi = hr.distance(); } else hits[i] = hr;
}); });
auto mit = std::min_element(phis.begin(), phis.end()); auto mit = std::min_element(hits.begin(), hits.end(),
[](const HitResult& hr1,
const HitResult& hr2)
{
return hr1.distance() < hr2.distance();
});
return *mit; return *mit;
} }
@ -1209,6 +1267,88 @@ class SLASupportTree::Algorithm {
} }
} }
long search_nearest(const Vec3d& querypoint)
{
SpatIndex spindex = m_pillar_index;
long nearest_id = -1;
const double gndlvl = m_result.ground_level;
while(nearest_id < 0 && !spindex.empty()) { m_thr();
// loop until a suitable head is not found
// if there is a pillar closer than the cluster center
// (this may happen as the clustering is not perfect)
// than we will bridge to this closer pillar
Vec3d qp(querypoint(X), querypoint(Y), gndlvl);
auto ne = spindex.nearest(qp, 1).front();
const Head& nearhead = m_result.head(ne.second);
Vec3d nearhead_jp = nearhead.junction_point();
double dist2d = distance(qp, ne.first);
// Bridge endpoint on the main pillar
Vec3d bridge_ep(nearhead_jp(X), nearhead_jp(Y), querypoint(Z) +
dist2d * std::tan(-m_cfg.bridge_slope));
if(bridge_ep(Z) > nearhead_jp(Z)) {
// If the sidepoint cannot connect to the pillar
// from its head junction, just skip this pillar.
spindex.remove(ne);
continue;
}
double d = distance(querypoint, bridge_ep);
// There will be a minimum distance from the ground where the
// bridge is allowed to connect. This is an empiric value.
double minz = gndlvl + 2 * m_cfg.head_width_mm;
// WARNING: previously, max_bridge_length was divided by two.
// I don't remember if this was intentional or by accident. There
// is no logical reason why it shouldn't be used directly.
if(bridge_ep(Z) <= minz || d > m_cfg.max_bridge_length_mm) break;
double chkd = bridge_mesh_intersect(querypoint,
dirv(querypoint, bridge_ep),
m_cfg.head_back_radius_mm);
if(chkd >= d) nearest_id = ne.second;
spindex.remove(ne);
}
return nearest_id;
}
void connect_to_nearhead(const Head& head, const Head& nearhead) {
Vec3d hjp = head.junction_point();
Vec3d headjp = hjp;
Vec3d nearheadjp = nearhead.junction_point();
double r = m_cfg.head_back_radius_mm;
double d = distance(Vec2d{headjp(X), headjp(Y)},
Vec2d{nearheadjp(X), nearheadjp(Y)});
Vec3d touchjp(nearheadjp(X), nearheadjp(Y), headjp(Z) +
d * std::tan(-m_cfg.bridge_slope));
if(touchjp(Z) > nearheadjp(Z)) {
double hdiff = touchjp(Z) - nearheadjp(Z);
headjp(Z) -= hdiff;
touchjp(Z) -= hdiff;
// create a pillar without base,
// it doesn't connect to ground just to an existing
// shorter pillar
m_result.add_pillar(unsigned(nearhead.id), headjp, r);
}
if(headjp(Z) < hjp(Z)) m_result.add_junction(headjp, r);
if(touchjp(Z) >= nearheadjp(Z)) m_result.add_junction(touchjp, r);
m_result.add_bridge(headjp, touchjp, r);
}
public: public:
Algorithm(const SupportConfig& config, Algorithm(const SupportConfig& config,
@ -1220,7 +1360,6 @@ public:
m_mesh(emesh), m_mesh(emesh),
m_support_pts(support_pts), m_support_pts(support_pts),
m_support_nmls(support_pts.size(), 3), m_support_nmls(support_pts.size(), 3),
m_ptheights(support_pts.size(), 0.0),
m_result(result), m_result(result),
m_points(support_pts.size(), 3), m_points(support_pts.size(), 3),
m_thr(thr) m_thr(thr)
@ -1407,7 +1546,6 @@ public:
void classify() void classify()
{ {
// We should first get the heads that reach the ground directly // We should first get the heads that reach the ground directly
m_ptheights.reserve(m_iheads.size());
PtIndices ground_head_indices; PtIndices ground_head_indices;
ground_head_indices.reserve(m_iheads.size()); ground_head_indices.reserve(m_iheads.size());
m_iheads_onmodel.reserve(m_iheads.size()); m_iheads_onmodel.reserve(m_iheads.size());
@ -1425,18 +1563,10 @@ public:
Vec3d headjp = head.junction_point(); Vec3d headjp = head.junction_point();
// collision check // collision check
double t = bridge_mesh_intersect(headjp, n, r); auto hit = bridge_mesh_intersect(headjp, n, r);
// Precise distance measurement if(std::isinf(hit)) ground_head_indices.emplace_back(i);
double tprec = ray_mesh_intersect(headjp, n); else m_iheads_onmodel.emplace_back(std::make_pair(i, hit));
// Save the distance from a surface in the Z axis downwards. It
// may be infinity but that is telling us that it touches the
// ground.
m_ptheights.emplace_back(tprec);
if(std::isinf(t)) ground_head_indices.emplace_back(i);
else m_iheads_onmodel.emplace_back(i);
} }
// We want to search for clusters of points that are far enough // We want to search for clusters of points that are far enough
@ -1445,13 +1575,13 @@ public:
// possibly in their centroid support point. // possibly in their centroid support point.
auto d_base = 2*m_cfg.base_radius_mm; auto d_base = 2*m_cfg.base_radius_mm;
auto& thr = m_thr; auto& thr = m_thr;
m_pillar_clusters = cluster(m_points, m_pillar_clusters = cluster(m_points, ground_head_indices,
[thr, d_base](const SpatElement& p, const SpatElement& s) [thr, d_base](const SpatElement& p, const SpatElement& s)
{ {
thr(); thr();
return distance(Vec2d(p.first(X), p.first(Y)), return distance(Vec2d(p.first(X), p.first(Y)),
Vec2d(s.first(X), s.first(Y))) < d_base; Vec2d(s.first(X), s.first(Y))) < d_base;
}, 3, ground_head_indices); // max 3 heads to connect to one pillar }, 3); // max 3 heads to connect to one pillar
} }
// Step: Routing the ground connected pinheads, and interconnecting // Step: Routing the ground connected pinheads, and interconnecting
@ -1461,17 +1591,12 @@ public:
// a central pillar is limited to avoid bad weight distribution. // a central pillar is limited to avoid bad weight distribution.
void routing_to_ground() void routing_to_ground()
{ {
const double hbr = m_cfg.head_back_radius_mm;
const double pradius = m_cfg.head_back_radius_mm; const double pradius = m_cfg.head_back_radius_mm;
const double maxbridgelen = m_cfg.max_bridge_length_mm;
const double gndlvl = m_result.ground_level; const double gndlvl = m_result.ground_level;
ClusterEl cl_centroids; ClusterEl cl_centroids;
cl_centroids.reserve(m_pillar_clusters.size()); cl_centroids.reserve(m_pillar_clusters.size());
SpatIndex pheadindex; // spatial index for the junctions
for(auto& cl : m_pillar_clusters) { m_thr(); for(auto& cl : m_pillar_clusters) { m_thr();
// place all the centroid head positions into the index. We // place all the centroid head positions into the index. We
// will query for alternative pillar positions. If a sidehead // will query for alternative pillar positions. If a sidehead
@ -1503,7 +1628,7 @@ public:
Head& h = m_result.head(hid); Head& h = m_result.head(hid);
h.transform(); h.transform();
Vec3d p = h.junction_point(); p(Z) = gndlvl; Vec3d p = h.junction_point(); p(Z) = gndlvl;
pheadindex.insert(p, hid); m_pillar_index.insert(p, hid);
} }
// now we will go through the clusters ones again and connect the // now we will go through the clusters ones again and connect the
@ -1531,97 +1656,31 @@ public:
// TODO: don't consider the cluster centroid but calculate a // TODO: don't consider the cluster centroid but calculate a
// central position where the pillar can be placed. this way // central position where the pillar can be placed. this way
// the weight is distributed more effectively on the pillar. // the weight is distributed more effectively on the pillar.
auto search_nearest =
[this, maxbridgelen, gndlvl, pradius]
(SpatIndex& spindex, const Vec3d& jsh)
{
long nearest_id = -1;
const double max_len = maxbridgelen / 2;
while(nearest_id < 0 && !spindex.empty()) { m_thr();
// loop until a suitable head is not found
// if there is a pillar closer than the cluster center
// (this may happen as the clustering is not perfect)
// than we will bridge to this closer pillar
Vec3d qp(jsh(X), jsh(Y), gndlvl);
auto ne = spindex.nearest(qp, 1).front();
const Head& nearhead = m_result.head(ne.second);
Vec3d jh = nearhead.junction_point();
Vec3d jp = jsh;
double dist2d = distance(qp, ne.first);
// Bridge endpoint on the main pillar
Vec3d jn(jh(X), jh(Y), jp(Z) +
dist2d * std::tan(-m_cfg.bridge_slope));
if(jn(Z) > jh(Z)) {
// If the sidepoint cannot connect to the pillar
// from its head junction, just skip this pillar.
spindex.remove(ne);
continue;
}
double d = distance(jp, jn);
if(jn(Z) <= gndlvl + 2*m_cfg.head_width_mm || d > max_len)
break;
double chkd = bridge_mesh_intersect(jp, dirv(jp, jn),
pradius);
if(chkd >= d) nearest_id = ne.second;
spindex.remove(ne);
}
return nearest_id;
};
for(auto c : cl) { m_thr(); for(auto c : cl) { m_thr();
auto& sidehead = m_result.head(c); auto& sidehead = m_result.head(c);
sidehead.transform(); sidehead.transform();
Vec3d jsh = sidehead.junction_point(); Vec3d sidehead_jp = sidehead.junction_point();
SpatIndex spindex = pheadindex; long nearest_id = search_nearest(sidehead_jp);
long nearest_id = search_nearest(spindex, jsh);
// at this point we either have our pillar index or we have // at this point we either have our pillar index or we have
// to connect the sidehead to the ground // to connect the sidehead to the ground
if(nearest_id < 0) { if(nearest_id < 0) {
// Could not find a pillar, create one // Could not find a pillar, create one
Vec3d jp = jsh; jp(Z) = gndlvl; m_result.add_pillar(
m_result.add_pillar(unsigned(sidehead.id), jp, pradius). unsigned(sidehead.id),
add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); Vec3d{sidehead_jp(X), sidehead_jp(Y), gndlvl},
pradius).add_base(m_cfg.base_height_mm,
m_cfg.base_radius_mm);
// connects to ground, eligible for bridging // connects to ground, eligible for bridging
cl_centroids.emplace_back(c); cl_centroids.emplace_back(c);
} else { } else {
// Creating the bridge to the nearest pillar // Creating the bridge to the nearest pillar
auto nearest_uid = unsigned(nearest_id); auto nearest_uid = unsigned(nearest_id);
const Head& nearhead = m_result.head(nearest_uid); const Head& nearhead = m_result.head(nearest_uid);
Vec3d jp = jsh; connect_to_nearhead(sidehead, nearhead);
Vec3d jh = nearhead.junction_point();
double d = distance(Vec2d{jp(X), jp(Y)},
Vec2d{jh(X), jh(Y)});
Vec3d jn(jh(X), jh(Y), jp(Z) +
d * std::tan(-m_cfg.bridge_slope));
if(jn(Z) > jh(Z)) {
double hdiff = jn(Z) - jh(Z);
jp(Z) -= hdiff;
jn(Z) -= hdiff;
// pillar without base, doesn't connect to ground.
m_result.add_pillar(nearest_uid, jp, pradius);
}
if(jp(Z) < jsh(Z)) m_result.add_junction(jp, hbr);
if(jn(Z) >= jh(Z)) m_result.add_junction(jn, hbr);
m_result.add_bridge(jp, jn,
sidehead.request_pillar_radius(pradius));
} }
} }
} }
@ -1716,112 +1775,175 @@ public:
// nearby pillar that can hold the supported weight. // nearby pillar that can hold the supported weight.
void routing_to_model() void routing_to_model()
{ {
// We need to check if there is an easy way out to the bed surface.
// If it can be routed there with a bridge shorter than
// min_bridge_distance.
// First we want to index the available pillars. The best is to connect
// these points to the available pillars
auto routedown = [this](Head& head, const Vec3d& dir, double dist)
{
head.transform();
Vec3d hjp = head.junction_point();
Vec3d endp = hjp + dist * dir;
m_result.add_bridge(hjp, endp, head.r_back_mm);
m_result.add_junction(endp, head.r_back_mm);
auto groundp = endp;
groundp(Z) = m_result.ground_level;
m_result.add_pillar(endp, groundp, head.r_back_mm).add_base(
m_cfg.base_height_mm, m_cfg.base_radius_mm);
};
// TODO: connect these to the ground pillars if possible // TODO: connect these to the ground pillars if possible
for(auto idx : m_iheads_onmodel) { m_thr(); for(auto item : m_iheads_onmodel) { m_thr();
double gh = m_ptheights[idx]; unsigned idx = item.first;
double base_width = m_cfg.head_width_mm; EigenMesh3D::hit_result hit = item.second;
auto& head = m_result.head(idx); auto& head = m_result.head(idx);
Vec3d hjp = head.junction_point();
if(std::isinf(gh)) { // in this case the the pillar geometry // /////////////////////////////////////////////////////////////////
head.invalidate(); continue; // Search nearby pillar
// // is partially inside the model geometry. We cannot go // /////////////////////////////////////////////////////////////////
// // straight down but at an angle. We will search for a suitable
// // direction with the optimizer, optimizing for the smallest
// // difference between the bridge body hit distance and the
// // bridge center hit distance.
// // Get the spherical representation of the normal. its easier to long nearest_pillar_id = search_nearest(hjp);
// // work with. if(nearest_pillar_id >= 0) { // successful search
// double z = head.dir(Z); auto nearest_uid = unsigned(nearest_pillar_id);
// double r = 1.0; // for normalized vector const Head& nearhead = m_result.head(nearest_uid);
// double polar = std::acos(z / r); head.transform(); // accept the head
// double azimuth = std::atan2(head.dir(Y), head.dir(X)); connect_to_nearhead(head, nearhead);
// using libnest2d::opt::bound;
// using libnest2d::opt::initvals;
// using libnest2d::opt::SimplexOptimizer;
// using libnest2d::opt::StopCriteria;
// StopCriteria stc;
// stc.max_iterations = 100;
// stc.relative_score_difference = 1e-3;
// stc.stop_score = head.r_pin_mm;
// SimplexOptimizer solver(stc);
// auto oresult = solver.optimize_max(
// [&head, &mesh](double plr, double azm)
// {
// auto n = Vec3d(std::cos(azm) * std::sin(plr),
// std::sin(azm) * std::sin(plr),
// std::cos(plr)).normalized();
// double score = bridge_mesh_intersect(head.junction_point(),
// n,
// head.r_back_mm,
// mesh);
// return score;
// },
// initvals(polar, azimuth), // let's start with what we have
// bound(3*PI/4, PI), // Must not exceed the slope limit
// bound(-PI, PI) // azimuth can be a full range search
// );
// t = oresult.score;
// polar = std::get<0>(oresult.optimum);
// azimuth = std::get<1>(oresult.optimum);
// nn = Vec3d(std::cos(azimuth) * std::sin(polar),
// std::sin(azimuth) * std::sin(polar),
// std::cos(polar)).normalized();
}
// In this case there is no room for the base pinhead.
if(gh < head.fullwidth()) {
double min_l =
2 * m_cfg.head_front_radius_mm +
2 * m_cfg.head_back_radius_mm -
m_cfg.head_penetration_mm;
base_width = gh - min_l;
}
if(base_width < 0) {
// There is really no space for even a reduced size head. We
// have to replace that with a small half sphere that touches
// the model surface. (TODO)
head.invalidate();
continue; continue;
} }
// /////////////////////////////////////////////////////////////////
// Try straight path
// /////////////////////////////////////////////////////////////////
// Cannot connect to nearby pillar. We will try to search for
// a route to the ground.
double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm);
double d = 0, tdown = 0;
Vec3d dirdown(0.0, 0.0, -1.0);
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
hjp + d*head.dir,
dirdown, head.r_back_mm))) {
d += head.r_back_mm;
}
if(std::isinf(tdown)) { // we heave found a route to the ground
routedown(head, head.dir, d); continue;
}
// /////////////////////////////////////////////////////////////////
// Optimize bridge direction
// /////////////////////////////////////////////////////////////////
// Straight path failed so we will try to search for a suitable
// direction out of the cavity.
// Get the spherical representation of the normal. its easier to
// work with.
double z = head.dir(Z);
double r = 1.0; // for normalized vector
double polar = std::acos(z / r);
double azimuth = std::atan2(head.dir(Y), head.dir(X));
using libnest2d::opt::bound;
using libnest2d::opt::initvals;
using libnest2d::opt::SimplexOptimizer;
using libnest2d::opt::StopCriteria;
StopCriteria stc;
stc.max_iterations = 100;
stc.relative_score_difference = 1e-3;
stc.stop_score = 1e6;
SimplexOptimizer solver(stc);
double r_back = head.r_back_mm;
auto oresult = solver.optimize_max(
[this, hjp, r_back](double plr, double azm)
{
Vec3d n = Vec3d(std::cos(azm) * std::sin(plr),
std::sin(azm) * std::sin(plr),
std::cos(plr)).normalized();
return bridge_mesh_intersect(hjp, n, r_back);
},
initvals(polar, azimuth), // let's start with what we have
bound(3*PI/4, PI), // Must not exceed the slope limit
bound(-PI, PI) // azimuth can be a full range search
);
d = 0; t = oresult.score;
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar),
std::sin(azimuth) * std::sin(polar),
std::cos(polar)).normalized();
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
hjp + d*bridgedir,
dirdown,
head.r_back_mm))) {
d += head.r_back_mm;
}
if(std::isinf(tdown)) { // we heave found a route to the ground
routedown(head, bridgedir, d); continue;
}
// /////////////////////////////////////////////////////////////////
// Route to model body
// /////////////////////////////////////////////////////////////////
double zangle = std::asin(hit.direction()(Z));
zangle = std::max(zangle, PI/4);
double h = std::sin(zangle) * head.fullwidth();
// The width of the tail head that we would like to have...
h = std::min(hit.distance() - head.r_back_mm, h);
if(h > 0) {
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
auto center_hit = m_mesh.query_ray_hit(hjp, dirdown);
double hitdiff = center_hit.distance() - hit.distance();
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
center_hit.position() : hit.position();
head.transform(); head.transform();
Vec3d headend = head.junction_point(); Pillar& pill = m_result.add_pillar(unsigned(head.id),
endp,
head.r_back_mm);
Head base_head(m_cfg.head_back_radius_mm, Vec3d taildir = endp - hitp;
m_cfg.head_front_radius_mm, double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
base_width, double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
Head tailhead(head.r_back_mm,
head.r_pin_mm,
w,
m_cfg.head_penetration_mm, m_cfg.head_penetration_mm,
{0.0, 0.0, 1.0}, taildir,
{headend(X), headend(Y), headend(Z) - gh}); hitp);
base_head.transform(); tailhead.transform();
pill.base = tailhead.mesh;
// Robustness check:
if(headend(Z) < base_head.junction_point()(Z)) {
// This should not happen it is against all assumptions
BOOST_LOG_TRIVIAL(warning)
<< "Ignoring invalid supports connecting to model body";
head.invalidate();
continue; continue;
} }
double hl = base_head.fullwidth() - head.r_back_mm; // We have failed to route this head.
BOOST_LOG_TRIVIAL(warning)
m_result.add_pillar(idx, << "Failed to route model facing support point."
Vec3d{headend(X), headend(Y), headend(Z) - gh + hl}, << " ID: " << idx;
m_cfg.head_back_radius_mm head.invalidate();
).base = base_head.mesh;
} }
} }
@ -1884,7 +2006,6 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
ROUTING_NONGROUND, ROUTING_NONGROUND,
HEADLESS, HEADLESS,
DONE, DONE,
HALT,
ABORT, ABORT,
NUM_STEPS NUM_STEPS
//... //...
@ -1913,16 +2034,12 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
// Done // Done
}, },
[] () {
// Halt
},
[] () { [] () {
// Abort // Abort
} }
}; };
Steps pc = BEGIN, pc_prev = BEGIN; Steps pc = BEGIN;
if(cfg.ground_facing_only) { if(cfg.ground_facing_only) {
program[ROUTING_NONGROUND] = []() { program[ROUTING_NONGROUND] = []() {
@ -1936,18 +2053,17 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
} }
// Let's define a simple automaton that will run our program. // Let's define a simple automaton that will run our program.
auto progress = [&ctl, &pc, &pc_prev] () { auto progress = [&ctl, &pc] () {
static const std::array<std::string, NUM_STEPS> stepstr { static const std::array<std::string, NUM_STEPS> stepstr {
"Starting", L("Starting"),
"Filtering", L("Filtering"),
"Generate pinheads", L("Generate pinheads"),
"Classification", L("Classification"),
"Routing to ground", L("Routing to ground"),
"Routing supports to model surface", L("Routing supports to model surface"),
"Processing small holes", L("Processing small holes"),
"Done", L("Done"),
"Halt", L("Abort")
"Abort"
}; };
static const std::array<unsigned, NUM_STEPS> stepstate { static const std::array<unsigned, NUM_STEPS> stepstate {
@ -1959,7 +2075,6 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
70, 70,
80, 80,
100, 100,
0,
0 0
}; };
@ -1973,7 +2088,6 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; case ROUTING_GROUND: pc = ROUTING_NONGROUND; break;
case ROUTING_NONGROUND: pc = HEADLESS; break; case ROUTING_NONGROUND: pc = HEADLESS; break;
case HEADLESS: pc = DONE; break; case HEADLESS: pc = DONE; break;
case HALT: pc = pc_prev; break;
case DONE: case DONE:
case ABORT: break; case ABORT: break;
default: ; default: ;
@ -1982,7 +2096,7 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
}; };
// Just here we run the computation... // Just here we run the computation...
while(pc < DONE || pc == HALT) { while(pc < DONE) {
progress(); progress();
program[pc](); program[pc]();
} }

View file

@ -169,6 +169,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
hit_result ret(*this); hit_result ret(*this);
ret.m_t = double(hit.t); ret.m_t = double(hit.t);
ret.m_dir = dir; ret.m_dir = dir;
ret.m_source = s;
if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id; if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id;
return ret; return ret;
@ -348,9 +349,9 @@ PointSet normals(const PointSet& points,
// Clustering a set of points by the given criteria // Clustering a set of points by the given criteria
ClusteredPoints cluster( ClusteredPoints cluster(
const sla::PointSet& points, const sla::PointSet& points, const std::vector<unsigned>& indices,
std::function<bool(const SpatElement&, const SpatElement&)> pred, std::function<bool(const SpatElement&, const SpatElement&)> pred,
unsigned max_points = 0, const std::vector<unsigned>& indices = {}) unsigned max_points = 0)
{ {
namespace bgi = boost::geometry::index; namespace bgi = boost::geometry::index;
@ -360,10 +361,6 @@ ClusteredPoints cluster(
Index3D sindex; Index3D sindex;
// Build the index // Build the index
if(indices.empty())
for(unsigned idx = 0; idx < points.rows(); idx++)
sindex.insert( std::make_pair(points.row(idx), idx));
else
for(unsigned idx : indices) for(unsigned idx : indices)
sindex.insert( std::make_pair(points.row(idx), idx)); sindex.insert( std::make_pair(points.row(idx), idx));