Bugfixes and new tests for pillar search

This commit is contained in:
tamasmeszaros 2022-11-15 17:00:16 +01:00
parent a20659fc2d
commit 0bbd50eaa0
6 changed files with 277 additions and 505 deletions

View file

@ -21,7 +21,7 @@ class Properties
public:
constexpr bool group_pillars() const noexcept { return true; }
constexpr bool group_pillars() const noexcept { return false; }
// Maximum slope for bridges of the tree
Properties &max_slope(double val) noexcept

View file

@ -26,30 +26,6 @@ using Slic3r::opt::AlgNLoptGenetic;
using Slic3r::Geometry::dir_to_spheric;
using Slic3r::Geometry::spheric_to_dir;
// Helper function for pillar interconnection where pairs of already connected
// pillars should be checked for not to be processed again. This can be done
// in constant time with a set of hash values uniquely representing a pair of
// integers. The order of numbers within the pair should not matter, it has
// the same unique hash. The hash value has to have twice as many bits as the
// arguments need. If the same integral type is used for args and return val,
// make sure the arguments use only the half of the type's bit depth.
template<class I, class DoubleI = IntegerOnly<I>>
IntegerOnly<DoubleI> pairhash(I a, I b)
{
using std::ceil; using std::log2; using std::max; using std::min;
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
I g = min(a, b), l = max(a, b);
// Assume the hash will fit into the output variable
assert((g ? (ceil(log2(g))) : 0) <= shift);
assert((l ? (ceil(log2(l))) : 0) <= shift);
return (DoubleI(g) << shift) + l;
}
// Give points on a 3D ring with given center, radius and orientation
// method based on:
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
@ -294,62 +270,6 @@ Hit pinhead_mesh_hit(Ex ex,
head.r_back_mm, head.width_mm, safety_d);
}
template<class Ex>
std::optional<DiffBridge> search_widening_path(Ex policy,
const SupportableMesh &sm,
const Vec3d &jp,
const Vec3d &dir,
double radius,
double new_radius)
{
double w = radius + 2 * sm.cfg.head_back_radius_mm;
double stopval = w + jp.z() - ground_level(sm);
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
auto [polar, azimuth] = dir_to_spheric(dir);
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
auto &[plr, azm, t] = input;
auto d = spheric_to_dir(plr, azm).normalized();
auto sd = sm.cfg.safety_distance(new_radius);
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
new_radius, t, sd)
.distance();
Beam beam{jp + t * d, d, new_radius};
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
if (ret > t && std::isinf(down))
ret += jp.z() - ground_level(sm);
return ret;
},
initvals({polar, azimuth, w}), // start with what we have
bounds({
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
{-PI, PI}, // azimuth can be a full search
{radius + sm.cfg.head_back_radius_mm,
fallback_ratio * sm.cfg.max_bridge_length_mm}
}));
if (oresult.score >= stopval) {
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
double t = std::get<2>(oresult.optimum);
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
}
return {};
}
inline double distance(const SupportPoint &a, const SupportPoint &b)
{
return (a.pos - b.pos).norm();
@ -518,167 +438,6 @@ struct GroundConnection {
operator bool() const { return pillar_base.has_value() && !path.empty(); }
};
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);
double sd = sm.cfg.safety_distance(source.r);
auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)};
auto hit = beam_mesh_hit(policy,
sm.emesh,
Beam{{source.pos, source.r}, {gp, end_radius}},
sd);
if (std::isinf(hit.distance())) {
double base_radius = std::max(sm.cfg.base_radius_mm, end_radius);
ret.pillar_base =
Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius};
}
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,
const SupportableMesh &sm,
const GroundConnection &conn)
@ -714,89 +473,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder,
return ret;
}
template<class Ex>
GroundConnection find_ground_connection(
Ex policy,
const SupportableMesh &sm,
const Junction &j,
const Vec3d &dir,
double end_r)
{
auto hjp = j.pos;
double r = j.r;
auto sd = sm.cfg.safety_distance(r);
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();
t = std::min(t, sm.cfg.max_bridge_length_mm);
double d = 0.;
GroundConnection gnd_route;
while (!gnd_route && d < t) {
Vec3d endp = hjp + d * dir;
double bridge_ratio = d / (d + (endp.z() - ground_level(sm)));
double pill_r = r + bridge_ratio * (end_r - r);
gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r);
d += r;
}
GroundConnection ret;
if (d > 0.)
ret.path.emplace_back(j);
for (auto &p : gnd_route.path)
ret.path.emplace_back(p);
// This will ultimately determine if the route is valid or not
// but the path junctions will be provided anyways, so invalid paths
// can be inspected
ret.pillar_base = gnd_route.pillar_base;
return ret;
}
template<class Ex>
GroundConnection optimize_ground_connection(
Ex policy,
const SupportableMesh &sm,
const Junction &j,
double end_radius,
const Vec3d &init_dir = DOWN)
{
double downdst = j.pos.z() - ground_level(sm);
auto res = find_ground_connection(policy, sm, j, init_dir, end_radius);
if (res)
return res;
// Optimize bridge direction:
// Straight path failed so we will try to search for a suitable
// direction out of the cavity.
auto [polar, azimuth] = dir_to_spheric(init_dir);
Optimizer<opt::AlgNLoptMLSL> solver(get_criteria(sm.cfg).stop_score(1e6));
solver.seed(0); // we want deterministic behavior
auto sd = sm.cfg.safety_distance(j.r);
auto oresult = solver.to_max().optimize(
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
auto &[plr, azm] = input;
Vec3d n = spheric_to_dir(plr, azm).normalized();
Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}};
return beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
},
initvals({polar, azimuth}), // let's start with what we have
bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} })
);
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
return find_ground_connection(policy, sm, j, bridgedir, end_radius);
}
template<class Ex>
GroundConnection deepsearch_ground_connection(
Ex policy,
@ -861,10 +537,10 @@ GroundConnection deepsearch_ground_connection(
if (gap < zelev_gap)
ret = full_len - zelev_gap + gap;
else // success
ret = StopScore;
ret = StopScore + EPSILON;
} else {
// No zero elevation, return success
ret = StopScore;
ret = StopScore + EPSILON;
}
} else {
// Ground route is not free
@ -902,7 +578,8 @@ GroundConnection deepsearch_ground_connection(
Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl};
conn.path.emplace_back(j);
conn.path.emplace_back(Junction{bridge_end, bridge_r});
if (bridge_len > EPSILON)
conn.path.emplace_back(Junction{bridge_end, bridge_r});
conn.pillar_base =
Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius};

