Merge branch 'tm_colldetection_upgr'

This commit is contained in:
tamasmeszaros 2019-02-01 13:30:29 +01:00
commit 88f847c5bd
6 changed files with 547 additions and 206 deletions

View File

@ -15,7 +15,7 @@ namespace Slic3r {
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights, SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
const Config& config, std::function<void(void)> throw_on_cancel) const Config& config, std::function<void(void)> throw_on_cancel)
: m_config(config), m_V(emesh.V), m_F(emesh.F), m_throw_on_cancel(throw_on_cancel) : m_config(config), m_V(emesh.V()), m_F(emesh.F()), m_throw_on_cancel(throw_on_cancel)
{ {
// FIXME: It might be safer to get rid of the rand() calls altogether, because it is probably // FIXME: It might be safer to get rid of the rand() calls altogether, because it is probably
// not always thread-safe and can be slow if it is. // not always thread-safe and can be slow if it is.

View File

@ -28,7 +28,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
// We will use only one instance of this converted mesh to examine different // We will use only one instance of this converted mesh to examine different
// rotations // rotations
EigenMesh3D emesh = to_eigenmesh(modelobj); EigenMesh3D emesh(modelobj.raw_mesh());
// For current iteration number // For current iteration number
unsigned status = 0; unsigned status = 0;
@ -68,12 +68,12 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
// area. The current function is only an example of how to optimize. // area. The current function is only an example of how to optimize.
// Later we can add more criteria like the number of overhangs, etc... // Later we can add more criteria like the number of overhangs, etc...
for(int i = 0; i < m.F.rows(); i++) { for(int i = 0; i < m.F().rows(); i++) {
auto idx = m.F.row(i); auto idx = m.F().row(i);
Vec3d p1 = m.V.row(idx(0)); Vec3d p1 = m.V().row(idx(0));
Vec3d p2 = m.V.row(idx(1)); Vec3d p2 = m.V().row(idx(1));
Vec3d p3 = m.V.row(idx(2)); Vec3d p3 = m.V().row(idx(2));
Eigen::Vector3d U = p2 - p1; Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1; Eigen::Vector3d V = p3 - p1;

View File

@ -13,6 +13,7 @@
#include <libslic3r/Model.hpp> #include <libslic3r/Model.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
/** /**
* Terminology: * Terminology:
@ -510,7 +511,6 @@ struct CompactBridge {
// A wrapper struct around the base pool (pad) // A wrapper struct around the base pool (pad)
struct Pad { struct Pad {
// Contour3D mesh;
TriangleMesh tmesh; TriangleMesh tmesh;
PoolConfig cfg; PoolConfig cfg;
double zlevel = 0; double zlevel = 0;
@ -543,23 +543,6 @@ struct Pad {
bool empty() const { return tmesh.facets_count() == 0; } bool empty() const { return tmesh.facets_count() == 0; }
}; };
EigenMesh3D to_eigenmesh(const Contour3D& cntr) {
EigenMesh3D emesh;
auto& V = emesh.V;
auto& F = emesh.F;
V.resize(Eigen::Index(cntr.points.size()), 3);
F.resize(Eigen::Index(cntr.indices.size()), 3);
for (int i = 0; i < V.rows(); ++i) {
V.row(i) = cntr.points[size_t(i)];
F.row(i) = cntr.indices[size_t(i)];
}
return emesh;
}
// The minimum distance for two support points to remain valid. // The minimum distance for two support points to remain valid.
static const double /*constexpr*/ D_SP = 0.1; static const double /*constexpr*/ D_SP = 0.1;
@ -567,46 +550,6 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
X, Y, Z X, Y, Z
}; };
EigenMesh3D to_eigenmesh(const TriangleMesh& tmesh) {
const stl_file& stl = tmesh.stl;
EigenMesh3D outmesh;
auto&& bb = tmesh.bounding_box();
outmesh.ground_level += bb.min(Z);
auto& V = outmesh.V;
auto& F = outmesh.F;
V.resize(3*stl.stats.number_of_facets, 3);
F.resize(stl.stats.number_of_facets, 3);
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
const stl_facet* facet = stl.facet_start+i;
V(3*i+0, 0) = double(facet->vertex[0](0));
V(3*i+0, 1) = double(facet->vertex[0](1));
V(3*i+0, 2) = double(facet->vertex[0](2));
V(3*i+1, 0) = double(facet->vertex[1](0));
V(3*i+1, 1) = double(facet->vertex[1](1));
V(3*i+1, 2) = double(facet->vertex[1](2));
V(3*i+2, 0) = double(facet->vertex[2](0));
V(3*i+2, 1) = double(facet->vertex[2](1));
V(3*i+2, 2) = double(facet->vertex[2](2));
F(i, 0) = int(3*i+0);
F(i, 1) = int(3*i+1);
F(i, 2) = int(3*i+2);
}
return outmesh;
}
EigenMesh3D to_eigenmesh(const ModelObject& modelobj) {
return to_eigenmesh(modelobj.raw_mesh());
}
PointSet to_point_set(const std::vector<Vec3d> &v) PointSet to_point_set(const std::vector<Vec3d> &v)
{ {
PointSet ret(v.size(), 3); PointSet ret(v.size(), 3);
@ -618,9 +561,170 @@ Vec3d model_coord(const ModelInstance& object, const Vec3f& mesh_coord) {
return object.transform_vector(mesh_coord.cast<double>()); return object.transform_vector(mesh_coord.cast<double>());
} }
double ray_mesh_intersect(const Vec3d& s, inline double ray_mesh_intersect(const Vec3d& s,
const Vec3d& dir, const Vec3d& dir,
const EigenMesh3D& m); const EigenMesh3D& m)
{
return m.query_ray_hit(s, dir).distance();
}
// This function will test if a future pinhead would not collide with the model
// geometry. It does not take a 'Head' object because those are created after
// this test.
// Parameters:
// s: The touching point on the model surface.
// dir: This is the direction of the head from the pin to the back
// r_pin, r_back: the radiuses of the pin and the back sphere
// width: This is the full width from the pin center to the back center
// m: The object mesh
//
// Optional:
// samples: how many rays will be shot
// safety distance: This will be added to the radiuses to have a safety distance
// from the mesh.
double pinhead_mesh_intersect(const Vec3d& s,
const Vec3d& dir,
double r_pin,
double r_back,
double width,
const EigenMesh3D& m,
unsigned samples = 8,
double safety_distance = 0.001)
{
// method based on:
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
// 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.
// Move away slightly from the touching point to avoid raycasting on the
// inner surface of the mesh.
Vec3d v = dir; // Our direction (axis)
Vec3d c = s + width * dir;
const double& sd = safety_distance;
// 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.
Vec3d a(0, 1, 0), b;
// The portions of the circle (the head-back circle) for which we will shoot
// rays.
std::vector<double> phis(samples);
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
a(Z) = -(v(X)*a(X) + v(Y)*a(Y)) / v(Z);
b = a.cross(v);
// Now a and b vectors are perpendicular to v and to each other. Together
// they define the plane where we have to iterate with the given angles
// in the 'phis' vector
tbb::parallel_for(size_t(0), phis.size(),
[&phis, &m, sd, r_pin, r_back, s, a, b, c](size_t i)
{
double& phi = phis[i];
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
// Let's have a safety coefficient for the radiuses.
double rpscos = (sd + r_pin) * cosphi;
double rpssin = (sd + r_pin) * sinphi;
double rpbcos = (sd + r_back) * cosphi;
double rpbsin = (sd + r_back) * sinphi;
// Point on the circle on the pin sphere
Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X),
s(Y) + rpscos * a(Y) + rpssin * b(Y),
s(Z) + rpscos * a(Z) + rpssin * b(Z));
// Point ps is not on mesh but can be inside or outside as well. This
// would cause many problems with ray-casting. So we query the closest
// point on the mesh to this.
// auto psq = m.signed_distance(ps);
// This is the point on the circle on the back sphere
Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X),
c(Y) + rpbcos * a(Y) + rpbsin * b(Y),
c(Z) + rpbcos * a(Z) + rpbsin * b(Z));
// Vec3d n = (p - psq.point_on_mesh()).normalized();
// phi = m.query_ray_hit(psq.point_on_mesh() + sd*n, n);
Vec3d n = (p - ps).normalized();
auto hr = m.query_ray_hit(ps + sd*n, n);
if(hr.is_inside()) { // the hit is inside the model
if(hr.distance() > 2*r_pin) phi = 0;
else {
// re-cast the ray from the outside of the object
auto hr2 = m.query_ray_hit(ps + (hr.distance() + 2*sd)*n, n);
phi = hr2.distance();
}
} else phi = hr.distance();
});
auto mit = std::min_element(phis.begin(), phis.end());
return *mit;
}
// Checking bridge (pillar and stick as well) intersection with the model. If
// the function is used for headless sticks, the ins_check parameter have to be
// true as the beginning of the stick might be inside the model geometry.
double bridge_mesh_intersect(const Vec3d& s,
const Vec3d& dir,
double r,
const EigenMesh3D& m,
bool ins_check = false,
unsigned samples = 4,
double safety_distance = 0.001)
{
// helper vector calculations
Vec3d a(0, 1, 0), b;
const double& sd = safety_distance;
a(Z) = -(dir(X)*a(X) + dir(Y)*a(Y)) / dir(Z);
b = a.cross(dir);
// circle portions
std::vector<double> phis(samples);
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
tbb::parallel_for(size_t(0), phis.size(),
[&phis, &m, a, b, sd, dir, r, s, ins_check](size_t i)
{
double& phi = phis[i];
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
// Let's have a safety coefficient for the radiuses.
double rcos = (sd + r) * cosphi;
double rsin = (sd + r) * sinphi;
// Point on the circle on the pin sphere
Vec3d p (s(X) + rcos * a(X) + rsin * b(X),
s(Y) + rcos * a(Y) + rsin * b(Y),
s(Z) + rcos * a(Z) + rsin * b(Z));
auto hr = m.query_ray_hit(p + sd*dir, dir);
if(ins_check && hr.is_inside()) {
if(hr.distance() > 2*r) phi = 0;
else {
// re-cast the ray from the outside of the object
auto hr2 = m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir);
phi = hr2.distance();
}
} else phi = hr.distance();
});
auto mit = std::min_element(phis.begin(), phis.end());
return *mit;
}
PointSet normals(const PointSet& points, const EigenMesh3D& mesh, PointSet normals(const PointSet& points, const EigenMesh3D& mesh,
double eps = 0.05, // min distance from edges double eps = 0.05, // min distance from edges
@ -640,6 +744,19 @@ ClusteredPoints cluster(
std::function<bool(const SpatElement&, const SpatElement&)> pred, std::function<bool(const SpatElement&, const SpatElement&)> pred,
unsigned max_points = 0); unsigned max_points = 0);
// This class will hold the support tree meshes with some additional bookkeeping
// as well. Various parts of the support geometry are stored separately and are
// merged when the caller queries the merged mesh. The merged result is cached
// for fast subsequent delivery of the merged mesh which can be quite complex.
// An object of this class will be used as the result type during the support
// generation algorithm. Parts will be added with the appropriate methods such
// as add_head or add_pillar which forwards the constructor arguments and fills
// the IDs of these substructures. The IDs are basically indices into the arrays
// of the appropriate type (heads, pillars, etc...). One can later query e.g. a
// pillar for a specific head...
//
// The support pad is considered an auxiliary geometry and is not part of the
// merged mesh. It can be retrieved using a dedicated method (pad())
class SLASupportTree::Impl { class SLASupportTree::Impl {
std::vector<Head> m_heads; std::vector<Head> m_heads;
std::vector<Pillar> m_pillars; std::vector<Pillar> m_pillars;
@ -1001,16 +1118,22 @@ bool SLASupportTree::generate(const PointSet &points,
// Indices of those who don't touch the ground // Indices of those who don't touch the ground
IndexSet noground_heads; IndexSet noground_heads;
// Groups of the 'ground_head' indices that belong into one cluster. These
// are candidates to be connected to one pillar.
ClusteredPoints ground_connectors; ClusteredPoints ground_connectors;
// A help function to translate ground head index to the actual coordinates.
auto gnd_head_pt = [&ground_heads, &head_positions] (size_t idx) { auto gnd_head_pt = [&ground_heads, &head_positions] (size_t idx) {
return Vec3d(head_positions.row(ground_heads[idx])); return Vec3d(head_positions.row(ground_heads[idx]));
}; };
// This algorithm uses the Impl class as its output stream. It will be
// filled gradually with support elements (heads, pillars, bridges, ...)
using Result = SLASupportTree::Impl; using Result = SLASupportTree::Impl;
Result& result = *m_impl; Result& result = *m_impl;
// Let's define the individual steps of the processing. We can experiment
// later with the ordering and the dependencies between them.
enum Steps { enum Steps {
BEGIN, BEGIN,
FILTER, FILTER,
@ -1026,14 +1149,15 @@ bool SLASupportTree::generate(const PointSet &points,
//... //...
}; };
// Debug: // t-hrow i-f c-ance-l-ed: It will be called many times so a shorthand will
// for(int pn = 0; pn < points.rows(); ++pn) { // come in handy.
// std::cout << "p " << pn << " " << points.row(pn) << std::endl;
// }
auto& tifcl = ctl.cancelfn; auto& tifcl = ctl.cancelfn;
// Filtering step: here we will discard inappropriate support points and
// decide the future of the appropriate ones. We will check if a pinhead
// is applicable and adjust its angle at each support point.
// We will also merge the support points that are just too close and can be
// considered as one.
auto filterfn = [tifcl] ( auto filterfn = [tifcl] (
const SupportConfig& cfg, const SupportConfig& cfg,
const PointSet& points, const PointSet& points,
@ -1044,10 +1168,6 @@ bool SLASupportTree::generate(const PointSet &points,
PointSet& headless_pos, PointSet& headless_pos,
PointSet& headless_norm) PointSet& headless_norm)
{ {
/* ******************************************************** */
/* Filtering step */
/* ******************************************************** */
// Get the points that are too close to each other and keep only the // Get the points that are too close to each other and keep only the
// first one // first one
auto aliases = auto aliases =
@ -1108,6 +1228,8 @@ bool SLASupportTree::generate(const PointSet &points,
std::sin(azimuth) * std::sin(polar), std::sin(azimuth) * std::sin(polar),
std::cos(polar)); std::cos(polar));
nn.normalize();
// save the head (pinpoint) position // save the head (pinpoint) position
Vec3d hp = filt_pts.row(i); Vec3d hp = filt_pts.row(i);
@ -1118,11 +1240,15 @@ bool SLASupportTree::generate(const PointSet &points,
// We should shoot a ray in the direction of the pinhead and // We should shoot a ray in the direction of the pinhead and
// see if there is enough space for it // see if there is enough space for it
double t = ray_mesh_intersect(hp + 0.1*nn, nn, mesh); double t = pinhead_mesh_intersect(
hp, // touching point
if(t > 2*w || std::isinf(t)) { nn,
// 2*w because of lower and upper pinhead cfg.head_front_radius_mm, // approx the radius
cfg.head_back_radius_mm,
w,
mesh);
if(t > w || std::isinf(t)) {
head_pos.row(pcount) = hp; head_pos.row(pcount) = hp;
// save the verified and corrected normal // save the verified and corrected normal
@ -1144,7 +1270,8 @@ bool SLASupportTree::generate(const PointSet &points,
headless_norm.conservativeResize(hlcount, Eigen::NoChange); headless_norm.conservativeResize(hlcount, Eigen::NoChange);
}; };
// Function to write the pinheads into the result // Pinhead creation: based on the filtering results, the Head objects will
// be constructed (together with their triangle meshes).
auto pinheadfn = [tifcl] ( auto pinheadfn = [tifcl] (
const SupportConfig& cfg, const SupportConfig& cfg,
PointSet& head_pos, PointSet& head_pos,
@ -1170,8 +1297,13 @@ bool SLASupportTree::generate(const PointSet &points,
} }
}; };
// &filtered_points, &head_positions, &result, &mesh, // Further classification of the support points with pinheads. If the
// &gndidx, &gndheight, &nogndidx, cfg // ground is directly reachable through a vertical line parallel to the Z
// axis we consider a support point as pillar candidate. If touches the
// model geometry, it will be marked as non-ground facing and further steps
// will process it. Also, the pillars will be grouped into clusters that can
// be interconnected with bridges. Elements of these groups may or may not
// be interconnected. Here we only run the clustering algorithm.
auto classifyfn = [tifcl] ( auto classifyfn = [tifcl] (
const SupportConfig& cfg, const SupportConfig& cfg,
const EigenMesh3D& mesh, const EigenMesh3D& mesh,
@ -1192,23 +1324,81 @@ bool SLASupportTree::generate(const PointSet &points,
gndidx.reserve(size_t(head_pos.rows())); gndidx.reserve(size_t(head_pos.rows()));
nogndidx.reserve(size_t(head_pos.rows())); nogndidx.reserve(size_t(head_pos.rows()));
// First we search decide which heads reach the ground and can be full
// pillars and which shall be connected to the model surface (or search
// a suitable path around the surface that leads to the ground -- TODO)
for(unsigned i = 0; i < head_pos.rows(); i++) { for(unsigned i = 0; i < head_pos.rows(); i++) {
tifcl(); tifcl();
auto& head = result.heads()[i]; auto& head = result.head(i);
Vec3d dir(0, 0, -1); Vec3d dir(0, 0, -1);
Vec3d startpoint = head.junction_point(); bool accept = false;
int ri = 1;
double t = std::numeric_limits<double>::infinity();
double hw = head.width_mm;
double t = ray_mesh_intersect(startpoint, dir, mesh); // We will try to assign a pillar to all the pinheads. If a pillar
// would pierce the model surface, we will try to adjust slightly
// the head with so that the pillar can be deployed.
while(!accept && head.width_mm > 0) {
Vec3d startpoint = head.junction_point();
// Collision detection
t = bridge_mesh_intersect(startpoint, dir, head.r_back_mm, mesh);
// Precise distance measurement
double tprec = ray_mesh_intersect(startpoint, dir, mesh);
if(std::isinf(tprec) && !std::isinf(t)) {
// This is a damned case where the pillar melds into the
// model but its center ray can reach the ground. We can
// not route this to the ground nor to the model surface.
head.width_mm = hw + (ri % 2? -1 : 1) * ri * head.r_back_mm;
} else {
accept = true; t = tprec;
auto id = head.id;
// We need to regenerate the head geometry
head = Head(head.r_back_mm,
head.r_pin_mm,
head.width_mm,
head.penetration_mm,
head.dir,
head.tr);
head.id = id;
}
ri++;
}
// Save the distance from a surface in the Z axis downwards. It may
// be infinity but that is telling us that it touches the ground.
gndheight.emplace_back(t); gndheight.emplace_back(t);
if(std::isinf(t)) gndidx.emplace_back(i); if(accept) {
else nogndidx.emplace_back(i); if(std::isinf(t)) gndidx.emplace_back(i);
else nogndidx.emplace_back(i);
} else {
// This is a serious issue. There was no way to deploy a pillar
// for the given pinhead. The whole thing has to be discarded
// leaving the model potentially unprintable.
//
// TODO: In the future this has to be solved by searching for
// a path in 3D space from this support point to a suitable
// pillar position or an existing pillar.
// As a workaround we could mark this head as "sidehead only"
// let it go trough the nearby pillar search in the next step.
BOOST_LOG_TRIVIAL(warning) << "A support point at "
<< head.tr.transpose()
<< " had to be discarded as there is"
<< " nowhere to route it.";
head.invalidate();
}
} }
// Transform the ground facing point indices top actual coordinates.
PointSet gnd(gndidx.size(), 3); PointSet gnd(gndidx.size(), 3);
for(size_t i = 0; i < gndidx.size(); i++) for(size_t i = 0; i < gndidx.size(); i++)
gnd.row(long(i)) = head_pos.row(gndidx[i]); gnd.row(long(i)) = head_pos.row(gndidx[i]);
@ -1228,7 +1418,8 @@ bool SLASupportTree::generate(const PointSet &points,
}, 3); // max 3 heads to connect to one centroid }, 3); // max 3 heads to connect to one centroid
}; };
// Helper function for interconnecting two pillars with zig-zag bridges // Helper function for interconnecting two pillars with zig-zag bridges.
// This is not an individual step.
auto interconnect = [&cfg]( auto interconnect = [&cfg](
const Pillar& pillar, const Pillar& pillar,
const Pillar& nextpillar, const Pillar& nextpillar,
@ -1246,7 +1437,7 @@ bool SLASupportTree::generate(const PointSet &points,
double zstep = pillar_dist * std::tan(-cfg.tilt); double zstep = pillar_dist * std::tan(-cfg.tilt);
ej(Z) = sj(Z) + zstep; ej(Z) = sj(Z) + zstep;
double chkd = ray_mesh_intersect(sj, dirv(sj, ej), emesh); double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh);
double bridge_distance = pillar_dist / std::cos(-cfg.tilt); double bridge_distance = pillar_dist / std::cos(-cfg.tilt);
// If the pillars are so close that they touch each other, // If the pillars are so close that they touch each other,
@ -1254,7 +1445,7 @@ bool SLASupportTree::generate(const PointSet &points,
if(pillar_dist > 2*cfg.head_back_radius_mm && if(pillar_dist > 2*cfg.head_back_radius_mm &&
bridge_distance < cfg.max_bridge_length_mm) bridge_distance < cfg.max_bridge_length_mm)
while(sj(Z) > pillar.endpoint(Z) + cfg.base_radius_mm && while(sj(Z) > pillar.endpoint(Z) + cfg.base_radius_mm &&
ej(Z) > nextpillar.endpoint(Z) + + cfg.base_radius_mm) ej(Z) > nextpillar.endpoint(Z) + cfg.base_radius_mm)
{ {
if(chkd >= bridge_distance) { if(chkd >= bridge_distance) {
result.add_bridge(sj, ej, pillar.r); result.add_bridge(sj, ej, pillar.r);
@ -1272,9 +1463,11 @@ bool SLASupportTree::generate(const PointSet &points,
Vec3d bej(sj(X), sj(Y), ej(Z)); Vec3d bej(sj(X), sj(Y), ej(Z));
// need to check collision for the cross stick // need to check collision for the cross stick
double backchkd = ray_mesh_intersect(bsj, double backchkd = bridge_mesh_intersect(bsj,
dirv(bsj, bej), dirv(bsj, bej),
emesh); pillar.r,
emesh);
if(backchkd >= bridge_distance) { if(backchkd >= bridge_distance) {
result.add_bridge(bsj, bej, pillar.r); result.add_bridge(bsj, bej, pillar.r);
@ -1283,10 +1476,15 @@ bool SLASupportTree::generate(const PointSet &points,
} }
sj.swap(ej); sj.swap(ej);
ej(Z) = sj(Z) + zstep; ej(Z) = sj(Z) + zstep;
chkd = ray_mesh_intersect(sj, dirv(sj, ej), emesh); chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh);
} }
}; };
// Step: Routing the ground connected pinheads, and interconnecting them
// with additional (angled) bridges. Not all of these pinheads will be
// a full pillar (ground connected). Some will connect to a nearby pillar
// using a bridge. The max number of such side-heads for a central pillar
// is limited to avoid bad weight distribution.
auto routing_ground_fn = [gnd_head_pt, interconnect, tifcl]( auto routing_ground_fn = [gnd_head_pt, interconnect, tifcl](
const SupportConfig& cfg, const SupportConfig& cfg,
const ClusteredPoints& gnd_clusters, const ClusteredPoints& gnd_clusters,
@ -1361,12 +1559,12 @@ bool SLASupportTree::generate(const PointSet &points,
// is distributed more effectively on the pillar. // is distributed more effectively on the pillar.
auto search_nearest = auto search_nearest =
[&cfg, &result, &emesh, maxbridgelen, gndlvl] [&tifcl, &cfg, &result, &emesh, maxbridgelen, gndlvl, pradius]
(SpatIndex& spindex, const Vec3d& jsh) (SpatIndex& spindex, const Vec3d& jsh)
{ {
long nearest_id = -1; long nearest_id = -1;
const double max_len = maxbridgelen / 2; const double max_len = maxbridgelen / 2;
while(nearest_id < 0 && !spindex.empty()) { while(nearest_id < 0 && !spindex.empty()) { tifcl();
// loop until a suitable head is not found // loop until a suitable head is not found
// if there is a pillar closer than the cluster center // if there is a pillar closer than the cluster center
// (this may happen as the clustering is not perfect) // (this may happen as the clustering is not perfect)
@ -1391,10 +1589,13 @@ bool SLASupportTree::generate(const PointSet &points,
} }
double d = distance(jp, jn); double d = distance(jp, jn);
if(jn(Z) <= (gndlvl + 2*cfg.head_width_mm) || d > max_len)
if(jn(Z) <= gndlvl + 2*cfg.head_width_mm || d > max_len)
break; break;
double chkd = ray_mesh_intersect(jp, dirv(jp, jn), emesh); double chkd = bridge_mesh_intersect(jp, dirv(jp, jn),
pradius,
emesh);
if(chkd >= d) nearest_id = ne.second; if(chkd >= d) nearest_id = ne.second;
spindex.remove(ne); spindex.remove(ne);
@ -1480,7 +1681,7 @@ bool SLASupportTree::generate(const PointSet &points,
if(!ring.empty()) { if(!ring.empty()) {
// inner ring is now in 'newring' and outer ring is in 'ring' // inner ring is now in 'newring' and outer ring is in 'ring'
SpatIndex innerring; SpatIndex innerring;
for(unsigned i : newring) { for(unsigned i : newring) { tifcl();
const Pillar& pill = result.head_pillar(gndidx[i]); const Pillar& pill = result.head_pillar(gndidx[i]);
assert(pill.id >= 0); assert(pill.id >= 0);
innerring.insert(pill.endpoint, unsigned(pill.id)); innerring.insert(pill.endpoint, unsigned(pill.id));
@ -1489,7 +1690,7 @@ bool SLASupportTree::generate(const PointSet &points,
// For all pillars in the outer ring find the closest in the // For all pillars in the outer ring find the closest in the
// inner ring and connect them. This will create the spider web // inner ring and connect them. This will create the spider web
// fashioned connections between pillars // fashioned connections between pillars
for(unsigned i : ring) { for(unsigned i : ring) { tifcl();
const Pillar& outerpill = result.head_pillar(gndidx[i]); const Pillar& outerpill = result.head_pillar(gndidx[i]);
auto res = innerring.nearest(outerpill.endpoint, 1); auto res = innerring.nearest(outerpill.endpoint, 1);
if(res.empty()) continue; if(res.empty()) continue;
@ -1515,6 +1716,7 @@ bool SLASupportTree::generate(const PointSet &points,
next != ring.end(); next != ring.end();
++it, ++next) ++it, ++next)
{ {
tifcl();
const Pillar& pillar = result.head_pillar(gndidx[*it]); const Pillar& pillar = result.head_pillar(gndidx[*it]);
const Pillar& nextpillar = result.head_pillar(gndidx[*next]); const Pillar& nextpillar = result.head_pillar(gndidx[*next]);
interconnect(pillar, nextpillar, emesh, result); interconnect(pillar, nextpillar, emesh, result);
@ -1529,6 +1731,11 @@ bool SLASupportTree::generate(const PointSet &points,
} }
}; };
// Step: routing the pinheads that are would connect to the model surface
// along the Z axis downwards. For now these will actually be connected with
// the model surface with a flipped pinhead. In the future here we could use
// some smart algorithms to search for a safe path to the ground or to a
// nearby pillar that can hold the supported weight.
auto routing_nongnd_fn = [tifcl]( auto routing_nongnd_fn = [tifcl](
const SupportConfig& cfg, const SupportConfig& cfg,
const std::vector<double>& gndheight, const std::vector<double>& gndheight,
@ -1590,6 +1797,9 @@ bool SLASupportTree::generate(const PointSet &points,
} }
}; };
// Step: process the support points where there is not enough space for a
// full pinhead. In this case we will use a rounded sphere as a touching
// point and use a thinner bridge (let's call it a stick).
auto process_headless = [tifcl]( auto process_headless = [tifcl](
const SupportConfig& cfg, const SupportConfig& cfg,
const PointSet& headless_pts, const PointSet& headless_pts,
@ -1606,32 +1816,48 @@ bool SLASupportTree::generate(const PointSet &points,
// We will sink the pins into the model surface for a distance of 1/3 of // We will sink the pins into the model surface for a distance of 1/3 of
// the pin radius // the pin radius
for(int i = 0; i < headless_pts.rows(); i++) { tifcl(); for(int i = 0; i < headless_pts.rows(); i++) { tifcl();
Vec3d sp = headless_pts.row(i); Vec3d sph = headless_pts.row(i); // Exact support position
Vec3d n = headless_norm.row(i); // mesh outward normal
Vec3d n = headless_norm.row(i); Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
sp = sp - n * HWIDTH_MM;
Vec3d dir = {0, 0, -1}; Vec3d dir = {0, 0, -1};
Vec3d sj = sp + R * n; Vec3d sj = sp + R * n; // stick start point
// This is only for checking
double idist = bridge_mesh_intersect(sph, dir, R, emesh, true);
double dist = ray_mesh_intersect(sj, dir, emesh); double dist = ray_mesh_intersect(sj, dir, emesh);
if(std::isinf(dist) || std::isnan(dist) || dist < 2*R) continue; if(std::isinf(idist) || std::isnan(idist) || idist < 2*R ||
std::isinf(dist) || std::isnan(dist) || dist < 2*R) {
BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless"
<< " support stick at: "
<< sj.transpose();
continue;
}
Vec3d ej = sj + (dist + HWIDTH_MM)* dir; Vec3d ej = sj + (dist + HWIDTH_MM)* dir;
result.add_compact_bridge(sp, ej, n, R); result.add_compact_bridge(sp, ej, n, R);
} }
}; };
using std::ref; // Now that the individual blocks are defined, lets connect the wires. We
using std::cref; // will create an array of functions which represents a program. Place the
// step methods in the array and bind the right arguments to the methods
// This way the data dependencies will be easily traceable between
// individual steps.
// There will be empty steps as well like the begin step or the done or
// abort steps. These are slots for future initialization or cleanup.
using std::cref; // Bind inputs with cref (read-only)
using std::ref; // Bind outputs with ref (writable)
using std::bind; using std::bind;
// Here we can easily track what goes in and what comes out of each step: // Here we can easily track what goes in and what comes out of each step:
// (see the cref-s as inputs and ref-s as outputs) // (see the cref-s as inputs and ref-s as outputs)
std::array<std::function<void()>, NUM_STEPS> program = { std::array<std::function<void()>, NUM_STEPS> program = {
[] () { [] () {
// Begin // Begin...
// clear up the shared data // Potentially clear up the shared data (not needed for now)
}, },
// Filtering unnecessary support points // Filtering unnecessary support points
@ -1674,6 +1900,7 @@ bool SLASupportTree::generate(const PointSet &points,
Steps pc = BEGIN, pc_prev = BEGIN; Steps pc = BEGIN, pc_prev = BEGIN;
// Let's define a simple automaton that will run our program.
auto progress = [&ctl, &pc, &pc_prev] () { auto progress = [&ctl, &pc, &pc_prev] () {
static const std::array<std::string, NUM_STEPS> stepstr { static const std::array<std::string, NUM_STEPS> stepstr {
"Starting", "Starting",
@ -1795,7 +2022,7 @@ SLASupportTree::SLASupportTree(const PointSet &points,
const Controller &ctl): const Controller &ctl):
m_impl(new Impl(ctl)) m_impl(new Impl(ctl))
{ {
m_impl->ground_level = emesh.ground_level - cfg.object_elevation_mm; m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm;
generate(points, emesh, cfg, ctl); generate(points, emesh, cfg, ctl);
} }

View File

@ -78,7 +78,7 @@ struct SupportConfig {
double object_elevation_mm = 10; double object_elevation_mm = 10;
// The max Z angle for a normal at which it will get completely ignored. // The max Z angle for a normal at which it will get completely ignored.
double normal_cutoff_angle = 110.0 * M_PI / 180.0; double normal_cutoff_angle = 150.0 * M_PI / 180.0;
}; };
@ -104,18 +104,90 @@ struct Controller {
/// An index-triangle structure for libIGL functions. Also serves as an /// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree /// alternative (raw) input format for the SLASupportTree
struct EigenMesh3D { class EigenMesh3D {
Eigen::MatrixXd V; class AABBImpl;
Eigen::MatrixXi F;
double ground_level = 0; Eigen::MatrixXd m_V;
Eigen::MatrixXi m_F;
double m_ground_level = 0;
std::unique_ptr<AABBImpl> m_aabb;
public:
EigenMesh3D(const TriangleMesh&);
EigenMesh3D(const EigenMesh3D& other);
EigenMesh3D& operator=(const EigenMesh3D&);
~EigenMesh3D();
inline double ground_level() const { return m_ground_level; }
inline const Eigen::MatrixXd& V() const { return m_V; }
inline const Eigen::MatrixXi& F() const { return m_F; }
// Result of a raycast
class hit_result {
double m_t = std::numeric_limits<double>::infinity();
int m_face_id = -1;
const EigenMesh3D& m_mesh;
Vec3d m_dir;
inline hit_result(const EigenMesh3D& em): m_mesh(em) {}
friend class EigenMesh3D;
public:
inline double distance() const { return m_t; }
inline int face() const { return m_face_id; }
inline Vec3d normal() const {
if(m_face_id < 0) return {};
auto trindex = m_mesh.m_F.row(m_face_id);
const Vec3d& p1 = m_mesh.V().row(trindex(0));
const Vec3d& p2 = m_mesh.V().row(trindex(1));
const Vec3d& p3 = m_mesh.V().row(trindex(2));
Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1;
return U.cross(V).normalized();
}
inline bool is_inside() {
return m_face_id >= 0 && normal().dot(m_dir) > 0;
}
};
// Casting a ray on the mesh, returns the distance where the hit occures.
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
class si_result {
double m_value;
int m_fidx;
Vec3d m_p;
si_result(double val, int i, const Vec3d& c):
m_value(val), m_fidx(i), m_p(c) {}
friend class EigenMesh3D;
public:
si_result() = delete;
double value() const { return m_value; }
operator double() const { return m_value; }
const Vec3d& point_on_mesh() const { return m_p; }
int F_idx() const { return m_fidx; }
};
// The signed distance from a point to the mesh. Outputs the distance,
// the index of the triangle and the closest point in mesh coordinate space.
si_result signed_distance(const Vec3d& p) const;
bool inside(const Vec3d& p) const;
}; };
using PointSet = Eigen::MatrixXd; using PointSet = Eigen::MatrixXd;
EigenMesh3D to_eigenmesh(const TriangleMesh& m); //EigenMesh3D to_eigenmesh(const TriangleMesh& m);
// needed for find best rotation // needed for find best rotation
EigenMesh3D to_eigenmesh(const ModelObject& model); //EigenMesh3D to_eigenmesh(const ModelObject& model);
// Simple conversion of 'vector of points' to an Eigen matrix // Simple conversion of 'vector of points' to an Eigen matrix
PointSet to_point_set(const std::vector<Vec3d>&); PointSet to_point_set(const std::vector<Vec3d>&);

View File

@ -3,6 +3,9 @@
#include "SLA/SLABoilerPlate.hpp" #include "SLA/SLABoilerPlate.hpp"
#include "SLA/SLASpatIndex.hpp" #include "SLA/SLASpatIndex.hpp"
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
#undef PI
// HEAVY headers... takes eternity to compile // HEAVY headers... takes eternity to compile
// for concave hull merging decisions // for concave hull merging decisions
@ -12,6 +15,7 @@
#include <igl/ray_mesh_intersect.h> #include <igl/ray_mesh_intersect.h>
#include <igl/point_mesh_squared_distance.h> #include <igl/point_mesh_squared_distance.h>
#include <igl/remove_duplicate_vertices.h> #include <igl/remove_duplicate_vertices.h>
#include <igl/signed_distance.h>
#include "SLASpatIndex.hpp" #include "SLASpatIndex.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
@ -19,6 +23,13 @@
namespace Slic3r { namespace Slic3r {
namespace sla { namespace sla {
// Bring back PI from the igl namespace
using igl::PI;
/* **************************************************************************
* SpatIndex implementation
* ************************************************************************** */
class SpatIndex::Impl { class SpatIndex::Impl {
public: public:
using BoostIndex = boost::geometry::index::rtree< SpatElement, using BoostIndex = boost::geometry::index::rtree< SpatElement,
@ -78,6 +89,101 @@ size_t SpatIndex::size() const
return m_impl->m_store.size(); return m_impl->m_store.size();
} }
/* ****************************************************************************
* EigenMesh3D implementation
* ****************************************************************************/
class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> {
public:
igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree;
};
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
static const double dEPS = 1e-6;
const stl_file& stl = tmesh.stl;
auto&& bb = tmesh.bounding_box();
m_ground_level += bb.min(Z);
Eigen::MatrixXd V;
Eigen::MatrixXi F;
V.resize(3*stl.stats.number_of_facets, 3);
F.resize(stl.stats.number_of_facets, 3);
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
const stl_facet* facet = stl.facet_start+i;
V(3*i+0, 0) = double(facet->vertex[0](0));
V(3*i+0, 1) = double(facet->vertex[0](1));
V(3*i+0, 2) = double(facet->vertex[0](2));
V(3*i+1, 0) = double(facet->vertex[1](0));
V(3*i+1, 1) = double(facet->vertex[1](1));
V(3*i+1, 2) = double(facet->vertex[1](2));
V(3*i+2, 0) = double(facet->vertex[2](0));
V(3*i+2, 1) = double(facet->vertex[2](1));
V(3*i+2, 2) = double(facet->vertex[2](2));
F(i, 0) = int(3*i+0);
F(i, 1) = int(3*i+1);
F(i, 2) = int(3*i+2);
}
// We will convert this to a proper 3d mesh with no duplicate points.
Eigen::VectorXi SVI, SVJ;
igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F);
// Build the AABB accelaration tree
m_aabb->init(m_V, m_F);
m_aabb->windtree.set_mesh(m_V, m_F);
}
EigenMesh3D::~EigenMesh3D() {}
EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
m_aabb( new AABBImpl(*other.m_aabb) ) {}
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
{
m_V = other.m_V;
m_F = other.m_F;
m_ground_level = other.m_ground_level;
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
}
EigenMesh3D::hit_result
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
{
igl::Hit hit;
hit.t = std::numeric_limits<float>::infinity();
m_aabb->intersect_ray(m_V, m_F, s, dir, hit);
hit_result ret(*this);
ret.m_t = double(hit.t);
ret.m_dir = dir;
if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id;
return ret;
}
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree,
p, sign, sqdst, i, c);
return si_result(sign * std::sqrt(sqdst), i, c);
}
bool EigenMesh3D::inside(const Vec3d &p) const {
return m_aabb->windtree.inside(p);
}
/* ****************************************************************************
* Misc functions
* ****************************************************************************/
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
double eps = 0.05) double eps = 0.05)
{ {
@ -93,35 +199,27 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
return std::sqrt(p.transpose() * p); return std::sqrt(p.transpose() * p);
} }
PointSet normals(const PointSet& points, const EigenMesh3D& emesh, PointSet normals(const PointSet& points, const EigenMesh3D& mesh,
double eps, double eps,
std::function<void()> throw_on_cancel) { std::function<void()> throw_on_cancel) {
if(points.rows() == 0 || emesh.V.rows() == 0 || emesh.F.rows() == 0) if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
return {}; return {};
Eigen::VectorXd dists; Eigen::VectorXd dists;
Eigen::VectorXi I; Eigen::VectorXi I;
PointSet C; PointSet C;
// We need to remove duplicate vertices and have a true index triangle igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C);
// structure
EigenMesh3D mesh;
Eigen::VectorXi SVI, SVJ;
static const double dEPS = 1e-6;
igl::remove_duplicate_vertices(emesh.V, emesh.F, dEPS,
mesh.V, SVI, SVJ, mesh.F);
igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C);
PointSet ret(I.rows(), 3); PointSet ret(I.rows(), 3);
for(int i = 0; i < I.rows(); i++) { for(int i = 0; i < I.rows(); i++) {
throw_on_cancel(); throw_on_cancel();
auto idx = I(i); auto idx = I(i);
auto trindex = mesh.F.row(idx); auto trindex = mesh.F().row(idx);
const Vec3d& p1 = mesh.V.row(trindex(0)); const Vec3d& p1 = mesh.V().row(trindex(0));
const Vec3d& p2 = mesh.V.row(trindex(1)); const Vec3d& p2 = mesh.V().row(trindex(1));
const Vec3d& p3 = mesh.V.row(trindex(2)); const Vec3d& p3 = mesh.V().row(trindex(2));
// We should check if the point lies on an edge of the hosting triangle. // We should check if the point lies on an edge of the hosting triangle.
// If it does than all the other triangles using the same two points // If it does than all the other triangles using the same two points
@ -159,18 +257,18 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh,
// vector for the neigboring triangles including the detected one. // vector for the neigboring triangles including the detected one.
std::vector<Vec3i> neigh; std::vector<Vec3i> neigh;
if(ic >= 0) { // The point is right on a vertex of the triangle if(ic >= 0) { // The point is right on a vertex of the triangle
for(int n = 0; n < mesh.F.rows(); ++n) { for(int n = 0; n < mesh.F().rows(); ++n) {
throw_on_cancel(); throw_on_cancel();
Vec3i ni = mesh.F.row(n); Vec3i ni = mesh.F().row(n);
if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
neigh.emplace_back(ni); neigh.emplace_back(ni);
} }
} }
else if(ia >= 0 && ib >= 0) { // the point is on and edge else if(ia >= 0 && ib >= 0) { // the point is on and edge
// now get all the neigboring triangles // now get all the neigboring triangles
for(int n = 0; n < mesh.F.rows(); ++n) { for(int n = 0; n < mesh.F().rows(); ++n) {
throw_on_cancel(); throw_on_cancel();
Vec3i ni = mesh.F.row(n); Vec3i ni = mesh.F().row(n);
if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
(ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) (ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
neigh.emplace_back(ni); neigh.emplace_back(ni);
@ -180,9 +278,9 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh,
// Calculate the normals for the neighboring triangles // Calculate the normals for the neighboring triangles
std::vector<Vec3d> neighnorms; neighnorms.reserve(neigh.size()); std::vector<Vec3d> neighnorms; neighnorms.reserve(neigh.size());
for(const Vec3i& tri : neigh) { for(const Vec3i& tri : neigh) {
const Vec3d& pt1 = mesh.V.row(tri(0)); const Vec3d& pt1 = mesh.V().row(tri(0));
const Vec3d& pt2 = mesh.V.row(tri(1)); const Vec3d& pt2 = mesh.V().row(tri(1));
const Vec3d& pt3 = mesh.V.row(tri(2)); const Vec3d& pt3 = mesh.V().row(tri(2));
Eigen::Vector3d U = pt2 - pt1; Eigen::Vector3d U = pt2 - pt1;
Eigen::Vector3d V = pt3 - pt1; Eigen::Vector3d V = pt3 - pt1;
neighnorms.emplace_back(U.cross(V).normalized()); neighnorms.emplace_back(U.cross(V).normalized());
@ -222,16 +320,6 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh,
return ret; return ret;
} }
double ray_mesh_intersect(const Vec3d& s,
const Vec3d& dir,
const EigenMesh3D& m)
{
igl::Hit hit;
hit.t = std::numeric_limits<float>::infinity();
igl::ray_mesh_intersect(s, dir, m.V, m.F, hit);
return double(hit.t);
}
// Clustering a set of points by the given criteria // Clustering a set of points by the given criteria
ClusteredPoints cluster( ClusteredPoints cluster(
const sla::PointSet& points, const sla::PointSet& points,
@ -309,53 +397,5 @@ ClusteredPoints cluster(
return result; return result;
} }
using Segments = std::vector<std::pair<Vec2d, Vec2d>>;
Segments model_boundary(const EigenMesh3D& emesh, double offs)
{
Segments ret;
Polygons pp;
pp.reserve(size_t(emesh.F.rows()));
for (int i = 0; i < emesh.F.rows(); i++) {
auto trindex = emesh.F.row(i);
auto& p1 = emesh.V.row(trindex(0));
auto& p2 = emesh.V.row(trindex(1));
auto& p3 = emesh.V.row(trindex(2));
Polygon p;
p.points.resize(3);
p.points[0] = Point::new_scale(p1(X), p1(Y));
p.points[1] = Point::new_scale(p2(X), p2(Y));
p.points[2] = Point::new_scale(p3(X), p3(Y));
p.make_counter_clockwise();
pp.emplace_back(p);
}
ExPolygons merged = union_ex(Slic3r::offset(pp, float(scale_(offs))), true);
for(auto& expoly : merged) {
auto lines = expoly.lines();
for(Line& l : lines) {
Vec2d a(l.a(X) * SCALING_FACTOR, l.a(Y) * SCALING_FACTOR);
Vec2d b(l.b(X) * SCALING_FACTOR, l.b(Y) * SCALING_FACTOR);
ret.emplace_back(std::make_pair(a, b));
}
}
return ret;
}
//struct SegmentIndex {
//};
//using SegmentIndexEl = std::pair<Segment, unsigned>;
//SegmentIndexEl
} }
} }

View File

@ -29,6 +29,8 @@ public:
SupportTreePtr support_tree_ptr; // the supports SupportTreePtr support_tree_ptr; // the supports
SlicedSupports support_slices; // sliced supports SlicedSupports support_slices; // sliced supports
std::vector<LevelID> level_ids; std::vector<LevelID> level_ids;
inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {}
}; };
namespace { namespace {
@ -503,8 +505,8 @@ void SLAPrint::process()
// support points. Then we sprinkle the rest of the mesh. // support points. Then we sprinkle the rest of the mesh.
auto support_points = [this, ilh](SLAPrintObject& po) { auto support_points = [this, ilh](SLAPrintObject& po) {
const ModelObject& mo = *po.m_model_object; const ModelObject& mo = *po.m_model_object;
po.m_supportdata.reset(new SLAPrintObject::SupportData()); po.m_supportdata.reset(
po.m_supportdata->emesh = sla::to_eigenmesh(po.transformed_mesh()); new SLAPrintObject::SupportData(po.transformed_mesh()) );
// If supports are disabled, we can skip the model scan. // If supports are disabled, we can skip the model scan.
if(!po.m_config.supports_enable.getBool()) return; if(!po.m_config.supports_enable.getBool()) return;