New ground route search implemented
Working gap avoidance for zero elevation
This commit is contained in:
parent
963e8e6585
commit
a20659fc2d
@ -72,9 +72,9 @@ public:
|
|||||||
double m_t = infty();
|
double m_t = infty();
|
||||||
int m_face_id = -1;
|
int m_face_id = -1;
|
||||||
const AABBMesh *m_mesh = nullptr;
|
const AABBMesh *m_mesh = nullptr;
|
||||||
Vec3d m_dir;
|
Vec3d m_dir = Vec3d::Zero();
|
||||||
Vec3d m_source;
|
Vec3d m_source = Vec3d::Zero();
|
||||||
Vec3d m_normal;
|
Vec3d m_normal = Vec3d::Zero();
|
||||||
friend class AABBMesh;
|
friend class AABBMesh;
|
||||||
|
|
||||||
// A valid object of this class can only be obtained from
|
// A valid object of this class can only be obtained from
|
||||||
|
@ -39,7 +39,7 @@ class BranchingTreeBuilder: public branchingtree::Builder {
|
|||||||
{
|
{
|
||||||
double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight;
|
double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight;
|
||||||
|
|
||||||
return std::min(m_sm.cfg.base_radius_mm, double(j.Rmin) + w);
|
return double(j.Rmin) + w;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> m_unroutable_pinheads;
|
std::vector<size_t> m_unroutable_pinheads;
|
||||||
@ -247,32 +247,18 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
|
|||||||
|
|
||||||
auto it = m_ground_mem.find(from.id);
|
auto it = m_ground_mem.find(from.id);
|
||||||
if (it == m_ground_mem.end()) {
|
if (it == m_ground_mem.end()) {
|
||||||
// std::optional<PointIndexEl> result = search_for_existing_pillar(from);
|
|
||||||
|
|
||||||
sla::Junction j{from.pos.cast<double>(), get_radius(from)};
|
sla::Junction j{from.pos.cast<double>(), get_radius(from)};
|
||||||
// if (!result) {
|
Vec3d init_dir = (to.pos - from.pos).cast<double>().normalized();
|
||||||
auto conn = optimize_ground_connection(
|
|
||||||
ex_tbb,
|
auto conn = deepsearch_ground_connection(ex_tbb, m_sm, j,
|
||||||
m_sm,
|
get_radius(to), init_dir);
|
||||||
j,
|
|
||||||
get_radius(to));
|
|
||||||
|
|
||||||
if (conn) {
|
if (conn) {
|
||||||
// Junction connlast = conn.path.back();
|
|
||||||
// branchingtree::Node n{connlast.pos.cast<float>(), float(connlast.r)};
|
|
||||||
// n.left = from.id;
|
|
||||||
m_pillars.emplace_back(from);
|
m_pillars.emplace_back(from);
|
||||||
// m_pillar_index.insert({n.pos, m_pillars.size() - 1});
|
|
||||||
m_gnd_connections[m_pillars.size() - 1] = conn;
|
m_gnd_connections[m_pillars.size() - 1] = conn;
|
||||||
|
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
// } else {
|
|
||||||
// const auto &resnode = m_pillars[result->second];
|
|
||||||
// m_builder.add_diffbridge(j.pos, resnode.pos.cast<double>(), j.r, get_radius(resnode));
|
|
||||||
// m_pillars[result->second].right = from.id;
|
|
||||||
// ret = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Remember that this node was tested if can go to ground, don't
|
// Remember that this node was tested if can go to ground, don't
|
||||||
// test it with any other destination ground point because
|
// test it with any other destination ground point because
|
||||||
|
@ -84,6 +84,12 @@ struct SupportTreeConfig
|
|||||||
2 * head_back_radius_mm - head_penetration_mm;
|
2 * head_back_radius_mm - head_penetration_mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double safety_distance() const { return safety_distance_mm; }
|
||||||
|
double safety_distance(double r) const
|
||||||
|
{
|
||||||
|
return std::min(safety_distance_mm, r * safety_distance_mm / head_back_radius_mm);
|
||||||
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Compile time configuration values (candidates for runtime)
|
// Compile time configuration values (candidates for runtime)
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
@ -91,7 +97,9 @@ struct SupportTreeConfig
|
|||||||
// The max Z angle for a normal at which it will get completely ignored.
|
// The max Z angle for a normal at which it will get completely ignored.
|
||||||
static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0;
|
static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0;
|
||||||
|
|
||||||
// The shortest distance of any support structure from the model surface
|
// The safety gap between a support structure and model body. For support
|
||||||
|
// struts smaller than head_back_radius, the safety distance is scaled
|
||||||
|
// down accordingly. see method safety_distance()
|
||||||
static const double constexpr safety_distance_mm = 0.5;
|
static const double constexpr safety_distance_mm = 0.5;
|
||||||
|
|
||||||
static const double constexpr max_solo_pillar_height_mm = 15.0;
|
static const double constexpr max_solo_pillar_height_mm = 15.0;
|
||||||
@ -117,11 +125,11 @@ struct SupportableMesh
|
|||||||
: emesh{trmsh}, pts{sp}, cfg{c}
|
: emesh{trmsh}, pts{sp}, cfg{c}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
explicit SupportableMesh(const AABBMesh &em,
|
// explicit SupportableMesh(const AABBMesh &em,
|
||||||
const SupportPoints &sp,
|
// const SupportPoints &sp,
|
||||||
const SupportTreeConfig &c)
|
// const SupportTreeConfig &c)
|
||||||
: emesh{em}, pts{sp}, cfg{c}
|
// : emesh{em}, pts{sp}, cfg{c}
|
||||||
{}
|
// {}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline double ground_level(const SupportableMesh &sm)
|
inline double ground_level(const SupportableMesh &sm)
|
||||||
|
@ -316,8 +316,7 @@ std::optional<DiffBridge> search_widening_path(Ex policy,
|
|||||||
|
|
||||||
auto d = spheric_to_dir(plr, azm).normalized();
|
auto d = spheric_to_dir(plr, azm).normalized();
|
||||||
|
|
||||||
auto sd = new_radius * sm.cfg.safety_distance_mm /
|
auto sd = sm.cfg.safety_distance(new_radius);
|
||||||
sm.cfg.head_back_radius_mm;
|
|
||||||
|
|
||||||
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
|
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
|
||||||
new_radius, t, sd)
|
new_radius, t, sd)
|
||||||
@ -427,7 +426,7 @@ bool optimize_pinhead_placement(Ex policy,
|
|||||||
// Reassemble the now corrected normal
|
// Reassemble the now corrected normal
|
||||||
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||||
|
|
||||||
double sd = m.cfg.safety_distance_mm;
|
double sd = m.cfg.safety_distance(back_r);
|
||||||
|
|
||||||
// check available distance
|
// check available distance
|
||||||
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd);
|
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd);
|
||||||
@ -471,10 +470,10 @@ bool optimize_pinhead_placement(Ex policy,
|
|||||||
head.r_back_mm = back_r;
|
head.r_back_mm = back_r;
|
||||||
|
|
||||||
ret = true;
|
ret = true;
|
||||||
} /*else if (back_r > m.cfg.head_fallback_radius_mm) {
|
} else if (back_r > m.cfg.head_fallback_radius_mm) {
|
||||||
head.r_back_mm = m.cfg.head_fallback_radius_mm;
|
head.r_back_mm = m.cfg.head_fallback_radius_mm;
|
||||||
ret = optimize_pinhead_placement(policy, m, head);
|
ret = optimize_pinhead_placement(policy, m, head);
|
||||||
}*/
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -527,116 +526,19 @@ GroundConnection find_pillar_route(Ex policy,
|
|||||||
double end_radius)
|
double end_radius)
|
||||||
{
|
{
|
||||||
GroundConnection ret;
|
GroundConnection ret;
|
||||||
|
|
||||||
ret.path.emplace_back(source);
|
ret.path.emplace_back(source);
|
||||||
|
|
||||||
Vec3d jp = source.pos, endp = jp, dir = sourcedir;
|
double sd = sm.cfg.safety_distance(source.r);
|
||||||
bool can_add_base = false/*, non_head = false*/;
|
auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)};
|
||||||
|
|
||||||
double gndlvl = 0.; // The Z level where pedestals should be
|
auto hit = beam_mesh_hit(policy,
|
||||||
double jp_gnd = 0.; // The lowest Z where a junction center can be
|
sm.emesh,
|
||||||
double gap_dist = 0.; // The gap distance between the model and the pad
|
Beam{{source.pos, source.r}, {gp, end_radius}},
|
||||||
double radius = source.r;
|
sd);
|
||||||
double sd = sm.cfg.safety_distance_mm;
|
|
||||||
|
|
||||||
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
|
|
||||||
|
|
||||||
auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
|
|
||||||
|
|
||||||
auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
|
|
||||||
(bool base_en = true)
|
|
||||||
{
|
|
||||||
can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
|
|
||||||
double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.;
|
|
||||||
gndlvl = ground_level(sm);
|
|
||||||
if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
|
|
||||||
jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
|
|
||||||
gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
|
|
||||||
};
|
|
||||||
|
|
||||||
eval_limits();
|
|
||||||
|
|
||||||
// We are dealing with a mini pillar that's potentially too long
|
|
||||||
if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
|
|
||||||
{
|
|
||||||
std::optional<DiffBridge> diffbr =
|
|
||||||
search_widening_path(policy, sm, jp, dir, radius,
|
|
||||||
sm.cfg.head_back_radius_mm);
|
|
||||||
|
|
||||||
if (diffbr && diffbr->endp.z() > jp_gnd) {
|
|
||||||
endp = diffbr->endp;
|
|
||||||
radius = diffbr->end_r;
|
|
||||||
ret.path.emplace_back(endp, radius);
|
|
||||||
dir = diffbr->get_dir();
|
|
||||||
eval_limits();
|
|
||||||
} else return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sm.cfg.object_elevation_mm < EPSILON)
|
|
||||||
{
|
|
||||||
// get a suitable direction for the corrector bridge. It is the
|
|
||||||
// original sourcedir's azimuth but the polar angle is saturated to the
|
|
||||||
// configured bridge slope.
|
|
||||||
auto [polar, azimuth] = dir_to_spheric(dir);
|
|
||||||
polar = PI - sm.cfg.bridge_slope;
|
|
||||||
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
|
|
||||||
double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
|
|
||||||
double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
|
|
||||||
t = 0.;
|
|
||||||
|
|
||||||
double zd = endp.z() - jp_gnd;
|
|
||||||
double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
|
||||||
tmax = std::min(tmax, tmax2);
|
|
||||||
|
|
||||||
Vec3d nexp = endp;
|
|
||||||
double dlast = 0.;
|
|
||||||
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
|
||||||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) &&
|
|
||||||
t < tmax)
|
|
||||||
{
|
|
||||||
t += radius;
|
|
||||||
nexp = endp + t * d;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dlast < gap_dist && can_add_base) {
|
|
||||||
nexp = endp;
|
|
||||||
t = 0.;
|
|
||||||
can_add_base = false;
|
|
||||||
eval_limits(can_add_base);
|
|
||||||
|
|
||||||
zd = endp.z() - jp_gnd;
|
|
||||||
tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
|
||||||
tmax = std::min(tmax, tmax2);
|
|
||||||
|
|
||||||
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
|
||||||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) {
|
|
||||||
t += radius;
|
|
||||||
nexp = endp + t * d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Could not find a path to avoid the pad gap
|
|
||||||
if (dlast < gap_dist) {
|
|
||||||
ret.path.clear();
|
|
||||||
return ret;
|
|
||||||
//return {false, pillar_id};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t > 0.) { // Need to make additional bridge
|
|
||||||
ret.path.emplace_back(nexp, radius);
|
|
||||||
endp = nexp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec3d gp = to_floor(endp);
|
|
||||||
auto hit = beam_mesh_hit(policy, sm.emesh,
|
|
||||||
Beam{{endp, radius}, {gp, end_radius}}, sd);
|
|
||||||
|
|
||||||
if (std::isinf(hit.distance())) {
|
if (std::isinf(hit.distance())) {
|
||||||
double base_radius = can_add_base ?
|
double base_radius = std::max(sm.cfg.base_radius_mm, end_radius);
|
||||||
std::max(sm.cfg.base_radius_mm, end_radius) : end_radius;
|
|
||||||
|
|
||||||
Vec3d gp = to_floor(endp);
|
|
||||||
ret.pillar_base =
|
ret.pillar_base =
|
||||||
Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius};
|
Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius};
|
||||||
}
|
}
|
||||||
@ -644,6 +546,139 @@ GroundConnection find_pillar_route(Ex policy,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//template<class Ex>
|
||||||
|
//GroundConnection find_pillar_route(Ex policy,
|
||||||
|
// const SupportableMesh &sm,
|
||||||
|
// const Junction &source,
|
||||||
|
// const Vec3d &sourcedir,
|
||||||
|
// double end_radius)
|
||||||
|
//{
|
||||||
|
// GroundConnection ret;
|
||||||
|
|
||||||
|
// ret.path.emplace_back(source);
|
||||||
|
|
||||||
|
// Vec3d jp = source.pos, endp = jp, dir = sourcedir;
|
||||||
|
// bool can_add_base = false/*, non_head = false*/;
|
||||||
|
|
||||||
|
// double gndlvl = 0.; // The Z level where pedestals should be
|
||||||
|
// double jp_gnd = 0.; // The lowest Z where a junction center can be
|
||||||
|
// double gap_dist = 0.; // The gap distance between the model and the pad
|
||||||
|
// double radius = source.r;
|
||||||
|
// double sd = sm.cfg.safety_distance(radius);
|
||||||
|
|
||||||
|
// double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
|
||||||
|
|
||||||
|
// auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
|
||||||
|
|
||||||
|
// auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd, end_radius]
|
||||||
|
// (bool base_en = true)
|
||||||
|
// {
|
||||||
|
// can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
|
||||||
|
// double base_r = can_add_base ? std::max(sm.cfg.base_radius_mm, end_radius) : end_radius;
|
||||||
|
// gndlvl = ground_level(sm);
|
||||||
|
// if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
|
||||||
|
// jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
|
||||||
|
// gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// eval_limits();
|
||||||
|
|
||||||
|
// // We are dealing with a mini pillar that's potentially too long
|
||||||
|
// if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
|
||||||
|
// {
|
||||||
|
// std::optional<DiffBridge> diffbr =
|
||||||
|
// search_widening_path(policy, sm, jp, dir, radius,
|
||||||
|
// sm.cfg.head_back_radius_mm);
|
||||||
|
|
||||||
|
// if (diffbr && diffbr->endp.z() > jp_gnd) {
|
||||||
|
// endp = diffbr->endp;
|
||||||
|
// radius = diffbr->end_r;
|
||||||
|
// ret.path.emplace_back(endp, radius);
|
||||||
|
// dir = diffbr->get_dir();
|
||||||
|
// eval_limits();
|
||||||
|
// } else return ret;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (sm.cfg.object_elevation_mm < EPSILON)
|
||||||
|
// {
|
||||||
|
// // get a suitable direction for the corrector bridge. It is the
|
||||||
|
// // original sourcedir's azimuth but the polar angle is saturated to the
|
||||||
|
// // configured bridge slope.
|
||||||
|
// auto [polar, azimuth] = dir_to_spheric(dir);
|
||||||
|
// polar = PI - sm.cfg.bridge_slope;
|
||||||
|
// Vec3d d = spheric_to_dir(polar, azimuth).normalized();
|
||||||
|
// double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
|
||||||
|
// double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
|
||||||
|
// t = 0.;
|
||||||
|
|
||||||
|
// double zd = endp.z() - jp_gnd;
|
||||||
|
// double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
||||||
|
// tmax = std::min(tmax, tmax2);
|
||||||
|
|
||||||
|
// Vec3d nexp = endp;
|
||||||
|
// double dlast = 0.;
|
||||||
|
// double rnext = radius;
|
||||||
|
// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
||||||
|
// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) &&
|
||||||
|
// t < tmax)
|
||||||
|
// {
|
||||||
|
// t += radius;
|
||||||
|
// nexp = endp + t * d;
|
||||||
|
// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm)));
|
||||||
|
// rnext = rnext + bridge_ratio * (end_radius - rnext);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // If could not find avoidance bridge for the pad gap, try again
|
||||||
|
// // without the pillar base
|
||||||
|
// if (dlast < gap_dist && can_add_base) {
|
||||||
|
// nexp = endp;
|
||||||
|
// t = 0.;
|
||||||
|
// rnext = radius;
|
||||||
|
// can_add_base = false;
|
||||||
|
// eval_limits(can_add_base);
|
||||||
|
|
||||||
|
// zd = endp.z() - jp_gnd;
|
||||||
|
// tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
||||||
|
// tmax = std::min(tmax, tmax2);
|
||||||
|
|
||||||
|
// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
||||||
|
// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && t < tmax) {
|
||||||
|
// t += radius;
|
||||||
|
// nexp = endp + t * d;
|
||||||
|
|
||||||
|
// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm)));
|
||||||
|
// rnext = rnext + bridge_ratio * (end_radius - rnext);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Could not find a path to avoid the pad gap
|
||||||
|
// if (dlast < gap_dist) {
|
||||||
|
// ret.path.clear();
|
||||||
|
// return ret;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (t > 0.) { // Need to make additional bridge
|
||||||
|
// ret.path.emplace_back(nexp, rnext);
|
||||||
|
// endp = nexp;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Vec3d gp = to_floor(endp);
|
||||||
|
// auto hit = beam_mesh_hit(policy, sm.emesh,
|
||||||
|
// Beam{{endp, radius}, {gp, end_radius}}, sd);
|
||||||
|
|
||||||
|
// if (std::isinf(hit.distance())) {
|
||||||
|
// double base_radius = can_add_base ?
|
||||||
|
// std::max(sm.cfg.base_radius_mm, end_radius) : end_radius;
|
||||||
|
|
||||||
|
// Vec3d gp = to_floor(endp);
|
||||||
|
// ret.pillar_base =
|
||||||
|
// Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius};
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return ret;
|
||||||
|
//}
|
||||||
|
|
||||||
inline long build_ground_connection(SupportTreeBuilder &builder,
|
inline long build_ground_connection(SupportTreeBuilder &builder,
|
||||||
const SupportableMesh &sm,
|
const SupportableMesh &sm,
|
||||||
const GroundConnection &conn)
|
const GroundConnection &conn)
|
||||||
@ -689,7 +724,7 @@ GroundConnection find_ground_connection(
|
|||||||
{
|
{
|
||||||
auto hjp = j.pos;
|
auto hjp = j.pos;
|
||||||
double r = j.r;
|
double r = j.r;
|
||||||
auto sd = sm.cfg.safety_distance_mm;
|
auto sd = sm.cfg.safety_distance(r);
|
||||||
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
|
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
|
||||||
|
|
||||||
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
|
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
|
||||||
@ -700,7 +735,7 @@ GroundConnection find_ground_connection(
|
|||||||
|
|
||||||
while (!gnd_route && d < t) {
|
while (!gnd_route && d < t) {
|
||||||
Vec3d endp = hjp + d * dir;
|
Vec3d endp = hjp + d * dir;
|
||||||
double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level()));
|
double bridge_ratio = d / (d + (endp.z() - ground_level(sm)));
|
||||||
double pill_r = r + bridge_ratio * (end_r - r);
|
double pill_r = r + bridge_ratio * (end_r - r);
|
||||||
gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r);
|
gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r);
|
||||||
|
|
||||||
@ -745,7 +780,7 @@ GroundConnection optimize_ground_connection(
|
|||||||
Optimizer<opt::AlgNLoptMLSL> solver(get_criteria(sm.cfg).stop_score(1e6));
|
Optimizer<opt::AlgNLoptMLSL> solver(get_criteria(sm.cfg).stop_score(1e6));
|
||||||
solver.seed(0); // we want deterministic behavior
|
solver.seed(0); // we want deterministic behavior
|
||||||
|
|
||||||
auto sd = sm.cfg.safety_distance_mm;
|
auto sd = sm.cfg.safety_distance(j.r);
|
||||||
auto oresult = solver.to_max().optimize(
|
auto oresult = solver.to_max().optimize(
|
||||||
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
|
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
|
||||||
auto &[plr, azm] = input;
|
auto &[plr, azm] = input;
|
||||||
@ -762,6 +797,120 @@ GroundConnection optimize_ground_connection(
|
|||||||
return find_ground_connection(policy, sm, j, bridgedir, end_radius);
|
return find_ground_connection(policy, sm, j, bridgedir, end_radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class Ex>
|
||||||
|
GroundConnection deepsearch_ground_connection(
|
||||||
|
Ex policy,
|
||||||
|
const SupportableMesh &sm,
|
||||||
|
const Junction &j,
|
||||||
|
double end_radius,
|
||||||
|
const Vec3d &init_dir = DOWN)
|
||||||
|
{
|
||||||
|
// Score is the total lenght of the route. Feasible routes will have
|
||||||
|
// infinite length (rays not colliding with model), thus the stop score
|
||||||
|
// should be a reasonably big number.
|
||||||
|
constexpr double StopScore = 1e6;
|
||||||
|
|
||||||
|
const auto sd = sm.cfg.safety_distance(j.r);
|
||||||
|
const auto gndlvl = ground_level(sm);
|
||||||
|
const double widening = end_radius - j.r;
|
||||||
|
const double base_r = std::max(sm.cfg.base_radius_mm, end_radius);
|
||||||
|
const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r;
|
||||||
|
|
||||||
|
auto criteria = get_criteria(sm.cfg).stop_score(StopScore);
|
||||||
|
|
||||||
|
Optimizer<opt::AlgNLoptMLSL> solver(criteria);
|
||||||
|
solver.seed(0); // enforce deterministic behavior
|
||||||
|
|
||||||
|
auto optfn = [&](const opt::Input<3> &input) {
|
||||||
|
double ret = NaNd;
|
||||||
|
|
||||||
|
// solver suggests polar, azimuth and bridge length values:
|
||||||
|
auto &[plr, azm, bridge_len] = input;
|
||||||
|
|
||||||
|
Vec3d n = spheric_to_dir(plr, azm);
|
||||||
|
Vec3d bridge_end = j.pos + bridge_len * n;
|
||||||
|
|
||||||
|
double full_len = bridge_len + bridge_end.z() - gndlvl;
|
||||||
|
double bridge_r = j.r + widening * bridge_len / full_len;
|
||||||
|
double brhit_dist = 0.;
|
||||||
|
|
||||||
|
if (bridge_len > EPSILON) {
|
||||||
|
// beam_mesh_hit with a zero lenght bridge is invalid
|
||||||
|
|
||||||
|
Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}};
|
||||||
|
auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd);
|
||||||
|
brhit_dist = brhit.distance();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brhit_dist < bridge_len) {
|
||||||
|
ret = brhit_dist;
|
||||||
|
} else {
|
||||||
|
// check if pillar can be placed below
|
||||||
|
auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl};
|
||||||
|
|
||||||
|
Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}};
|
||||||
|
auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd);
|
||||||
|
|
||||||
|
if (std::isinf(gndhit.distance())) {
|
||||||
|
// Ground route is free with this bridge
|
||||||
|
|
||||||
|
if (sm.cfg.object_elevation_mm < EPSILON) {
|
||||||
|
// Dealing with zero elevation mode, to not route pillars
|
||||||
|
// into the gap between the optional pad and the model
|
||||||
|
double gap = std::sqrt(sm.emesh.squared_distance(gp));
|
||||||
|
if (gap < zelev_gap)
|
||||||
|
ret = full_len - zelev_gap + gap;
|
||||||
|
else // success
|
||||||
|
ret = StopScore;
|
||||||
|
} else {
|
||||||
|
// No zero elevation, return success
|
||||||
|
ret = StopScore;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ground route is not free
|
||||||
|
ret = bridge_len + gndhit.distance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto [plr_init, azm_init] = dir_to_spheric(init_dir);
|
||||||
|
|
||||||
|
// Saturate the polar angle to max tilt defined in config
|
||||||
|
plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope);
|
||||||
|
|
||||||
|
auto oresult = solver.to_max().optimize(
|
||||||
|
optfn,
|
||||||
|
initvals({plr_init, azm_init, 0.}), // start with a zero bridge
|
||||||
|
bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle
|
||||||
|
{-PI, PI}, // bounds for azimuth
|
||||||
|
{0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length
|
||||||
|
);
|
||||||
|
|
||||||
|
GroundConnection conn;
|
||||||
|
|
||||||
|
if (oresult.score >= StopScore) {
|
||||||
|
// search was successful, extract and apply the result
|
||||||
|
auto &[plr, azm, bridge_len] = oresult.optimum;
|
||||||
|
|
||||||
|
Vec3d n = spheric_to_dir(plr, azm);
|
||||||
|
Vec3d bridge_end = j.pos + bridge_len * n;
|
||||||
|
|
||||||
|
double full_len = bridge_len + bridge_end.z() - gndlvl;
|
||||||
|
double bridge_r = j.r + widening * bridge_len / full_len;
|
||||||
|
Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl};
|
||||||
|
|
||||||
|
conn.path.emplace_back(j);
|
||||||
|
conn.path.emplace_back(Junction{bridge_end, bridge_r});
|
||||||
|
|
||||||
|
conn.pillar_base =
|
||||||
|
Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius};
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
template<class Ex>
|
template<class Ex>
|
||||||
bool optimize_anchor_placement(Ex policy,
|
bool optimize_anchor_placement(Ex policy,
|
||||||
const SupportableMesh &sm,
|
const SupportableMesh &sm,
|
||||||
@ -779,8 +928,7 @@ bool optimize_anchor_placement(Ex policy,
|
|||||||
double lmax = std::min(sm.cfg.head_width_mm,
|
double lmax = std::min(sm.cfg.head_width_mm,
|
||||||
distance(from.pos, anchor.pos) - 2 * from.r);
|
distance(from.pos, anchor.pos) - 2 * from.r);
|
||||||
|
|
||||||
double sd = anchor.r_back_mm * sm.cfg.safety_distance_mm /
|
double sd = sm.cfg.safety_distance(anchor.r_back_mm);
|
||||||
sm.cfg.head_back_radius_mm;
|
|
||||||
|
|
||||||
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg)
|
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg)
|
||||||
.stop_score(anchor.fullwidth())
|
.stop_score(anchor.fullwidth())
|
||||||
|
@ -28,7 +28,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]")
|
|||||||
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
||||||
|
|
||||||
sla::GroundConnection conn =
|
sla::GroundConnection conn =
|
||||||
sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
|
||||||
@ -63,6 +63,66 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]")
|
|||||||
REQUIRE(pR + FromRadius > CylRadius);
|
REQUIRE(pR + FromRadius > CylRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Avoid disk below junction - Zero elevation", "[suptreeutils]")
|
||||||
|
{
|
||||||
|
// In this test there will be a disk mesh with some radius, centered at
|
||||||
|
// (0, 0, 0) and above the disk, a junction from which the support pillar
|
||||||
|
// should be routed. The algorithm needs to find an avoidance route.
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
constexpr double FromRadius = .5;
|
||||||
|
constexpr double EndRadius = 1.;
|
||||||
|
constexpr double CylRadius = 4.;
|
||||||
|
constexpr double CylHeight = 1.;
|
||||||
|
|
||||||
|
sla::SupportTreeConfig cfg;
|
||||||
|
cfg.object_elevation_mm = 0.;
|
||||||
|
|
||||||
|
indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight);
|
||||||
|
|
||||||
|
// 2.5 * CyRadius height should be enough to be able to insert a bridge
|
||||||
|
// with 45 degree tilt above the disk.
|
||||||
|
sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius};
|
||||||
|
|
||||||
|
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
||||||
|
|
||||||
|
sla::GroundConnection conn =
|
||||||
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
sla::SupportTreeBuilder builder;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
builder.add_junction(j);
|
||||||
|
|
||||||
|
sla::build_ground_connection(builder, sm, conn);
|
||||||
|
|
||||||
|
its_merge(disk, builder.merged_mesh());
|
||||||
|
|
||||||
|
its_write_stl_ascii("output_disk_ze.stl", "disk", disk);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
REQUIRE(bool(conn));
|
||||||
|
|
||||||
|
// The route should include the source and one avoidance junction.
|
||||||
|
REQUIRE(conn.path.size() == 2);
|
||||||
|
|
||||||
|
// Check if the radius increases with each node
|
||||||
|
REQUIRE(conn.path.front().r < conn.path.back().r);
|
||||||
|
REQUIRE(conn.path.back().r < conn.pillar_base->r_top);
|
||||||
|
|
||||||
|
// The end radius and the pillar base's upper radius should match
|
||||||
|
REQUIRE(conn.pillar_base->r_top == Approx(EndRadius));
|
||||||
|
|
||||||
|
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||||
|
// edge.
|
||||||
|
auto p = conn.path.back().pos;
|
||||||
|
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
||||||
|
REQUIRE(pR + FromRadius > CylRadius);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]")
|
TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]")
|
||||||
{
|
{
|
||||||
// In this test there will be a disk mesh with some radius, centered at
|
// In this test there will be a disk mesh with some radius, centered at
|
||||||
@ -91,7 +151,7 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]"
|
|||||||
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
||||||
|
|
||||||
sla::GroundConnection conn =
|
sla::GroundConnection conn =
|
||||||
sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ void test_supports(const std::string &obj_filename,
|
|||||||
|
|
||||||
// Create the special index-triangle mesh with spatial indexing which
|
// Create the special index-triangle mesh with spatial indexing which
|
||||||
// is the input of the support point and support mesh generators
|
// is the input of the support point and support mesh generators
|
||||||
AABBMesh emesh{mesh};
|
sla::SupportableMesh sm{mesh.its, {}, supportcfg};
|
||||||
|
|
||||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||||
if (hollowingcfg.enabled)
|
if (hollowingcfg.enabled)
|
||||||
@ -130,23 +130,23 @@ void test_supports(const std::string &obj_filename,
|
|||||||
// Create the support point generator
|
// Create the support point generator
|
||||||
sla::SupportPointGenerator::Config autogencfg;
|
sla::SupportPointGenerator::Config autogencfg;
|
||||||
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
|
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
|
||||||
sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}};
|
sla::SupportPointGenerator point_gen{sm.emesh, autogencfg, [] {}, [](int) {}};
|
||||||
|
|
||||||
point_gen.seed(0); // Make the test repeatable
|
point_gen.seed(0); // Make the test repeatable
|
||||||
point_gen.execute(out.model_slices, out.slicegrid);
|
point_gen.execute(out.model_slices, out.slicegrid);
|
||||||
|
|
||||||
// Get the calculated support points.
|
// Get the calculated support points.
|
||||||
std::vector<sla::SupportPoint> support_points = point_gen.output();
|
sm.pts = point_gen.output();
|
||||||
|
|
||||||
int validityflags = ASSUME_NO_REPAIR;
|
int validityflags = ASSUME_NO_REPAIR;
|
||||||
|
|
||||||
// If there is no elevation, support points shall be removed from the
|
// If there is no elevation, support points shall be removed from the
|
||||||
// bottom of the object.
|
// bottom of the object.
|
||||||
if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
|
if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
|
||||||
sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm);
|
sla::remove_bottom_points(sm.pts, zmin + supportcfg.base_height_mm);
|
||||||
} else {
|
} else {
|
||||||
// Should be support points at least on the bottom of the model
|
// Should be support points at least on the bottom of the model
|
||||||
REQUIRE_FALSE(support_points.empty());
|
REQUIRE_FALSE(sm.pts.empty());
|
||||||
|
|
||||||
// Also the support mesh should not be empty.
|
// Also the support mesh should not be empty.
|
||||||
validityflags |= ASSUME_NO_EMPTY;
|
validityflags |= ASSUME_NO_EMPTY;
|
||||||
@ -154,7 +154,6 @@ void test_supports(const std::string &obj_filename,
|
|||||||
|
|
||||||
// Generate the actual support tree
|
// Generate the actual support tree
|
||||||
sla::SupportTreeBuilder treebuilder;
|
sla::SupportTreeBuilder treebuilder;
|
||||||
sla::SupportableMesh sm{emesh, support_points, supportcfg};
|
|
||||||
|
|
||||||
switch (sm.cfg.tree_type) {
|
switch (sm.cfg.tree_type) {
|
||||||
case sla::SupportTreeType::Default: {
|
case sla::SupportTreeType::Default: {
|
||||||
|
Loading…
Reference in New Issue
Block a user