View file

@ -8,6 +8,86 @@
namespace Slic3r { namespace sla {
// Helper function for pillar interconnection where pairs of already connected
// pillars should be checked for not to be processed again. This can be done
// in constant time with a set of hash values uniquely representing a pair of
// integers. The order of numbers within the pair should not matter, it has
// the same unique hash. The hash value has to have twice as many bits as the
// arguments need. If the same integral type is used for args and return val,
// make sure the arguments use only the half of the type's bit depth.
template<class I, class DoubleI = IntegerOnly<I>>
IntegerOnly<DoubleI> pairhash(I a, I b)
{
using std::ceil; using std::log2; using std::max; using std::min;
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
I g = min(a, b), l = max(a, b);
// Assume the hash will fit into the output variable
assert((g ? (ceil(log2(g))) : 0) <= shift);
assert((l ? (ceil(log2(l))) : 0) <= shift);
return (DoubleI(g) << shift) + l;
}
template<class Ex>
std::optional<DiffBridge> search_widening_path(Ex policy,
const SupportableMesh &sm,
const Vec3d &jp,
const Vec3d &dir,
double radius,
double new_radius)
{
double w = radius + 2 * sm.cfg.head_back_radius_mm;
double stopval = w + jp.z() - ground_level(sm);
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
auto [polar, azimuth] = dir_to_spheric(dir);
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
auto &[plr, azm, t] = input;
auto d = spheric_to_dir(plr, azm).normalized();
auto sd = sm.cfg.safety_distance(new_radius);
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
new_radius, t, sd)
.distance();
Beam beam{jp + t * d, d, new_radius};
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
if (ret > t && std::isinf(down))
ret += jp.z() - ground_level(sm);
return ret;
},
initvals({polar, azimuth, w}), // start with what we have
bounds({
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
{-PI, PI}, // azimuth can be a full search
{radius + sm.cfg.head_back_radius_mm,
fallback_ratio * sm.cfg.max_bridge_length_mm}
}));
if (oresult.score >= stopval) {
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
double t = std::get<2>(oresult.optimum);
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
}
return {};
}
// This is a proxy function for pillar creation which will mind the gap
// between the pad and the model bottom in zero elevation mode.
// 'pinhead_junctionpt' is the starting junction point which needs to be