Bugfixes for support generator
* Fix support heads floating in air * Fix failing tests for the bridge mesh intersection * Fix failing assertions WIP refactoring support tree gen, as its a mess.
This commit is contained in:
parent
ed460a3e7e
commit
2ff04e6f68
@ -214,6 +214,56 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps)
|
|||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps)
|
||||||
|
{
|
||||||
|
if(baseheight <= 0) return {};
|
||||||
|
|
||||||
|
assert(steps >= 0);
|
||||||
|
auto last = int(steps - 1);
|
||||||
|
|
||||||
|
Contour3D base;
|
||||||
|
|
||||||
|
double a = 2*PI/steps;
|
||||||
|
double z = endpt(Z) + baseheight;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < steps; ++i) {
|
||||||
|
double phi = i*a;
|
||||||
|
double x = endpt(X) + radius * std::cos(phi);
|
||||||
|
double y = endpt(Y) + radius * std::sin(phi);
|
||||||
|
base.points.emplace_back(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < steps; ++i) {
|
||||||
|
double phi = i*a;
|
||||||
|
double x = endpt(X) + radius*std::cos(phi);
|
||||||
|
double y = endpt(Y) + radius*std::sin(phi);
|
||||||
|
base.points.emplace_back(x, y, z - baseheight);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ep = endpt; ep(Z) += baseheight;
|
||||||
|
base.points.emplace_back(endpt);
|
||||||
|
base.points.emplace_back(ep);
|
||||||
|
|
||||||
|
auto& indices = base.faces3;
|
||||||
|
auto hcenter = int(base.points.size() - 1);
|
||||||
|
auto lcenter = int(base.points.size() - 2);
|
||||||
|
auto offs = int(steps);
|
||||||
|
for(int i = 0; i < last; ++i) {
|
||||||
|
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||||
|
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||||
|
indices.emplace_back(i, i + 1, hcenter);
|
||||||
|
indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
indices.emplace_back(0, last, offs);
|
||||||
|
indices.emplace_back(last, offs + last, offs);
|
||||||
|
indices.emplace_back(hcenter, last, 0);
|
||||||
|
indices.emplace_back(offs, offs + last, lcenter);
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
Head::Head(double r_big_mm,
|
Head::Head(double r_big_mm,
|
||||||
double r_small_mm,
|
double r_small_mm,
|
||||||
double length_mm,
|
double length_mm,
|
||||||
@ -229,77 +279,76 @@ Head::Head(double r_big_mm,
|
|||||||
, width_mm(length_mm)
|
, width_mm(length_mm)
|
||||||
, penetration_mm(penetration)
|
, penetration_mm(penetration)
|
||||||
{
|
{
|
||||||
mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps);
|
// mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps);
|
||||||
|
|
||||||
// To simplify further processing, we translate the mesh so that the
|
// To simplify further processing, we translate the mesh so that the
|
||||||
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
|
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
|
||||||
for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm);
|
// for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm);
|
||||||
}
|
}
|
||||||
|
|
||||||
Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
|
//Pillar::Pillar(const Vec3d &endp, double h, double radius, size_t st):
|
||||||
r(radius), steps(st), endpt(endp), starts_from_head(false)
|
// height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false)
|
||||||
{
|
//{
|
||||||
assert(steps > 0);
|
// assert(steps > 0);
|
||||||
|
|
||||||
height = jp(Z) - endp(Z);
|
// if(height > EPSILON) { // Endpoint is below the starting point
|
||||||
if(height > EPSILON) { // Endpoint is below the starting point
|
|
||||||
|
|
||||||
// We just create a bridge geometry with the pillar parameters and
|
// // We just create a bridge geometry with the pillar parameters and
|
||||||
// move the data.
|
// // move the data.
|
||||||
Contour3D body = cylinder(radius, height, st, endp);
|
// Contour3D body = cylinder(radius, height, st, endp);
|
||||||
mesh.points.swap(body.points);
|
// mesh.points.swap(body.points);
|
||||||
mesh.faces3.swap(body.faces3);
|
// mesh.faces3.swap(body.faces3);
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
Pillar &Pillar::add_base(double baseheight, double radius)
|
//Pillar &Pillar::add_base(double baseheight, double radius)
|
||||||
{
|
//{
|
||||||
if(baseheight <= 0) return *this;
|
// if(baseheight <= 0) return *this;
|
||||||
if(baseheight > height) baseheight = height;
|
// if(baseheight > height) baseheight = height;
|
||||||
|
|
||||||
assert(steps >= 0);
|
// assert(steps >= 0);
|
||||||
auto last = int(steps - 1);
|
// auto last = int(steps - 1);
|
||||||
|
|
||||||
if(radius < r ) radius = r;
|
// if(radius < r ) radius = r;
|
||||||
|
|
||||||
double a = 2*PI/steps;
|
// double a = 2*PI/steps;
|
||||||
double z = endpt(Z) + baseheight;
|
// double z = endpt(Z) + baseheight;
|
||||||
|
|
||||||
for(size_t i = 0; i < steps; ++i) {
|
// for(size_t i = 0; i < steps; ++i) {
|
||||||
double phi = i*a;
|
// double phi = i*a;
|
||||||
double x = endpt(X) + r*std::cos(phi);
|
// double x = endpt(X) + r*std::cos(phi);
|
||||||
double y = endpt(Y) + r*std::sin(phi);
|
// double y = endpt(Y) + r*std::sin(phi);
|
||||||
base.points.emplace_back(x, y, z);
|
// base.points.emplace_back(x, y, z);
|
||||||
}
|
// }
|
||||||
|
|
||||||
for(size_t i = 0; i < steps; ++i) {
|
// for(size_t i = 0; i < steps; ++i) {
|
||||||
double phi = i*a;
|
// double phi = i*a;
|
||||||
double x = endpt(X) + radius*std::cos(phi);
|
// double x = endpt(X) + radius*std::cos(phi);
|
||||||
double y = endpt(Y) + radius*std::sin(phi);
|
// double y = endpt(Y) + radius*std::sin(phi);
|
||||||
base.points.emplace_back(x, y, z - baseheight);
|
// base.points.emplace_back(x, y, z - baseheight);
|
||||||
}
|
// }
|
||||||
|
|
||||||
auto ep = endpt; ep(Z) += baseheight;
|
// auto ep = endpt; ep(Z) += baseheight;
|
||||||
base.points.emplace_back(endpt);
|
// base.points.emplace_back(endpt);
|
||||||
base.points.emplace_back(ep);
|
// base.points.emplace_back(ep);
|
||||||
|
|
||||||
auto& indices = base.faces3;
|
// auto& indices = base.faces3;
|
||||||
auto hcenter = int(base.points.size() - 1);
|
// auto hcenter = int(base.points.size() - 1);
|
||||||
auto lcenter = int(base.points.size() - 2);
|
// auto lcenter = int(base.points.size() - 2);
|
||||||
auto offs = int(steps);
|
// auto offs = int(steps);
|
||||||
for(int i = 0; i < last; ++i) {
|
// for(int i = 0; i < last; ++i) {
|
||||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
// indices.emplace_back(i, i + offs, offs + i + 1);
|
||||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
// indices.emplace_back(i, offs + i + 1, i + 1);
|
||||||
indices.emplace_back(i, i + 1, hcenter);
|
// indices.emplace_back(i, i + 1, hcenter);
|
||||||
indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
// indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
||||||
}
|
// }
|
||||||
|
|
||||||
indices.emplace_back(0, last, offs);
|
// indices.emplace_back(0, last, offs);
|
||||||
indices.emplace_back(last, offs + last, offs);
|
// indices.emplace_back(last, offs + last, offs);
|
||||||
indices.emplace_back(hcenter, last, 0);
|
// indices.emplace_back(hcenter, last, 0);
|
||||||
indices.emplace_back(offs, offs + last, lcenter);
|
// indices.emplace_back(offs, offs + last, lcenter);
|
||||||
return *this;
|
// return *this;
|
||||||
}
|
//}
|
||||||
|
|
||||||
Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps):
|
Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps):
|
||||||
r(r_mm), startp(j1), endp(j2)
|
r(r_mm), startp(j1), endp(j2)
|
||||||
@ -423,7 +472,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
|||||||
|
|
||||||
for (auto &head : m_heads) {
|
for (auto &head : m_heads) {
|
||||||
if (ctl().stopcondition()) break;
|
if (ctl().stopcondition()) break;
|
||||||
if (head.is_valid()) merged.merge(head.mesh);
|
if (head.is_valid()) merged.merge(get_mesh(head));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &stick : m_pillars) {
|
for (auto &stick : m_pillars) {
|
||||||
@ -512,119 +561,5 @@ static Hit min_hit(const C &hits)
|
|||||||
return *mit;
|
return *mit;
|
||||||
}
|
}
|
||||||
|
|
||||||
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h)
|
|
||||||
{
|
|
||||||
static const size_t SAMPLES = 8;
|
|
||||||
|
|
||||||
// Move away slightly from the touching point to avoid raycasting on the
|
|
||||||
// inner surface of the mesh.
|
|
||||||
|
|
||||||
const double& sd = msh.cfg.safety_distance_mm;
|
|
||||||
|
|
||||||
auto& m = msh.emesh;
|
|
||||||
using HitResult = EigenMesh3D::hit_result;
|
|
||||||
|
|
||||||
// Hit results
|
|
||||||
std::array<HitResult, SAMPLES> hits;
|
|
||||||
|
|
||||||
Vec3d s1 = h.pos, s2 = h.junction_point();
|
|
||||||
|
|
||||||
struct Rings {
|
|
||||||
double rpin;
|
|
||||||
double rback;
|
|
||||||
Vec3d spin;
|
|
||||||
Vec3d sback;
|
|
||||||
PointRing<SAMPLES> ring;
|
|
||||||
|
|
||||||
Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); }
|
|
||||||
Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); }
|
|
||||||
} rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir};
|
|
||||||
|
|
||||||
// We will shoot multiple rays from the head pinpoint in the direction
|
|
||||||
// of the pinhead robe (side) surface. The result will be the smallest
|
|
||||||
// hit distance.
|
|
||||||
|
|
||||||
auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) {
|
|
||||||
// Point on the circle on the pin sphere
|
|
||||||
Vec3d ps = rings.pinring(i);
|
|
||||||
// This is the point on the circle on the back sphere
|
|
||||||
Vec3d p = rings.backring(i);
|
|
||||||
|
|
||||||
// Point ps is not on mesh but can be inside or
|
|
||||||
// outside as well. This would cause many problems
|
|
||||||
// with ray-casting. To detect the position we will
|
|
||||||
// use the ray-casting result (which has an is_inside
|
|
||||||
// predicate).
|
|
||||||
|
|
||||||
Vec3d n = (p - ps).normalized();
|
|
||||||
auto q = m.query_ray_hit(ps + sd * n, n);
|
|
||||||
|
|
||||||
if (q.is_inside()) { // the hit is inside the model
|
|
||||||
if (q.distance() > rings.rpin) {
|
|
||||||
// If we are inside the model and the hit
|
|
||||||
// distance is bigger than our pin circle
|
|
||||||
// diameter, it probably indicates that the
|
|
||||||
// support point was already inside the
|
|
||||||
// model, or there is really no space
|
|
||||||
// around the point. We will assign a zero
|
|
||||||
// hit distance to these cases which will
|
|
||||||
// enforce the function return value to be
|
|
||||||
// an invalid ray with zero hit distance.
|
|
||||||
// (see min_element at the end)
|
|
||||||
hit = HitResult(0.0);
|
|
||||||
} else {
|
|
||||||
// re-cast the ray from the outside of the
|
|
||||||
// object. The starting point has an offset
|
|
||||||
// of 2*safety_distance because the
|
|
||||||
// original ray has also had an offset
|
|
||||||
auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n);
|
|
||||||
hit = q2;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
hit = q;
|
|
||||||
};
|
|
||||||
|
|
||||||
ccr::enumerate(hits.begin(), hits.end(), hitfn);
|
|
||||||
|
|
||||||
return min_hit(hits);
|
|
||||||
}
|
|
||||||
|
|
||||||
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d)
|
|
||||||
{
|
|
||||||
static const size_t SAMPLES = 8;
|
|
||||||
|
|
||||||
Vec3d dir = (br.endp - br.startp).normalized();
|
|
||||||
PointRing<SAMPLES> ring{dir};
|
|
||||||
|
|
||||||
using Hit = EigenMesh3D::hit_result;
|
|
||||||
|
|
||||||
// Hit results
|
|
||||||
std::array<Hit, SAMPLES> hits;
|
|
||||||
|
|
||||||
const double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d;
|
|
||||||
bool ins_check = sd < msh.cfg.safety_distance_mm;
|
|
||||||
|
|
||||||
auto hitfn = [&br, &ring, &msh, dir, sd, ins_check](Hit & hit, size_t i) {
|
|
||||||
// Point on the circle on the pin sphere
|
|
||||||
Vec3d p = ring.get(i, br.startp, br.r + sd);
|
|
||||||
|
|
||||||
auto hr = msh.emesh.query_ray_hit(p + sd * dir, dir);
|
|
||||||
|
|
||||||
if (ins_check && hr.is_inside()) {
|
|
||||||
if (hr.distance() > 2 * br.r + sd)
|
|
||||||
hit = Hit(0.0);
|
|
||||||
else {
|
|
||||||
// re-cast the ray from the outside of the object
|
|
||||||
hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir,
|
|
||||||
dir);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
hit = hr;
|
|
||||||
};
|
|
||||||
|
|
||||||
ccr::enumerate(hits.begin(), hits.end(), hitfn);
|
|
||||||
|
|
||||||
return min_hit(hits);
|
|
||||||
}
|
|
||||||
|
|
||||||
}} // namespace Slic3r::sla
|
}} // namespace Slic3r::sla
|
||||||
|
@ -74,10 +74,12 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
|||||||
// h: Height
|
// h: Height
|
||||||
// ssteps: how many edges will create the base circle
|
// ssteps: how many edges will create the base circle
|
||||||
// sp: starting point
|
// sp: starting point
|
||||||
Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0});
|
Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero());
|
||||||
|
|
||||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45);
|
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45);
|
||||||
|
|
||||||
|
Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45);
|
||||||
|
|
||||||
const constexpr long ID_UNSET = -1;
|
const constexpr long ID_UNSET = -1;
|
||||||
|
|
||||||
struct Head {
|
struct Head {
|
||||||
@ -114,15 +116,7 @@ struct Head {
|
|||||||
|
|
||||||
void transform()
|
void transform()
|
||||||
{
|
{
|
||||||
using Quaternion = Eigen::Quaternion<double>;
|
// TODO: remove occurences
|
||||||
|
|
||||||
// We rotate the head to the specified direction The head's pointing
|
|
||||||
// side is facing upwards so this means that it would hold a support
|
|
||||||
// point with a normal pointing straight down. This is the reason of
|
|
||||||
// the -1 z coordinate
|
|
||||||
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir);
|
|
||||||
|
|
||||||
for(auto& p : mesh.points) p = quatern * p + pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double real_width() const
|
inline double real_width() const
|
||||||
@ -164,8 +158,8 @@ struct Junction {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Pillar {
|
struct Pillar {
|
||||||
Contour3D mesh;
|
// Contour3D mesh;
|
||||||
Contour3D base;
|
// Contour3D base;
|
||||||
double r = 1;
|
double r = 1;
|
||||||
size_t steps = 0;
|
size_t steps = 0;
|
||||||
Vec3d endpt;
|
Vec3d endpt;
|
||||||
@ -182,27 +176,42 @@ struct Pillar {
|
|||||||
|
|
||||||
// How many pillars are cascaded with this one
|
// How many pillars are cascaded with this one
|
||||||
unsigned links = 0;
|
unsigned links = 0;
|
||||||
|
|
||||||
Pillar(const Vec3d& jp, const Vec3d& endp,
|
Pillar(const Vec3d &endp, double h, double radius = 1, size_t st = 45):
|
||||||
double radius = 1, size_t st = 45);
|
height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) {}
|
||||||
|
|
||||||
Pillar(const Junction &junc, const Vec3d &endp)
|
|
||||||
: Pillar(junc.pos, endp, junc.r, junc.steps)
|
// Pillar(const Junction &junc, const Vec3d &endp)
|
||||||
{}
|
// : Pillar(junc.pos, endp, junc.r, junc.steps)
|
||||||
|
// {}
|
||||||
Pillar(const Head &head, const Vec3d &endp, double radius = 1)
|
|
||||||
: Pillar(head.junction_point(), endp,
|
|
||||||
head.request_pillar_radius(radius), head.steps)
|
|
||||||
{}
|
|
||||||
|
|
||||||
inline Vec3d startpoint() const
|
inline Vec3d startpoint() const
|
||||||
{
|
{
|
||||||
return {endpt(X), endpt(Y), endpt(Z) + height};
|
return {endpt.x(), endpt.y(), endpt.z() + height};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const Vec3d& endpoint() const { return endpt; }
|
inline const Vec3d& endpoint() const { return endpt; }
|
||||||
|
|
||||||
Pillar& add_base(double baseheight = 3, double radius = 2);
|
// Pillar& add_base(double baseheight = 3, double radius = 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pedestal {
|
||||||
|
Vec3d pos;
|
||||||
|
double height, radius;
|
||||||
|
size_t steps = 45;
|
||||||
|
|
||||||
|
Pedestal() = default;
|
||||||
|
Pedestal(const Vec3d &p, double h = 3., double r = 2., size_t stps = 45)
|
||||||
|
: pos{p}, height{h}, radius{r}, steps{stps}
|
||||||
|
{}
|
||||||
|
|
||||||
|
Pedestal(const Pillar &p, double h = 3., double r = 2.)
|
||||||
|
: Pedestal{p.endpt, std::min(h, p.height), std::max(r, p.r), p.steps}
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PinJoin {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A Bridge between two pillars (with junction endpoints)
|
// A Bridge between two pillars (with junction endpoints)
|
||||||
@ -241,66 +250,39 @@ struct Pad {
|
|||||||
bool empty() const { return tmesh.facets_count() == 0; }
|
bool empty() const { return tmesh.facets_count() == 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Give points on a 3D ring with given center, radius and orientation
|
inline Contour3D get_mesh(const Head &h)
|
||||||
// method based on:
|
{
|
||||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, h.steps);
|
||||||
template<size_t N>
|
|
||||||
class PointRing {
|
|
||||||
std::array<double, N> m_phis;
|
|
||||||
|
|
||||||
// Two vectors that will be perpendicular to each other and to the
|
using Quaternion = Eigen::Quaternion<double>;
|
||||||
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
|
|
||||||
// placeholder.
|
|
||||||
// a and b vectors are perpendicular to the ring direction and to each other.
|
|
||||||
// Together they define the plane where we have to iterate with the
|
|
||||||
// given angles in the 'm_phis' vector
|
|
||||||
Vec3d a = {0, 1, 0}, b;
|
|
||||||
double m_radius = 0.;
|
|
||||||
|
|
||||||
static inline bool constexpr is_one(double val)
|
// We rotate the head to the specified direction The head's pointing
|
||||||
{
|
// side is facing upwards so this means that it would hold a support
|
||||||
return std::abs(std::abs(val) - 1) < 1e-20;
|
// point with a normal pointing straight down. This is the reason of
|
||||||
|
// the -1 z coordinate
|
||||||
|
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir);
|
||||||
|
|
||||||
|
for(auto& p : mesh.points) p = quatern * p + h.pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Contour3D get_mesh(const Pillar &p)
|
||||||
|
{
|
||||||
|
assert(p.steps > 0);
|
||||||
|
|
||||||
|
if(p.height > EPSILON) { // Endpoint is below the starting point
|
||||||
|
// We just create a bridge geometry with the pillar parameters and
|
||||||
|
// move the data.
|
||||||
|
return cylinder(p.r, p.height, p.steps, p.endpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
PointRing(const Vec3d &n)
|
inline Contour3D get_mesh(const Pedestal &p, double h, double r)
|
||||||
{
|
{
|
||||||
m_phis = linspace_array<N>(0., 2 * PI);
|
return pedestal(p.pos, p.height, p.radius, p.steps);
|
||||||
|
}
|
||||||
|
|
||||||
// We have to address the case when the direction vector v (same as
|
|
||||||
// dir) is coincident with one of the world axes. In this case two of
|
|
||||||
// its components will be completely zero and one is 1.0. Our method
|
|
||||||
// becomes dangerous here due to division with zero. Instead, vector
|
|
||||||
// 'a' can be an element-wise rotated version of 'v'
|
|
||||||
if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
|
|
||||||
a = {n(Z), n(X), n(Y)};
|
|
||||||
b = {n(Y), n(Z), n(X)};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
|
|
||||||
b = a.cross(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec3d get(size_t idx, const Vec3d src, double r) const
|
|
||||||
{
|
|
||||||
double phi = m_phis[idx];
|
|
||||||
double sinphi = std::sin(phi);
|
|
||||||
double cosphi = std::cos(phi);
|
|
||||||
|
|
||||||
double rpscos = r * cosphi;
|
|
||||||
double rpssin = r * sinphi;
|
|
||||||
|
|
||||||
// Point on the sphere
|
|
||||||
return {src(X) + rpscos * a(X) + rpssin * b(X),
|
|
||||||
src(Y) + rpscos * a(Y) + rpssin * b(Y),
|
|
||||||
src(Z) + rpscos * a(Z) + rpssin * b(Z)};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
|
|
||||||
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
|
|
||||||
|
|
||||||
// This class will hold the support tree meshes with some additional
|
// This class will hold the support tree meshes with some additional
|
||||||
// bookkeeping as well. Various parts of the support geometry are stored
|
// bookkeeping as well. Various parts of the support geometry are stored
|
||||||
|
@ -15,6 +15,119 @@ using libnest2d::opt::StopCriteria;
|
|||||||
using libnest2d::opt::GeneticOptimizer;
|
using libnest2d::opt::GeneticOptimizer;
|
||||||
using libnest2d::opt::SubplexOptimizer;
|
using libnest2d::opt::SubplexOptimizer;
|
||||||
|
|
||||||
|
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h)
|
||||||
|
{
|
||||||
|
static const size_t SAMPLES = 8;
|
||||||
|
|
||||||
|
// Move away slightly from the touching point to avoid raycasting on the
|
||||||
|
// inner surface of the mesh.
|
||||||
|
|
||||||
|
const double& sd = msh.cfg.safety_distance_mm;
|
||||||
|
|
||||||
|
auto& m = msh.emesh;
|
||||||
|
using HitResult = EigenMesh3D::hit_result;
|
||||||
|
|
||||||
|
// Hit results
|
||||||
|
std::array<HitResult, SAMPLES> hits;
|
||||||
|
|
||||||
|
Vec3d s1 = h.pos, s2 = h.junction_point();
|
||||||
|
|
||||||
|
struct Rings {
|
||||||
|
double rpin;
|
||||||
|
double rback;
|
||||||
|
Vec3d spin;
|
||||||
|
Vec3d sback;
|
||||||
|
PointRing<SAMPLES> ring;
|
||||||
|
|
||||||
|
Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); }
|
||||||
|
Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); }
|
||||||
|
} rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir};
|
||||||
|
|
||||||
|
// We will shoot multiple rays from the head pinpoint in the direction
|
||||||
|
// of the pinhead robe (side) surface. The result will be the smallest
|
||||||
|
// hit distance.
|
||||||
|
|
||||||
|
auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) {
|
||||||
|
// Point on the circle on the pin sphere
|
||||||
|
Vec3d ps = rings.pinring(i);
|
||||||
|
// This is the point on the circle on the back sphere
|
||||||
|
Vec3d p = rings.backring(i);
|
||||||
|
|
||||||
|
// Point ps is not on mesh but can be inside or
|
||||||
|
// outside as well. This would cause many problems
|
||||||
|
// with ray-casting. To detect the position we will
|
||||||
|
// use the ray-casting result (which has an is_inside
|
||||||
|
// predicate).
|
||||||
|
|
||||||
|
Vec3d n = (p - ps).normalized();
|
||||||
|
auto q = m.query_ray_hit(ps + sd * n, n);
|
||||||
|
|
||||||
|
if (q.is_inside()) { // the hit is inside the model
|
||||||
|
if (q.distance() > rings.rpin) {
|
||||||
|
// If we are inside the model and the hit
|
||||||
|
// distance is bigger than our pin circle
|
||||||
|
// diameter, it probably indicates that the
|
||||||
|
// support point was already inside the
|
||||||
|
// model, or there is really no space
|
||||||
|
// around the point. We will assign a zero
|
||||||
|
// hit distance to these cases which will
|
||||||
|
// enforce the function return value to be
|
||||||
|
// an invalid ray with zero hit distance.
|
||||||
|
// (see min_element at the end)
|
||||||
|
hit = HitResult(0.0);
|
||||||
|
} else {
|
||||||
|
// re-cast the ray from the outside of the
|
||||||
|
// object. The starting point has an offset
|
||||||
|
// of 2*safety_distance because the
|
||||||
|
// original ray has also had an offset
|
||||||
|
auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n);
|
||||||
|
hit = q2;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
hit = q;
|
||||||
|
};
|
||||||
|
|
||||||
|
ccr::enumerate(hits.begin(), hits.end(), hitfn);
|
||||||
|
|
||||||
|
return min_hit(hits);
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d)
|
||||||
|
{
|
||||||
|
|
||||||
|
static const size_t SAMPLES = 8;
|
||||||
|
|
||||||
|
Vec3d dir = (br.endp - br.startp).normalized();
|
||||||
|
PointRing<SAMPLES> ring{dir};
|
||||||
|
|
||||||
|
using Hit = EigenMesh3D::hit_result;
|
||||||
|
|
||||||
|
// Hit results
|
||||||
|
std::array<Hit, SAMPLES> hits;
|
||||||
|
|
||||||
|
double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d;
|
||||||
|
|
||||||
|
auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) {
|
||||||
|
|
||||||
|
// Point on the circle on the pin sphere
|
||||||
|
Vec3d p = ring.get(i, br.startp, br.r + sd);
|
||||||
|
|
||||||
|
auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir);
|
||||||
|
|
||||||
|
if(hr.is_inside()) {
|
||||||
|
if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0);
|
||||||
|
else {
|
||||||
|
// re-cast the ray from the outside of the object
|
||||||
|
hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir);
|
||||||
|
}
|
||||||
|
} else hit = hr;
|
||||||
|
};
|
||||||
|
|
||||||
|
ccr::enumerate(hits.begin(), hits.end(), hitfn);
|
||||||
|
|
||||||
|
return min_hit(hits);
|
||||||
|
}
|
||||||
|
|
||||||
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
||||||
const SupportableMesh &sm)
|
const SupportableMesh &sm)
|
||||||
: m_cfg(sm.cfg)
|
: m_cfg(sm.cfg)
|
||||||
@ -246,7 +359,7 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
|
EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
|
||||||
const Vec3d &src, const Vec3d &dir, double r, double safety_d)
|
const Vec3d &src, const Vec3d &dir, double r, double sd)
|
||||||
{
|
{
|
||||||
static const size_t SAMPLES = 8;
|
static const size_t SAMPLES = 8;
|
||||||
PointRing<SAMPLES> ring{dir};
|
PointRing<SAMPLES> ring{dir};
|
||||||
@ -255,25 +368,20 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
|
|||||||
|
|
||||||
// Hit results
|
// Hit results
|
||||||
std::array<Hit, SAMPLES> hits;
|
std::array<Hit, SAMPLES> hits;
|
||||||
|
|
||||||
double sd = std::isnan(safety_d) ? m_cfg.safety_distance_mm : safety_d;
|
|
||||||
sd = sd * r / m_cfg.head_back_radius_mm;
|
|
||||||
|
|
||||||
bool ins_check = sd < m_cfg.safety_distance_mm;
|
|
||||||
|
|
||||||
ccr::enumerate(hits.begin(), hits.end(),
|
ccr::enumerate(hits.begin(), hits.end(),
|
||||||
[this, r, src, ins_check, &ring, dir, sd] (Hit &hit, size_t i) {
|
[this, r, src, /*ins_check,*/ &ring, dir, sd] (Hit &hit, size_t i) {
|
||||||
|
|
||||||
// Point on the circle on the pin sphere
|
// Point on the circle on the pin sphere
|
||||||
Vec3d p = ring.get(i, src, r + sd);
|
Vec3d p = ring.get(i, src, r + sd);
|
||||||
|
|
||||||
auto hr = m_mesh.query_ray_hit(p + r * dir, dir);
|
auto hr = m_mesh.query_ray_hit(p + r * dir, dir);
|
||||||
|
|
||||||
if(ins_check && hr.is_inside()) {
|
if(/*ins_check && */hr.is_inside()) {
|
||||||
if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
|
if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
|
||||||
else {
|
else {
|
||||||
// re-cast the ray from the outside of the object
|
// re-cast the ray from the outside of the object
|
||||||
hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir);
|
hit = m_mesh.query_ray_hit(p + (hr.distance() + EPSILON) * dir, dir);
|
||||||
}
|
}
|
||||||
} else hit = hr;
|
} else hit = hr;
|
||||||
});
|
});
|
||||||
@ -499,7 +607,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||||||
normal_mode = false;
|
normal_mode = false;
|
||||||
|
|
||||||
if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) {
|
if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) {
|
||||||
m_builder.add_pillar(head_id, jp, radius);
|
if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -507,7 +615,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||||||
|
|
||||||
// Check if the deduced route is sane and exit with error if not.
|
// Check if the deduced route is sane and exit with error if not.
|
||||||
if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) {
|
if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) {
|
||||||
m_builder.add_pillar(head_id, jp, radius);
|
if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -798,13 +906,16 @@ void SupportTreeBuildsteps::routing_to_ground()
|
|||||||
cl_centroids.emplace_back(hid);
|
cl_centroids.emplace_back(hid);
|
||||||
|
|
||||||
Head &h = m_builder.head(hid);
|
Head &h = m_builder.head(hid);
|
||||||
h.transform();
|
|
||||||
|
|
||||||
if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) {
|
if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) {
|
||||||
BOOST_LOG_TRIVIAL(warning)
|
BOOST_LOG_TRIVIAL(warning)
|
||||||
<< "Pillar cannot be created for support point id: " << hid;
|
<< "Pillar cannot be created for support point id: " << hid;
|
||||||
h.invalidate();
|
m_iheads_onmodel.emplace_back(h.id);
|
||||||
|
// h.invalidate();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we will go through the clusters ones again and connect the
|
// now we will go through the clusters ones again and connect the
|
||||||
@ -854,12 +965,14 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
|
|||||||
if(!std::isinf(tdown)) return false;
|
if(!std::isinf(tdown)) return false;
|
||||||
|
|
||||||
Vec3d endp = hjp + d * dir;
|
Vec3d endp = hjp + d * dir;
|
||||||
m_builder.add_bridge(head.id, endp);
|
bool ret = false;
|
||||||
m_builder.add_junction(endp, head.r_back_mm);
|
|
||||||
|
if ((ret = create_ground_pillar(endp, dir, head.r_back_mm))) {
|
||||||
|
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 ret;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SupportTreeBuildsteps::connect_to_ground(Head &head)
|
bool SupportTreeBuildsteps::connect_to_ground(Head &head)
|
||||||
|
@ -46,6 +46,68 @@ inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
|
|||||||
return spheric_to_dir(v.first, v.second);
|
return spheric_to_dir(v.first, v.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
|
template<size_t N>
|
||||||
|
class PointRing {
|
||||||
|
std::array<double, N> m_phis;
|
||||||
|
|
||||||
|
// Two vectors that will be perpendicular to each other and to the
|
||||||
|
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
|
||||||
|
// placeholder.
|
||||||
|
// a and b vectors are perpendicular to the ring direction and to each other.
|
||||||
|
// Together they define the plane where we have to iterate with the
|
||||||
|
// given angles in the 'm_phis' vector
|
||||||
|
Vec3d a = {0, 1, 0}, b;
|
||||||
|
double m_radius = 0.;
|
||||||
|
|
||||||
|
static inline bool constexpr is_one(double val)
|
||||||
|
{
|
||||||
|
return std::abs(std::abs(val) - 1) < 1e-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
PointRing(const Vec3d &n)
|
||||||
|
{
|
||||||
|
m_phis = linspace_array<N>(0., 2 * PI);
|
||||||
|
|
||||||
|
// We have to address the case when the direction vector v (same as
|
||||||
|
// dir) is coincident with one of the world axes. In this case two of
|
||||||
|
// its components will be completely zero and one is 1.0. Our method
|
||||||
|
// becomes dangerous here due to division with zero. Instead, vector
|
||||||
|
// 'a' can be an element-wise rotated version of 'v'
|
||||||
|
if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
|
||||||
|
a = {n(Z), n(X), n(Y)};
|
||||||
|
b = {n(Y), n(Z), n(X)};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
|
||||||
|
b = a.cross(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d get(size_t idx, const Vec3d src, double r) const
|
||||||
|
{
|
||||||
|
double phi = m_phis[idx];
|
||||||
|
double sinphi = std::sin(phi);
|
||||||
|
double cosphi = std::cos(phi);
|
||||||
|
|
||||||
|
double rpscos = r * cosphi;
|
||||||
|
double rpssin = r * sinphi;
|
||||||
|
|
||||||
|
// Point on the sphere
|
||||||
|
return {src(X) + rpscos * a(X) + rpssin * b(X),
|
||||||
|
src(Y) + rpscos * a(Y) + rpssin * b(Y),
|
||||||
|
src(Z) + rpscos * a(Z) + rpssin * b(Z)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
|
||||||
|
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
|
||||||
|
|
||||||
// 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>
|
||||||
@ -242,7 +304,15 @@ class SupportTreeBuildsteps {
|
|||||||
const Vec3d& s,
|
const Vec3d& s,
|
||||||
const Vec3d& dir,
|
const Vec3d& dir,
|
||||||
double r,
|
double r,
|
||||||
double safety_d = std::nan(""));
|
double safety_d);
|
||||||
|
|
||||||
|
EigenMesh3D::hit_result bridge_mesh_intersect(
|
||||||
|
const Vec3d& s,
|
||||||
|
const Vec3d& dir,
|
||||||
|
double r)
|
||||||
|
{
|
||||||
|
return bridge_mesh_intersect(s, dir, r, m_cfg.safety_distance_mm);
|
||||||
|
}
|
||||||
|
|
||||||
template<class...Args>
|
template<class...Args>
|
||||||
inline double bridge_mesh_distance(Args&&...args) {
|
inline double bridge_mesh_distance(Args&&...args) {
|
||||||
|
Loading…
Reference in New Issue
Block a user