Merge branch 'tm_hollowing' into lm_tm_hollowing
This commit is contained in:
commit
929e467df9
6 changed files with 337 additions and 249 deletions
|
@ -49,18 +49,64 @@ float SupportPointGenerator::distance_limit(float angle) const
|
||||||
return 1./(2.4*get_required_density(angle));
|
return 1./(2.4*get_required_density(angle));
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
SupportPointGenerator::SupportPointGenerator(const sla::EigenMesh3D & emesh,
|
class SupportPointGenerator::RandomGen {
|
||||||
const std::vector<ExPolygons> &slices,
|
std::mt19937 m_;
|
||||||
const std::vector<float> & heights,
|
public:
|
||||||
const Config & config,
|
|
||||||
std::function<void(void)> throw_on_cancel,
|
using result_type = long;
|
||||||
std::function<void(int)> statusfn)
|
|
||||||
|
RandomGen()
|
||||||
|
{
|
||||||
|
std::random_device rd;
|
||||||
|
m_.seed(rd());
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit RandomGen(long seedval) { seed(seedval); }
|
||||||
|
|
||||||
|
void seed(long s) { m_.seed(std::mt19937::result_type(s)); }
|
||||||
|
long operator() () { return long(m_()); }
|
||||||
|
long min() const { return m_.min(); }
|
||||||
|
long max() const { return m_.max(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
SupportPointGenerator::SupportPointGenerator(
|
||||||
|
const sla::EigenMesh3D &emesh,
|
||||||
|
const std::vector<ExPolygons> &slices,
|
||||||
|
const std::vector<float> & heights,
|
||||||
|
const Config & config,
|
||||||
|
std::function<void(void)> throw_on_cancel,
|
||||||
|
std::function<void(int)> statusfn)
|
||||||
|
: SupportPointGenerator(emesh, config, throw_on_cancel, statusfn)
|
||||||
|
{
|
||||||
|
execute(slices, heights);
|
||||||
|
}
|
||||||
|
|
||||||
|
SupportPointGenerator::SupportPointGenerator(
|
||||||
|
const EigenMesh3D &emesh,
|
||||||
|
const SupportPointGenerator::Config &config,
|
||||||
|
std::function<void ()> throw_on_cancel,
|
||||||
|
std::function<void (int)> statusfn)
|
||||||
: m_config(config)
|
: m_config(config)
|
||||||
, m_emesh(emesh)
|
, m_emesh(emesh)
|
||||||
, m_throw_on_cancel(throw_on_cancel)
|
, m_throw_on_cancel(throw_on_cancel)
|
||||||
, m_statusfn(statusfn)
|
, m_statusfn(statusfn)
|
||||||
{
|
{
|
||||||
process(slices, heights);
|
}
|
||||||
|
|
||||||
|
void SupportPointGenerator::execute(const std::vector<ExPolygons> &slices,
|
||||||
|
const std::vector<float> & heights)
|
||||||
|
{
|
||||||
|
RandomGen rng;
|
||||||
|
process(slices, heights, rng);
|
||||||
|
project_onto_mesh(m_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SupportPointGenerator::execute(const std::vector<ExPolygons> &slices,
|
||||||
|
const std::vector<float> & heights,
|
||||||
|
long seed)
|
||||||
|
{
|
||||||
|
RandomGen rng(seed);
|
||||||
|
process(slices, heights, rng);
|
||||||
project_onto_mesh(m_output);
|
project_onto_mesh(m_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +230,7 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
||||||
return layers;
|
return layers;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
|
void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights, RandomGen &rng)
|
||||||
{
|
{
|
||||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
std::vector<std::pair<ExPolygon, coord_t>> islands;
|
std::vector<std::pair<ExPolygon, coord_t>> islands;
|
||||||
|
@ -239,15 +285,15 @@ void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const
|
||||||
|
|
||||||
//float force_deficit = s.support_force_deficit(m_config.tear_pressure());
|
//float force_deficit = s.support_force_deficit(m_config.tear_pressure());
|
||||||
if (s.islands_below.empty()) { // completely new island - needs support no doubt
|
if (s.islands_below.empty()) { // completely new island - needs support no doubt
|
||||||
uniformly_cover({ *s.polygon }, s, point_grid, true);
|
uniformly_cover({ *s.polygon }, s, point_grid, rng, true);
|
||||||
} else if (! s.dangling_areas.empty()) {
|
} else if (! s.dangling_areas.empty()) {
|
||||||
// Let's see if there's anything that overlaps enough to need supports:
|
// Let's see if there's anything that overlaps enough to need supports:
|
||||||
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
|
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
|
||||||
//FIXME is it an island point or not? Vojtech thinks it is.
|
//FIXME is it an island point or not? Vojtech thinks it is.
|
||||||
uniformly_cover(s.dangling_areas, s, point_grid);
|
uniformly_cover(s.dangling_areas, s, point_grid, rng);
|
||||||
} else if (! s.overhangs_slopes.empty()) {
|
} else if (! s.overhangs_slopes.empty()) {
|
||||||
//FIXME add the support force deficit as a parameter, only cover until the defficiency is covered.
|
//FIXME add the support force deficit as a parameter, only cover until the defficiency is covered.
|
||||||
uniformly_cover(s.overhangs_slopes, s, point_grid);
|
uniformly_cover(s.overhangs_slopes, s, point_grid, rng);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +312,7 @@ void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng)
|
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, SupportPointGenerator::RandomGen &rng)
|
||||||
{
|
{
|
||||||
// Triangulate the polygon with holes into triplets of 3D points.
|
// Triangulate the polygon with holes into triplets of 3D points.
|
||||||
std::vector<Vec2f> triangles = Slic3r::triangulate_expolygon_2f(expoly);
|
std::vector<Vec2f> triangles = Slic3r::triangulate_expolygon_2f(expoly);
|
||||||
|
@ -306,7 +352,7 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
|
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, SupportPointGenerator::RandomGen &rng)
|
||||||
{
|
{
|
||||||
std::vector<Vec2f> out = sample_expolygon(expoly, samples_per_mm2, rng);
|
std::vector<Vec2f> out = sample_expolygon(expoly, samples_per_mm2, rng);
|
||||||
double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary;
|
double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary;
|
||||||
|
@ -319,7 +365,7 @@ std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygon &expoly, float
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygons &expolys, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
|
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygons &expolys, float samples_per_mm2, float samples_per_mm_boundary, SupportPointGenerator::RandomGen &rng)
|
||||||
{
|
{
|
||||||
std::vector<Vec2f> out;
|
std::vector<Vec2f> out;
|
||||||
for (const ExPolygon &expoly : expolys)
|
for (const ExPolygon &expoly : expolys)
|
||||||
|
@ -442,7 +488,7 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
|
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, RandomGen &rng, bool is_new_island, bool just_one)
|
||||||
{
|
{
|
||||||
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
||||||
|
|
||||||
|
@ -463,8 +509,7 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
|
||||||
float min_spacing = poisson_radius;
|
float min_spacing = poisson_radius;
|
||||||
|
|
||||||
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 rng(rd());
|
|
||||||
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
|
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
|
||||||
std::vector<Vec2f> poisson_samples;
|
std::vector<Vec2f> poisson_samples;
|
||||||
for (size_t iter = 0; iter < 4; ++ iter) {
|
for (size_t iter = 0; iter < 4; ++ iter) {
|
||||||
|
|
|
@ -29,7 +29,10 @@ public:
|
||||||
SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||||
|
|
||||||
const std::vector<SupportPoint>& output() { return m_output; }
|
SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||||
|
|
||||||
|
const std::vector<SupportPoint>& output() const { return m_output; }
|
||||||
|
std::vector<SupportPoint>& output() { return m_output; }
|
||||||
|
|
||||||
struct MyLayer;
|
struct MyLayer;
|
||||||
|
|
||||||
|
@ -76,7 +79,7 @@ public:
|
||||||
ExPolygons overhangs;
|
ExPolygons overhangs;
|
||||||
// Overhangs, where the surface must slope.
|
// Overhangs, where the surface must slope.
|
||||||
ExPolygons overhangs_slopes;
|
ExPolygons overhangs_slopes;
|
||||||
float overhangs_area;
|
float overhangs_area = 0.f;
|
||||||
|
|
||||||
bool overlaps(const Structure &rhs) const {
|
bool overlaps(const Structure &rhs) const {
|
||||||
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
|
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
|
||||||
|
@ -184,13 +187,21 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void execute(const std::vector<ExPolygons> &slices,
|
||||||
|
const std::vector<float> & heights);
|
||||||
|
|
||||||
|
void execute(const std::vector<ExPolygons> &slices,
|
||||||
|
const std::vector<float> & heights, long seed);
|
||||||
|
|
||||||
|
class RandomGen;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<SupportPoint> m_output;
|
std::vector<SupportPoint> m_output;
|
||||||
|
|
||||||
SupportPointGenerator::Config m_config;
|
SupportPointGenerator::Config m_config;
|
||||||
|
|
||||||
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
|
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights, RandomGen&);
|
||||||
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
|
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, RandomGen&, bool is_new_island = false, bool just_one = false);
|
||||||
void project_onto_mesh(std::vector<SupportPoint>& points) const;
|
void project_onto_mesh(std::vector<SupportPoint>& points) const;
|
||||||
|
|
||||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
|
|
|
@ -7,6 +7,14 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
|
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||||
|
|
||||||
|
using libnest2d::opt::initvals;
|
||||||
|
using libnest2d::opt::bound;
|
||||||
|
using libnest2d::opt::StopCriteria;
|
||||||
|
using libnest2d::opt::GeneticOptimizer;
|
||||||
|
using libnest2d::opt::SubplexOptimizer;
|
||||||
|
|
||||||
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
||||||
const SupportableMesh &sm)
|
const SupportableMesh &sm)
|
||||||
: m_cfg(sm.cfg)
|
: m_cfg(sm.cfg)
|
||||||
|
@ -560,9 +568,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
double radius,
|
double radius,
|
||||||
long head_id)
|
long head_id)
|
||||||
{
|
{
|
||||||
// People were killed for this number (seriously)
|
const double SLOPE = 1. / std::cos(m_cfg.bridge_slope);
|
||||||
static const double SQR2 = std::sqrt(2.0);
|
|
||||||
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
|
||||||
|
|
||||||
double gndlvl = m_builder.ground_level;
|
double gndlvl = m_builder.ground_level;
|
||||||
Vec3d endp = {jp(X), jp(Y), gndlvl};
|
Vec3d endp = {jp(X), jp(Y), gndlvl};
|
||||||
|
@ -573,38 +579,47 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
bool can_add_base = true;
|
bool can_add_base = true;
|
||||||
bool normal_mode = true;
|
bool normal_mode = true;
|
||||||
|
|
||||||
|
// If in zero elevation mode and the pillar is too close to the model body,
|
||||||
|
// the support pillar can not be placed in the gap between the model and
|
||||||
|
// the pad, and the pillar bases must not touch the model body either.
|
||||||
|
// To solve this, a corrector bridge is inserted between the starting point
|
||||||
|
// (jp) and the new pillar.
|
||||||
if (m_cfg.object_elevation_mm < EPSILON
|
if (m_cfg.object_elevation_mm < EPSILON
|
||||||
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
|
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
|
||||||
// Get the distance from the mesh. This can be later optimized
|
// Get the distance from the mesh. This can be later optimized
|
||||||
// to get the distance in 2D plane because we are dealing with
|
// to get the distance in 2D plane because we are dealing with
|
||||||
// the ground level only.
|
// the ground level only.
|
||||||
|
|
||||||
|
normal_mode = false;
|
||||||
|
|
||||||
|
// The min distance needed to move away from the model in XY plane.
|
||||||
|
double current_d = min_dist - dist;
|
||||||
|
double current_bride_d = SLOPE * current_d;
|
||||||
|
|
||||||
|
// 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(sourcedir);
|
||||||
|
polar = PI - m_cfg.bridge_slope;
|
||||||
|
auto dir = spheric_to_dir(polar, azimuth).normalized();
|
||||||
|
|
||||||
normal_mode = false;
|
|
||||||
double mind = min_dist - dist;
|
|
||||||
double azimuth = std::atan2(sourcedir(Y), sourcedir(X));
|
|
||||||
double sinpolar = std::sin(PI - m_cfg.bridge_slope);
|
|
||||||
double cospolar = std::cos(PI - m_cfg.bridge_slope);
|
|
||||||
double cosazm = std::cos(azimuth);
|
|
||||||
double sinazm = std::sin(azimuth);
|
|
||||||
|
|
||||||
auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar)
|
|
||||||
.normalized();
|
|
||||||
|
|
||||||
using namespace libnest2d::opt;
|
|
||||||
StopCriteria scr;
|
StopCriteria scr;
|
||||||
scr.stop_score = min_dist;
|
scr.stop_score = min_dist;
|
||||||
SubplexOptimizer solver(scr);
|
SubplexOptimizer solver(scr);
|
||||||
|
|
||||||
|
// Search for a distance along the corrector bridge to move the endpoint
|
||||||
|
// sufficiently away form the model body. The first few optimization
|
||||||
|
// cycles should succeed here.
|
||||||
auto result = solver.optimize_max(
|
auto result = solver.optimize_max(
|
||||||
[this, dir, jp, gndlvl](double mv) {
|
[this, dir, jp, gndlvl](double mv) {
|
||||||
Vec3d endpt = jp + SQR2 * mv * dir;
|
Vec3d endpt = jp + mv * dir;
|
||||||
endpt(Z) = gndlvl;
|
endpt(Z) = gndlvl;
|
||||||
return std::sqrt(m_mesh.squared_distance(endpt));
|
return std::sqrt(m_mesh.squared_distance(endpt));
|
||||||
},
|
},
|
||||||
initvals(mind), bound(0.0, 2 * min_dist));
|
initvals(current_bride_d),
|
||||||
|
bound(0.0, m_cfg.max_bridge_length_mm - current_bride_d));
|
||||||
|
|
||||||
mind = std::get<0>(result.optimum);
|
endp = jp + std::get<0>(result.optimum) * dir;
|
||||||
endp = jp + SQR2 * mind * dir;
|
|
||||||
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
|
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
|
||||||
can_add_base = result.score > min_dist;
|
can_add_base = result.score > min_dist;
|
||||||
|
|
||||||
|
@ -623,7 +638,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
else {
|
else {
|
||||||
// If the new endpoint is below ground, do not make a pillar
|
// If the new endpoint is below ground, do not make a pillar
|
||||||
if (endp(Z) < gndlvl)
|
if (endp(Z) < gndlvl)
|
||||||
endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off
|
endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off
|
||||||
else {
|
else {
|
||||||
|
|
||||||
auto hit = bridge_mesh_intersect(endp, DOWN, radius);
|
auto hit = bridge_mesh_intersect(endp, DOWN, radius);
|
||||||
|
@ -685,11 +700,6 @@ void SupportTreeBuildsteps::filter()
|
||||||
// not be enough space for the pinhead. Filtering is applied for
|
// not be enough space for the pinhead. Filtering is applied for
|
||||||
// these reasons.
|
// these reasons.
|
||||||
|
|
||||||
using libnest2d::opt::bound;
|
|
||||||
using libnest2d::opt::initvals;
|
|
||||||
using libnest2d::opt::GeneticOptimizer;
|
|
||||||
using libnest2d::opt::StopCriteria;
|
|
||||||
|
|
||||||
ccr::SpinningMutex mutex;
|
ccr::SpinningMutex mutex;
|
||||||
auto addfn = [&mutex](PtIndices &container, unsigned val) {
|
auto addfn = [&mutex](PtIndices &container, unsigned val) {
|
||||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
||||||
|
@ -708,10 +718,7 @@ void SupportTreeBuildsteps::filter()
|
||||||
// (Quaternion::FromTwoVectors) and apply the rotation to the
|
// (Quaternion::FromTwoVectors) and apply the rotation to the
|
||||||
// arrow head.
|
// arrow head.
|
||||||
|
|
||||||
double z = n(2);
|
auto [polar, azimuth] = dir_to_spheric(n);
|
||||||
double r = 1.0; // for normalized vector
|
|
||||||
double polar = std::acos(z / r);
|
|
||||||
double azimuth = std::atan2(n(1), n(0));
|
|
||||||
|
|
||||||
// skip if the tilt is not sane
|
// skip if the tilt is not sane
|
||||||
if(polar >= PI - m_cfg.normal_cutoff_angle) {
|
if(polar >= PI - m_cfg.normal_cutoff_angle) {
|
||||||
|
@ -729,9 +736,7 @@ void SupportTreeBuildsteps::filter()
|
||||||
double pin_r = double(m_support_pts[fidx].head_front_radius);
|
double pin_r = double(m_support_pts[fidx].head_front_radius);
|
||||||
|
|
||||||
// Reassemble the now corrected normal
|
// Reassemble the now corrected normal
|
||||||
auto nn = Vec3d(std::cos(azimuth) * std::sin(polar),
|
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||||
std::sin(azimuth) * std::sin(polar),
|
|
||||||
std::cos(polar)).normalized();
|
|
||||||
|
|
||||||
// check available distance
|
// check available distance
|
||||||
EigenMesh3D::hit_result t
|
EigenMesh3D::hit_result t
|
||||||
|
@ -757,9 +762,7 @@ void SupportTreeBuildsteps::filter()
|
||||||
auto oresult = solver.optimize_max(
|
auto oresult = solver.optimize_max(
|
||||||
[this, pin_r, w, hp](double plr, double azm)
|
[this, pin_r, w, hp](double plr, double azm)
|
||||||
{
|
{
|
||||||
auto dir = Vec3d(std::cos(azm) * std::sin(plr),
|
auto dir = spheric_to_dir(plr, azm).normalized();
|
||||||
std::sin(azm) * std::sin(plr),
|
|
||||||
std::cos(plr)).normalized();
|
|
||||||
|
|
||||||
double score = pinhead_mesh_intersect(
|
double score = pinhead_mesh_intersect(
|
||||||
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
||||||
|
@ -767,17 +770,14 @@ void SupportTreeBuildsteps::filter()
|
||||||
return score;
|
return score;
|
||||||
},
|
},
|
||||||
initvals(polar, azimuth), // start with what we have
|
initvals(polar, azimuth), // start with what we have
|
||||||
bound(3 * PI / 4,
|
bound(3 * PI / 4, PI), // Must not exceed the tilt limit
|
||||||
PI), // Must not exceed the tilt limit
|
|
||||||
bound(-PI, PI) // azimuth can be a full search
|
bound(-PI, PI) // azimuth can be a full search
|
||||||
);
|
);
|
||||||
|
|
||||||
if(oresult.score > w) {
|
if(oresult.score > w) {
|
||||||
polar = std::get<0>(oresult.optimum);
|
polar = std::get<0>(oresult.optimum);
|
||||||
azimuth = std::get<1>(oresult.optimum);
|
azimuth = std::get<1>(oresult.optimum);
|
||||||
nn = Vec3d(std::cos(azimuth) * std::sin(polar),
|
nn = spheric_to_dir(polar, azimuth).normalized();
|
||||||
std::sin(azimuth) * std::sin(polar),
|
|
||||||
std::cos(polar)).normalized();
|
|
||||||
t = EigenMesh3D::hit_result(oresult.score);
|
t = EigenMesh3D::hit_result(oresult.score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -837,16 +837,17 @@ void SupportTreeBuildsteps::classify()
|
||||||
m_thr();
|
m_thr();
|
||||||
|
|
||||||
auto& head = m_builder.head(i);
|
auto& head = m_builder.head(i);
|
||||||
Vec3d n(0, 0, -1);
|
|
||||||
double r = head.r_back_mm;
|
double r = head.r_back_mm;
|
||||||
Vec3d headjp = head.junction_point();
|
Vec3d headjp = head.junction_point();
|
||||||
|
|
||||||
// collision check
|
// collision check
|
||||||
auto hit = bridge_mesh_intersect(headjp, n, r);
|
auto hit = bridge_mesh_intersect(headjp, DOWN, r);
|
||||||
|
|
||||||
if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
|
if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
|
||||||
else if(m_cfg.ground_facing_only) head.invalidate();
|
else if(m_cfg.ground_facing_only) head.invalidate();
|
||||||
else m_iheads_onmodel.emplace_back(std::make_pair(i, hit));
|
else m_iheads_onmodel.emplace_back(i);
|
||||||
|
|
||||||
|
m_head_to_ground_scans[i] = hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to search for clusters of points that are far enough
|
// We want to search for clusters of points that are far enough
|
||||||
|
@ -893,13 +894,14 @@ void SupportTreeBuildsteps::routing_to_ground()
|
||||||
// get the current cluster centroid
|
// get the current cluster centroid
|
||||||
auto & thr = m_thr;
|
auto & thr = m_thr;
|
||||||
const auto &points = m_points;
|
const auto &points = m_points;
|
||||||
long lcid = cluster_centroid(
|
|
||||||
|
long lcid = cluster_centroid(
|
||||||
cl, [&points](size_t idx) { return points.row(long(idx)); },
|
cl, [&points](size_t idx) { return points.row(long(idx)); },
|
||||||
[thr](const Vec3d &p1, const Vec3d &p2) {
|
[thr](const Vec3d &p1, const Vec3d &p2) {
|
||||||
thr();
|
thr();
|
||||||
return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y)));
|
return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y)));
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(lcid >= 0);
|
assert(lcid >= 0);
|
||||||
unsigned hid = cl[size_t(lcid)]; // Head ID
|
unsigned hid = cl[size_t(lcid)]; // Head ID
|
||||||
|
|
||||||
|
@ -944,192 +946,138 @@ void SupportTreeBuildsteps::routing_to_ground()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
|
||||||
|
{
|
||||||
|
auto hjp = head.junction_point();
|
||||||
|
double r = head.r_back_mm;
|
||||||
|
double t = bridge_mesh_intersect(hjp, dir, head.r_back_mm);
|
||||||
|
double d = 0, tdown = 0;
|
||||||
|
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||||
|
|
||||||
|
while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r)))
|
||||||
|
d += r;
|
||||||
|
|
||||||
|
if(!std::isinf(tdown)) return false;
|
||||||
|
|
||||||
|
Vec3d endp = hjp + d * dir;
|
||||||
|
m_builder.add_bridge(head.id, endp);
|
||||||
|
m_builder.add_junction(endp, head.r_back_mm);
|
||||||
|
|
||||||
|
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SupportTreeBuildsteps::connect_to_ground(Head &head)
|
||||||
|
{
|
||||||
|
if (connect_to_ground(head, head.dir)) return true;
|
||||||
|
|
||||||
|
// 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(head.dir);
|
||||||
|
|
||||||
|
StopCriteria stc;
|
||||||
|
stc.max_iterations = m_cfg.optimizer_max_iterations;
|
||||||
|
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
|
||||||
|
stc.stop_score = 1e6;
|
||||||
|
GeneticOptimizer solver(stc);
|
||||||
|
solver.seed(0); // we want deterministic behavior
|
||||||
|
|
||||||
|
double r_back = head.r_back_mm;
|
||||||
|
Vec3d hjp = head.junction_point();
|
||||||
|
auto oresult = solver.optimize_max(
|
||||||
|
[this, hjp, r_back](double plr, double azm) {
|
||||||
|
Vec3d n = spheric_to_dir(plr, azm).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
|
||||||
|
);
|
||||||
|
|
||||||
|
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
||||||
|
return connect_to_ground(head, bridgedir);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
|
||||||
|
{
|
||||||
|
if (head.id <= ID_UNSET) return false;
|
||||||
|
|
||||||
|
auto it = m_head_to_ground_scans.find(unsigned(head.id));
|
||||||
|
if (it == m_head_to_ground_scans.end()) return false;
|
||||||
|
|
||||||
|
auto &hit = it->second;
|
||||||
|
Vec3d hjp = head.junction_point();
|
||||||
|
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.) return false;
|
||||||
|
|
||||||
|
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
|
||||||
|
auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
|
||||||
|
|
||||||
|
double hitdiff = center_hit.distance() - hit.distance();
|
||||||
|
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
|
||||||
|
center_hit.position() : hit.position();
|
||||||
|
|
||||||
|
head.transform();
|
||||||
|
|
||||||
|
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
|
||||||
|
Pillar &pill = m_builder.pillar(pillar_id);
|
||||||
|
|
||||||
|
Vec3d taildir = endp - hitp;
|
||||||
|
double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
|
||||||
|
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
|
||||||
|
|
||||||
|
if (w < 0.) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
|
||||||
|
w = 0.;
|
||||||
|
}
|
||||||
|
|
||||||
|
Head tailhead(head.r_back_mm, head.r_pin_mm, w,
|
||||||
|
m_cfg.head_penetration_mm, taildir, hitp);
|
||||||
|
|
||||||
|
tailhead.transform();
|
||||||
|
pill.base = tailhead.mesh;
|
||||||
|
|
||||||
|
m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void SupportTreeBuildsteps::routing_to_model()
|
void SupportTreeBuildsteps::routing_to_model()
|
||||||
{
|
{
|
||||||
// We need to check if there is an easy way out to the bed surface.
|
// 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
|
// If it can be routed there with a bridge shorter than
|
||||||
// min_bridge_distance.
|
// 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 endp = head.junction_point() + dist * dir;
|
|
||||||
m_builder.add_bridge(head.id, endp);
|
|
||||||
m_builder.add_junction(endp, head.r_back_mm);
|
|
||||||
|
|
||||||
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<unsigned> modelpillars;
|
|
||||||
ccr::SpinningMutex mutex;
|
|
||||||
|
|
||||||
auto onmodelfn =
|
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
|
||||||
[this, routedown, &modelpillars, &mutex]
|
[this] (const unsigned idx, size_t) {
|
||||||
(const std::pair<unsigned, EigenMesh3D::hit_result> &el, size_t)
|
|
||||||
{
|
|
||||||
m_thr();
|
m_thr();
|
||||||
unsigned idx = el.first;
|
|
||||||
EigenMesh3D::hit_result hit = el.second;
|
|
||||||
|
|
||||||
auto& head = m_builder.head(idx);
|
auto& head = m_builder.head(idx);
|
||||||
Vec3d hjp = head.junction_point();
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
// Search nearby pillar
|
// Search nearby pillar
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
if(search_pillar_and_connect(head)) { head.transform(); return; }
|
if(search_pillar_and_connect(head)) { head.transform(); return; }
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
// Try straight path
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Cannot connect to nearby pillar. We will try to search for
|
// Cannot connect to nearby pillar. We will try to search for
|
||||||
// a route to the ground.
|
// a route to the ground.
|
||||||
|
if(connect_to_ground(head)) { head.transform(); return; }
|
||||||
|
|
||||||
double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm);
|
// No route to the ground, so connect to the model body as a last resort
|
||||||
double d = 0, tdown = 0;
|
if (connect_to_model_body(head)) { return; }
|
||||||
Vec3d dirdown(0.0, 0.0, -1.0);
|
|
||||||
|
|
||||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
|
||||||
|
|
||||||
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); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
// 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::GeneticOptimizer;
|
|
||||||
using libnest2d::opt::StopCriteria;
|
|
||||||
|
|
||||||
StopCriteria stc;
|
|
||||||
stc.max_iterations = m_cfg.optimizer_max_iterations;
|
|
||||||
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
|
|
||||||
stc.stop_score = 1e6;
|
|
||||||
GeneticOptimizer solver(stc);
|
|
||||||
solver.seed(0); // we want deterministic behavior
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
|
||||||
|
|
||||||
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); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
|
|
||||||
Pillar &pill = m_builder.pillar(pillar_id);
|
|
||||||
|
|
||||||
Vec3d taildir = endp - hitp;
|
|
||||||
double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
|
|
||||||
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
|
|
||||||
|
|
||||||
if (w < 0.) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
|
|
||||||
w = 0.;
|
|
||||||
}
|
|
||||||
|
|
||||||
Head tailhead(head.r_back_mm,
|
|
||||||
head.r_pin_mm,
|
|
||||||
w,
|
|
||||||
m_cfg.head_penetration_mm,
|
|
||||||
taildir,
|
|
||||||
hitp);
|
|
||||||
|
|
||||||
tailhead.transform();
|
|
||||||
pill.base = tailhead.mesh;
|
|
||||||
|
|
||||||
// Experimental: add the pillar to the index for cascading
|
|
||||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
|
||||||
modelpillars.emplace_back(unsigned(pill.id));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have failed to route this head.
|
// We have failed to route this head.
|
||||||
BOOST_LOG_TRIVIAL(warning)
|
BOOST_LOG_TRIVIAL(warning)
|
||||||
<< "Failed to route model facing support point."
|
<< "Failed to route model facing support point. ID: " << idx;
|
||||||
<< " ID: " << idx;
|
|
||||||
head.invalidate();
|
head.invalidate();
|
||||||
};
|
});
|
||||||
|
|
||||||
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), onmodelfn);
|
|
||||||
|
|
||||||
for(auto pillid : modelpillars) {
|
|
||||||
auto& pillar = m_builder.pillar(pillid);
|
|
||||||
m_pillar_index.insert(pillar.endpoint(), pillid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SupportTreeBuildsteps::interconnect_pillars()
|
void SupportTreeBuildsteps::interconnect_pillars()
|
||||||
|
@ -1280,7 +1228,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
||||||
spts[n] = s;
|
spts[n] = s;
|
||||||
|
|
||||||
// Check the path vertically down
|
// Check the path vertically down
|
||||||
auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r);
|
Vec3d check_from = s + Vec3d{0., 0., pillar().r};
|
||||||
|
auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r);
|
||||||
Vec3d gndsp{s(X), s(Y), gnd};
|
Vec3d gndsp{s(X), s(Y), gnd};
|
||||||
|
|
||||||
// If the path is clear, check for pillar base collisions
|
// If the path is clear, check for pillar base collisions
|
||||||
|
@ -1360,12 +1309,11 @@ void SupportTreeBuildsteps::routing_headless()
|
||||||
Vec3d n = m_support_nmls.row(i); // mesh outward normal
|
Vec3d n = m_support_nmls.row(i); // mesh outward normal
|
||||||
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
|
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
|
||||||
|
|
||||||
Vec3d dir = {0, 0, -1};
|
|
||||||
Vec3d sj = sp + R * n; // stick start point
|
Vec3d sj = sp + R * n; // stick start point
|
||||||
|
|
||||||
// This is only for checking
|
// This is only for checking
|
||||||
double idist = bridge_mesh_intersect(sph, dir, R, true);
|
double idist = bridge_mesh_intersect(sph, DOWN, R, true);
|
||||||
double realdist = ray_mesh_intersect(sj, dir);
|
double realdist = ray_mesh_intersect(sj, DOWN);
|
||||||
double dist = realdist;
|
double dist = realdist;
|
||||||
|
|
||||||
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
|
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
|
||||||
|
@ -1378,7 +1326,7 @@ void SupportTreeBuildsteps::routing_headless()
|
||||||
}
|
}
|
||||||
|
|
||||||
bool use_endball = !std::isinf(realdist);
|
bool use_endball = !std::isinf(realdist);
|
||||||
Vec3d ej = sj + (dist + HWIDTH_MM) * dir;
|
Vec3d ej = sj + (dist + HWIDTH_MM) * DOWN ;
|
||||||
m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
|
m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,32 @@ inline Vec2d to_vec2(const Vec3d& v3) {
|
||||||
return {v3(X), v3(Y)};
|
return {v3(X), v3(Y)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
|
||||||
|
{
|
||||||
|
double z = n.z();
|
||||||
|
double r = norm;
|
||||||
|
double polar = std::acos(z / r);
|
||||||
|
double azimuth = std::atan2(n(1), n(0));
|
||||||
|
return {polar, azimuth};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec3d spheric_to_dir(double polar, double azimuth)
|
||||||
|
{
|
||||||
|
return {std::cos(azimuth) * std::sin(polar),
|
||||||
|
std::sin(azimuth) * std::sin(polar), std::cos(polar)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec3d spheric_to_dir(const std::tuple<double, double> &v)
|
||||||
|
{
|
||||||
|
auto [plr, azm] = v;
|
||||||
|
return spheric_to_dir(plr, azm);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
|
||||||
|
{
|
||||||
|
return spheric_to_dir(v.first, v.second);
|
||||||
|
}
|
||||||
|
|
||||||
// This function returns the position of the centroid in the input 'clust'
|
// This function returns the position of the centroid in the input 'clust'
|
||||||
// vector of point indices.
|
// vector of point indices.
|
||||||
template<class DistFn>
|
template<class DistFn>
|
||||||
|
@ -151,10 +177,10 @@ class SupportTreeBuildsteps {
|
||||||
using PtIndices = std::vector<unsigned>;
|
using PtIndices = std::vector<unsigned>;
|
||||||
|
|
||||||
PtIndices m_iheads; // support points with pinhead
|
PtIndices m_iheads; // support points with pinhead
|
||||||
|
PtIndices m_iheads_onmodel;
|
||||||
PtIndices m_iheadless; // headless support points
|
PtIndices m_iheadless; // headless support points
|
||||||
|
|
||||||
// supp. pts. connecting to model: point index and the ray hit data
|
std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
|
||||||
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;
|
||||||
|
@ -223,15 +249,29 @@ class SupportTreeBuildsteps {
|
||||||
|
|
||||||
// For connecting a head to a nearby pillar.
|
// For connecting a head to a nearby pillar.
|
||||||
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
|
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
|
||||||
|
|
||||||
|
// Find route for a head to the ground. Inserts additional bridge from the
|
||||||
|
// head to the pillar if cannot create pillar directly.
|
||||||
|
// The optional dir parameter is the direction of the bridge which is the
|
||||||
|
// direction of the pinhead if omitted.
|
||||||
|
bool connect_to_ground(Head& head, const Vec3d &dir);
|
||||||
|
inline bool connect_to_ground(Head& head);
|
||||||
|
|
||||||
|
bool connect_to_model_body(Head &head);
|
||||||
|
|
||||||
bool search_pillar_and_connect(const Head& head);
|
bool search_pillar_and_connect(const Head& head);
|
||||||
|
|
||||||
// This is a proxy function for pillar creation which will mind the gap
|
// This is a proxy function for pillar creation which will mind the gap
|
||||||
// between the pad and the model bottom in zero elevation mode.
|
// between the pad and the model bottom in zero elevation mode.
|
||||||
|
// jp is the starting junction point which needs to be routed down.
|
||||||
|
// sourcedir is the allowed direction of an optional bridge between the
|
||||||
|
// jp junction and the final pillar.
|
||||||
void create_ground_pillar(const Vec3d &jp,
|
void create_ground_pillar(const Vec3d &jp,
|
||||||
const Vec3d &sourcedir,
|
const Vec3d &sourcedir,
|
||||||
double radius,
|
double radius,
|
||||||
long head_id = ID_UNSET);
|
long head_id = ID_UNSET);
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,53 @@ TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") {
|
||||||
test_pairhash<unsigned, unsigned long>();
|
test_pairhash<unsigned, unsigned long>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Support point generator should be deterministic if seeded",
|
||||||
|
"[SLASupportGeneration], [SLAPointGen]") {
|
||||||
|
TriangleMesh mesh = load_model("A_upsidedown.obj");
|
||||||
|
|
||||||
|
sla::EigenMesh3D emesh{mesh};
|
||||||
|
|
||||||
|
sla::SupportConfig supportcfg;
|
||||||
|
sla::SupportPointGenerator::Config autogencfg;
|
||||||
|
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
|
||||||
|
sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}};
|
||||||
|
|
||||||
|
TriangleMeshSlicer slicer{&mesh};
|
||||||
|
|
||||||
|
auto bb = mesh.bounding_box();
|
||||||
|
double zmin = bb.min.z();
|
||||||
|
double zmax = bb.max.z();
|
||||||
|
double gnd = zmin - supportcfg.object_elevation_mm;
|
||||||
|
auto layer_h = 0.05f;
|
||||||
|
|
||||||
|
auto slicegrid = grid(float(gnd), float(zmax), layer_h);
|
||||||
|
std::vector<ExPolygons> slices;
|
||||||
|
slicer.slice(slicegrid, CLOSING_RADIUS, &slices, []{});
|
||||||
|
|
||||||
|
point_gen.execute(slices, slicegrid, 0);
|
||||||
|
|
||||||
|
auto get_chksum = [](const std::vector<sla::SupportPoint> &pts){
|
||||||
|
long long chksum = 0;
|
||||||
|
for (auto &pt : pts) {
|
||||||
|
auto p = scaled(pt.pos);
|
||||||
|
chksum += p.x() + p.y() + p.z();
|
||||||
|
}
|
||||||
|
|
||||||
|
return chksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
long long checksum = get_chksum(point_gen.output());
|
||||||
|
size_t ptnum = point_gen.output().size();
|
||||||
|
REQUIRE(point_gen.output().size() > 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
point_gen.output().clear();
|
||||||
|
point_gen.execute(slices, slicegrid, 0);
|
||||||
|
REQUIRE(point_gen.output().size() == ptnum);
|
||||||
|
REQUIRE(checksum == get_chksum(point_gen.output()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Flat pad geometry is valid", "[SLASupportGeneration]") {
|
TEST_CASE("Flat pad geometry is valid", "[SLASupportGeneration]") {
|
||||||
sla::PadConfig padcfg;
|
sla::PadConfig padcfg;
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,6 @@ void test_support_model_collision(const std::string &obj_filename,
|
||||||
// the supports will not touch the model body.
|
// the supports will not touch the model body.
|
||||||
supportcfg.head_penetration_mm = -0.15;
|
supportcfg.head_penetration_mm = -0.15;
|
||||||
|
|
||||||
// TODO: currently, the tailheads penetrating into the model body do not
|
|
||||||
// respect the penetration parameter properly. No issues were reported so
|
|
||||||
// far but we should definitely fix this.
|
|
||||||
supportcfg.ground_facing_only = true;
|
|
||||||
|
|
||||||
test_supports(obj_filename, supportcfg, hollowingcfg, drainholes, byproducts);
|
test_supports(obj_filename, supportcfg, hollowingcfg, drainholes, byproducts);
|
||||||
|
|
||||||
// Slice the support mesh given the slice grid of the model.
|
// Slice the support mesh given the slice grid of the model.
|
||||||
|
@ -115,8 +110,10 @@ 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, out.model_slices, out.slicegrid,
|
sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}};
|
||||||
autogencfg, [] {}, [](int) {}};
|
|
||||||
|
long seed = 0; // Make the test repeatable
|
||||||
|
point_gen.execute(out.model_slices, out.slicegrid, seed);
|
||||||
|
|
||||||
// Get the calculated support points.
|
// Get the calculated support points.
|
||||||
std::vector<sla::SupportPoint> support_points = point_gen.output();
|
std::vector<sla::SupportPoint> support_points = point_gen.output();
|
||||||
|
|
Loading…
Reference in a new issue